Skip to content

Instantly share code, notes, and snippets.

@Rene-Sackers
Last active April 13, 2017 10:44
Show Gist options
  • Save Rene-Sackers/dbce83c934aa697385d4165e542948b7 to your computer and use it in GitHub Desktop.
Save Rene-Sackers/dbce83c934aa697385d4165e542948b7 to your computer and use it in GitHub Desktop.
Async log writer
public interface ILogger
{
void Write(string message, LogMessageSeverity severity = LogMessageSeverity.Info);
void Write(object message, LogMessageSeverity severity = LogMessageSeverity.Info);
void Write(Exception exception, LogMessageSeverity severity = LogMessageSeverity.Error);
}
public class Logger : ILogger, IDisposable
{
private const int LogFileMaxSizeInMegabytes = 5;
private readonly TaskQueue _taskQueue = new TaskQueue(1, false);
private StreamWriter _logFileStream;
public Logger()
{
var logFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".log");
DeleteLogFileIfExceedsMaxSize(logFilePath);
EnsureLogFile(logFilePath);
OpenLogFileStream(logFilePath);
}
private static void EnsureLogFile(string logFilePath)
{
if (File.Exists(logFilePath)) return;
try
{
File.Create(logFilePath).Dispose();
}
catch (Exception ex)
{
// Failed to create file
}
}
private static void DeleteLogFileIfExceedsMaxSize(string logFilePath)
{
if (!File.Exists(logFilePath)) return;
var fileSize = new FileInfo(logFilePath).Length;
var fileSizeInMegabytes = fileSize / 1024 / 1024;
if (fileSizeInMegabytes < LogFileMaxSizeInMegabytes) return;
try
{
File.Delete(logFilePath);
}
catch (Exception ex)
{
// Failed delete log file exceeding maximum size
}
}
private void OpenLogFileStream(string logFilePath)
{
if (!File.Exists(logFilePath)) return;
try
{
_logFileStream = new StreamWriter(logFilePath, true, Encoding.UTF8) {AutoFlush = true};
}
catch (Exception ex)
{
// Failed to open log file for writing
}
}
private async void TryWriteLogLine(string message, LogMessageSeverity severity)
{
if (_logFileStream == null) return;
message = FormatMessage(message, severity);
try
{
await _taskQueue.Enqueue(() => _logFileStream.WriteLineAsync(message));
}
catch
{
// ignore
}
}
private static string FormatMessage(string message, LogMessageSeverity severity)
{
return $"[{DateTime.Now.ToLongTimeString()}] [{severity}] {message}";
}
private static string GetExceptionText(Exception exception)
{
var exceptionText = string.Empty;
exceptionText += $"Message: {exception.Message}. Exception: {exception}\n";
if (exception.InnerException != null)
exceptionText += GetExceptionText(exception.InnerException);
return exceptionText;
}
public void Write(string message, LogMessageSeverity severity = LogMessageSeverity.Info)
{
TryWriteLogLine(message, severity);
}
public void Write(object message, LogMessageSeverity severity = LogMessageSeverity.Info)
{
TryWriteLogLine(message.ToString(), severity);
}
public void Write(Exception exception, LogMessageSeverity severity = LogMessageSeverity.Error)
{
TryWriteLogLine(GetExceptionText(exception), severity);
}
public void Dispose()
{
_logFileStream?.Dispose();
}
}
public enum LogMessageSeverity
{
Info,
Warning,
Error
}
public class TaskQueue
{
private readonly SemaphoreSlim _semaphore;
private readonly int _queueSize;
private readonly bool _cancelIfOverflow;
private CancellationTokenSource _currentCancellationTokenSource;
public bool IsRunning => _semaphore.CurrentCount == 0;
public TaskQueue(int queueSize = 1, bool cancelIfOverflow = true)
{
_semaphore = new SemaphoreSlim(queueSize);
_queueSize = queueSize;
_cancelIfOverflow = cancelIfOverflow;
}
private void CancelCurrentAndSetNewCancellationToken(CancellationTokenSource cancellationTokenSource)
{
if (!_cancelIfOverflow || _semaphore.CurrentCount < _queueSize) return;
if (_currentCancellationTokenSource?.IsCancellationRequested == false) _currentCancellationTokenSource.Cancel();
_currentCancellationTokenSource = cancellationTokenSource;
}
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator, CancellationTokenSource cancellationTokenSource = null)
{
CancelCurrentAndSetNewCancellationToken(cancellationTokenSource);
await _semaphore.WaitAsync();
try
{
return await taskGenerator();
}
finally
{
_semaphore.Release();
}
}
public async Task Enqueue(Func<Task> taskGenerator, CancellationTokenSource cancellationTokenSource = null)
{
CancelCurrentAndSetNewCancellationToken(cancellationTokenSource);
await _semaphore.WaitAsync();
try
{
await taskGenerator();
}
finally
{
_semaphore.Release();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment