Last active
April 13, 2017 10:44
-
-
Save Rene-Sackers/dbce83c934aa697385d4165e542948b7 to your computer and use it in GitHub Desktop.
Async log writer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum LogMessageSeverity | |
{ | |
Info, | |
Warning, | |
Error | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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