Skip to content

Instantly share code, notes, and snippets.

@Kashkovsky
Created November 15, 2017 15:29
Show Gist options
  • Save Kashkovsky/eba3c91cc9d64e705f18ba9a272f7d92 to your computer and use it in GitHub Desktop.
Save Kashkovsky/eba3c91cc9d64e705f18ba9a272f7d92 to your computer and use it in GitHub Desktop.
C# Logger with log rotation
public abstract class Logger : IDisposable
{
private LogVerbosity _verbosity;
private Queue<Action> _queue = new Queue<Action>();
private ManualResetEvent _hasNewItems = new ManualResetEvent(false);
private ManualResetEvent _terminate = new ManualResetEvent(false);
private ManualResetEvent _waiting = new ManualResetEvent(false);
private Thread _loggingThread;
private static readonly Lazy<Logger> _lazyLog = new Lazy<Logger>(() => {
switch (Settings.Default.LogFlow)
{
case (int)LogFlow.Local:
return new LocalLogger();
case (int)LogFlow.Remote:
return new RemoteLogger();
default:
throw new InvalidOperationException("LogFlow value is invalid. Set valid value in settings based on LogFlow enum.");
}
});
public static Logger Current => _lazyLog.Value;
protected Logger()
{
_verbosity = (LogVerbosity)Settings.Default.LogVerbosity;
_loggingThread = new Thread(new ThreadStart(ProcessQueue)) { IsBackground = true };
_loggingThread.Start();
}
public void Info(string message)
{
Log(message, LogType.INF);
}
public void Debug(string message)
{
Log(message, LogType.DBG);
}
public void Error(string message)
{
Log(message, LogType.ERR);
}
public void Error(Exception e)
{
if (_verbosity != LogVerbosity.None)
{
Log(UnwrapExceptionMessages(e), LogType.ERR);
}
}
public override string ToString() => $"Logger settings: [Type: {this.GetType().Name}, Verbosity: {_verbosity}, ";
protected abstract void CreateLog(string message);
public void Flush() => _waiting.WaitOne();
public void Dispose()
{
_terminate.Set();
_loggingThread.Join();
}
protected virtual string ComposeLogRow(string message, LogType logType) =>
$"[{DateTime.Now.ToString(CultureInfo.InvariantCulture)} {logType}] - {message}";
protected virtual string UnwrapExceptionMessages(Exception ex)
{
if (ex == null)
return string.Empty;
return $"{ex}, Inner exception: {UnwrapExceptionMessages(ex.InnerException)} ";
}
private void ProcessQueue()
{
while (true)
{
_waiting.Set();
int i = WaitHandle.WaitAny(new WaitHandle[] { _hasNewItems, _terminate });
if (i == 1) return;
_hasNewItems.Reset();
_waiting.Reset();
Queue<Action> queueCopy;
lock (_queue)
{
queueCopy = new Queue<Action>(_queue);
_queue.Clear();
}
foreach (var log in queueCopy)
{
log();
}
}
}
private void Log(string message, LogType logType)
{
if (string.IsNullOrEmpty(message))
return;
var logRow = ComposeLogRow(message, logType);
System.Diagnostics.Debug.WriteLine(logRow);
if (_verbosity == LogVerbosity.Full)
{
lock (_queue)
_queue.Enqueue(() => CreateLog(logRow));
_hasNewItems.Set();
}
}
}
class LocalLogger : Logger
{
private const string LogFolderName = "EDC";
private const string LogFileName = "EDC.log";
private readonly int _logChunkSize = Settings.Default.LogChunkSize;
private readonly int _logChunkMaxCount = Settings.Default.LogChunkMaxCount;
private readonly int _logArchiveMaxCount = Settings.Default.LogArchiveMaxCount;
private readonly int _logCleanupPeriod = Settings.Default.LogCleanupPeriod;
protected override void CreateLog(string message)
{
var logFolderPath = Path.Combine(Path.GetTempPath(), LogFolderName);
if (!Directory.Exists(logFolderPath))
Directory.CreateDirectory(logFolderPath);
var logFilePath = Path.Combine(logFolderPath, LogFileName);
Rotate(logFilePath);
using (var sw = File.AppendText(logFilePath))
{
sw.WriteLine(message);
}
}
private void Rotate(string filePath)
{
if (!File.Exists(filePath))
return;
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length < _logChunkSize)
return;
var fileTime = DateTime.Now.ToString("dd_MM_yy_h_m_s");
var rotatedPath = filePath.Replace(".log", $".{fileTime}");
File.Move(filePath, rotatedPath);
var folderPath = Path.GetDirectoryName(rotatedPath);
var logFolderContent = new DirectoryInfo(folderPath).GetFileSystemInfos();
var chunks = logFolderContent.Where(x => !x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase));
if (chunks.Count() <= _logChunkMaxCount)
return;
var archiveFolderInfo = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(rotatedPath), $"{LogFolderName}_{fileTime}"));
foreach(var chunk in chunks)
{
Directory.Move(chunk.FullName, Path.Combine(archiveFolderInfo.FullName, chunk.Name));
}
ZipFile.CreateFromDirectory(archiveFolderInfo.FullName, Path.Combine(folderPath, $"{LogFolderName}_{fileTime}.zip"));
Directory.Delete(archiveFolderInfo.FullName, true);
var archives = logFolderContent.Where(x => x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)).ToArray();
if (archives.Count() <= _logArchiveMaxCount)
return;
var oldestArchive = archives.OrderBy(x => x.CreationTime).First();
var cleanupDate = oldestArchive.CreationTime.AddDays(_logCleanupPeriod);
if (DateTime.Compare(cleanupDate, DateTime.Now) <= 0)
{
foreach (var file in logFolderContent)
{
file.Delete();
}
}
else
File.Delete(oldestArchive.FullName);
}
public override string ToString() => $"{base.ToString()}, Chunk Size: {_logChunkSize}, Max chunk count: {_logChunkMaxCount}, Max log archive count: {_logArchiveMaxCount}, Cleanup period: {_logCleanupPeriod} days]";
}
class RemoteLogger : Logger
{
protected async override void CreateLog(string message)
{
using (var httpClient = HttpClientProvider.CreateHttpClient(await AuthorizationProvider.Instance.GetAccessToken()))
{
var param = JsonConvert.SerializeObject(new { Message = message });
var content = new StringContent(param, Encoding.UTF8, "application/json"); //TODO: OData?
try
{
await httpClient.PostAsync(Settings.Default.LogUrl, new StringContent(param))
.ConfigureAwait(false);
}
catch (HttpRequestException e)
{
System.Diagnostics.Debug.WriteLine($"Error sending log to remote server: {e}");
}
}
}
public override string ToString() => $"{base.ToString()}, Log URL: {Settings.Default.LogUrl}]";
}
enum LogVerbosity
{
None = 0,
Exceptions,
Full
}
public enum LogType
{
INF,
DBG,
ERR
}
enum LogFlow
{
Local = 0,
Remote
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment