A bulk period logger example in C#.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace Logging
{
/// <summary>
/// Represents a type used to perform logging.
/// </summary>
/// <remarks>
/// Aggregates most logging patterns to a single method.
/// Source: https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/ILogger.cs
/// </remarks>
public interface ILogger
{
/// <summary>
/// Writes a log entry.
/// </summary>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">Id of the event.</param>
/// <param name="state">The entry to be written. Can be also an object.</param>
/// <param name="exception">The exception related to this entry.</param>
/// <param name="formatter">Function to create a <c>string</c> message of the <paramref name="state"/> and <paramref name="exception"/>.</param>
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
/// <summary>
/// Checks if the given <paramref name="logLevel"/> is enabled.
/// </summary>
/// <param name="logLevel">level to be checked.</param>
/// <returns><c>true</c> if enabled.</returns>
bool IsEnabled(LogLevel logLevel);
/// <summary>
/// Begins a logical operation scope.
/// </summary>
/// <param name="state">The identifier for the scope.</param>
/// <returns>An IDisposable that ends the logical operation scope on dispose.</returns>
IDisposable BeginScope<TState>(TState state);
}
/// <summary>
/// ILogger extension methods for common scenarios.
/// </summary>
/// <remarks>https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/LoggerExtensions.cs</remarks>
public static class LoggerExtensions
{
private static readonly Func<FormattedLogValues, Exception, string> _messageFormatter = MessageFormatter;
//------------------------------------------DEBUG------------------------------------------//
/// <summary>
/// Formats and writes a debug log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogDebug(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogDebug(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Debug, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes a debug log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogDebug(0, "Processing request from {Address}", address)</example>
public static void LogDebug(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Debug, eventId, message, args);
}
/// <summary>
/// Formats and writes a debug log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogDebug(exception, "Error while processing request from {Address}", address)</example>
public static void LogDebug(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Debug, exception, message, args);
}
/// <summary>
/// Formats and writes a debug log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogDebug("Processing request from {Address}", address)</example>
public static void LogDebug(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Debug, message, args);
}
//------------------------------------------TRACE------------------------------------------//
/// <summary>
/// Formats and writes a trace log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogTrace(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogTrace(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Trace, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes a trace log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogTrace(0, "Processing request from {Address}", address)</example>
public static void LogTrace(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Trace, eventId, message, args);
}
/// <summary>
/// Formats and writes a trace log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogTrace(exception, "Error while processing request from {Address}", address)</example>
public static void LogTrace(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Trace, exception, message, args);
}
/// <summary>
/// Formats and writes a trace log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogTrace("Processing request from {Address}", address)</example>
public static void LogTrace(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Trace, message, args);
}
//------------------------------------------INFORMATION------------------------------------------//
/// <summary>
/// Formats and writes an informational log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogInfo(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogInfo(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Info, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes an informational log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogInfo(0, "Processing request from {Address}", address)</example>
public static void LogInfo(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Info, eventId, message, args);
}
/// <summary>
/// Formats and writes an informational log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogInfo(exception, "Error while processing request from {Address}", address)</example>
public static void LogInfo(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Info, exception, message, args);
}
/// <summary>
/// Formats and writes an informational log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogInfo("Processing request from {Address}", address)</example>
public static void LogInfo(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Info, message, args);
}
//------------------------------------------WARNING------------------------------------------//
/// <summary>
/// Formats and writes a warning log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogWarning(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogWarning(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Warning, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes a warning log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogWarning(0, "Processing request from {Address}", address)</example>
public static void LogWarning(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Warning, eventId, message, args);
}
/// <summary>
/// Formats and writes a warning log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogWarning(exception, "Error while processing request from {Address}", address)</example>
public static void LogWarning(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Warning, exception, message, args);
}
/// <summary>
/// Formats and writes a warning log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogWarning("Processing request from {Address}", address)</example>
public static void LogWarning(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Warning, message, args);
}
//------------------------------------------ERROR------------------------------------------//
/// <summary>
/// Formats and writes an error log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogError(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogError(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Error, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes an error log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogError(0, "Processing request from {Address}", address)</example>
public static void LogError(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Error, eventId, message, args);
}
/// <summary>
/// Formats and writes an error log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogError(exception, "Error while processing request from {Address}", address)</example>
public static void LogError(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Error, exception, message, args);
}
/// <summary>
/// Formats and writes an error log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogError("Processing request from {Address}", address)</example>
public static void LogError(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Error, message, args);
}
//------------------------------------------CRITICAL------------------------------------------//
/// <summary>
/// Formats and writes a critical log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogCritical(0, exception, "Error while processing request from {Address}", address)</example>
public static void LogCritical(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Critical, eventId, exception, message, args);
}
/// <summary>
/// Formats and writes a critical log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogCritical(0, "Processing request from {Address}", address)</example>
public static void LogCritical(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Critical, eventId, message, args);
}
/// <summary>
/// Formats and writes a critical log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogCritical(exception, "Error while processing request from {Address}", address)</example>
public static void LogCritical(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Critical, exception, message, args);
}
/// <summary>
/// Formats and writes a critical log message.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <example>logger.LogCritical("Processing request from {Address}", address)</example>
public static void LogCritical(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Critical, message, args);
}
/// <summary>
/// Formats and writes a log message at the specified log level.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="message">Format string of the log message.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static void Log(this ILogger logger, LogLevel logLevel, string message, params object[] args)
{
logger.Log(logLevel, 0, null, message, args);
}
/// <summary>
/// Formats and writes a log message at the specified log level.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="message">Format string of the log message.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, string message, params object[] args)
{
logger.Log(logLevel, eventId, null, message, args);
}
/// <summary>
/// Formats and writes a log message at the specified log level.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static void Log(this ILogger logger, LogLevel logLevel, Exception exception, string message, params object[] args)
{
logger.Log(logLevel, 0, exception, message, args);
}
/// <summary>
/// Formats and writes a log message at the specified log level.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">The event id associated with the log.</param>
/// <param name="exception">The exception to log.</param>
/// <param name="message">Format string of the log message.</param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
logger.Log(logLevel, eventId, new FormattedLogValues(message, args), exception, _messageFormatter);
}
//------------------------------------------Scope------------------------------------------//
/// <summary>
/// Formats the message and creates a scope.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to create the scope in.</param>
/// <param name="messageFormat">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param>
/// <param name="args">An object array that contains zero or more objects to format.</param>
/// <returns>A disposable scope object. Can be null.</returns>
/// <example>
/// using(logger.BeginScope("Processing request from {Address}", address))
/// {
/// }
/// </example>
public static IDisposable BeginScope(
this ILogger logger,
string messageFormat,
params object[] args)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return logger.BeginScope(new FormattedLogValues(messageFormat, args));
}
//------------------------------------------HELPERS------------------------------------------//
private static string MessageFormatter(FormattedLogValues state, Exception error)
{
return state.ToString();
}
}
/// <summary>
/// EventId struct
/// </summary>
/// <remarks>Source: https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/EventId.cs</remarks>
public struct EventId
{
public static implicit operator EventId(int id)
{
return new EventId(id);
}
public static bool operator ==(EventId left, EventId right)
{
return left.Equals(right);
}
public static bool operator !=(EventId left, EventId right)
{
return !left.Equals(right);
}
public EventId(int id, string name = null)
{
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
public override string ToString()
{
return Name ?? Id.ToString();
}
public bool Equals(EventId other)
{
return Id == other.Id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is EventId eventId && Equals(eventId);
}
public override int GetHashCode()
{
return Id;
}
}
public static class LogLevelOutputMapping
{
public static string Get(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
return "Success";
case LogLevel.Debug:
return "Debug";
case LogLevel.Info:
return "Info";
case LogLevel.Warning:
return "Warning";
case LogLevel.Error:
return "Error";
case LogLevel.Critical:
return "Critical";
case LogLevel.None:
return "None";
default:
return "None";
}
}
}
internal class LoggingRetryExhaustedException : Exception
{
public LoggingRetryExhaustedException() : base() { }
public LoggingRetryExhaustedException(string message) : base(message) { }
public LoggingRetryExhaustedException(string message, Exception inner) : base(message, inner) { }
}
/// <summary>
/// This is a static class instead of an enum so that we can pass into Log Methods with casting to int.
/// We cannot change parameter of these methods because it is not our code.
/// </summary>
internal static class LogDestination
{
public const int All = 0;
public const int File = 1;
public const int Console = 2;
}
public class LoggingRetryHelper
{
private static ILogger Logger { get; } = MigratorLogging.CreateLogger<LoggingRetryHelper>();
public static void Retry(Action function, int retryCount, int secsDelay = 1)
{
Exception exception = null;
var succeeded = true;
for (var i = 0; i < retryCount; i++)
{
try
{
succeeded = true;
function();
return;
}
catch (Exception ex)
{
exception = ex;
succeeded = false;
Logger.LogInfo(LogDestination.Console, $"Sleeping for {secsDelay} seconds and retrying again for logging");
var task = Task.Delay(secsDelay * 1000);
task.Wait();
// add 1 second to delay so that each delay is slightly incrementing in wait time
secsDelay += 1;
}
finally
{
if (succeeded && i >= 1)
{
Logger.LogSuccess(LogDestination.File, $"Logging request succeeded.");
}
}
}
if (exception is null)
{
throw new LoggingRetryExhaustedException($"Retry count exhausted for logging request.");
}
throw exception;
}
}
/// <summary>
/// Logging Constants.
/// </summary>
/// <remarks>https://github.com/Microsoft/vsts-work-item-migrator/blob/master/Logging/LoggingConstants.cs</remarks>
public static class LoggingConstants
{
// Time interval in seconds for how often we check to see if it is time to write to the log file.
public const int CheckInterval = 1;
// Time interval in seconds for the maximum amount of time we will wait before writing to the log file.
public const int LogInterval = 3;
// Maximum number of items we can have in Queue before writing to the log file.
public const long LogItemsUnLoggedLimit = 500;
}
public class LogItem
{
public LogLevel LogLevel { get; }
private DateTime DateTimeStamp { get; }
public string Message { get; private set; }
public Exception Exception { get; }
public int LogDestination { get; }
public LogItem(LogLevel logLevel, DateTime dateTimeStamp, string message, int logDestination)
{
this.LogLevel = logLevel;
this.DateTimeStamp = dateTimeStamp;
this.Message = message;
this.Exception = null;
this.LogDestination = logDestination;
}
public LogItem(LogLevel logLevel, DateTime dateTimeStamp, string message, Exception exception, int logDestination)
{
this.LogLevel = logLevel;
this.DateTimeStamp = dateTimeStamp;
this.Message = message;
this.Exception = exception;
this.LogDestination = logDestination;
}
public string OutputFormat(bool includeExceptionMessage, bool includeLogLevelTimeStamp)
{
if (includeExceptionMessage && this.Exception != null && this.Exception.Message != null)
{
this.Message = $"{this.Message}. {this.Exception.Message}";
}
if (includeLogLevelTimeStamp)
{
// HH specifies 24-hour time format
string timeStamp = DateTimeStampString();
string logLevelName = LogLevelName();
return $"[{logLevelName} @{timeStamp}] {this.Message}";
}
else
{
return this.Message;
}
}
public virtual string DateTimeStampString()
{
return this.DateTimeStamp.ToString("HH.mm.ss.fff");
}
public virtual string LogLevelName()
{
return LogLevelOutputMapping.Get(this.LogLevel);
}
}
public class BulkLogger
{
// we can only let one thread in at a time to log to the file
private static object lockObject = new object();
private ConcurrentQueue<LogItem> bulkLoggerLogItems;
private Stopwatch stopwatch;
private Timer bulkLoggerCheckTimer;
private string filePath;
public class BulkLogger
{
// we can only let one thread in at a time to log to the file
private static readonly object LockObject = new object();
private readonly ConcurrentQueue<LogItem> _bulkLoggerLogItems;
private readonly Stopwatch _stopwatch;
// ReSharper disable NotAccessedField.Local
private Timer _bulkLoggerCheckTimer;
// ReSharper restore NotAccessedField.Local
private readonly string _filePath;
public BulkLogger()
{
_bulkLoggerLogItems = new ConcurrentQueue<LogItem>();
_bulkLoggerCheckTimer = new Timer(BulkLoggerCheck, "Some state", TimeSpan.FromSeconds(LoggingConstants.CheckInterval), TimeSpan.FromSeconds(LoggingConstants.CheckInterval));
_stopwatch = Stopwatch.StartNew();
_filePath = GetFilePathBasedOnTime();
Console.WriteLine($"Detailed logging sent to file: {Directory.GetCurrentDirectory()}\\{_filePath}");
}
public void WriteToQueue(LogItem logItem)
{
if (logItem != null)
{
_bulkLoggerLogItems.Enqueue(logItem);
}
}
private string GetFilePathBasedOnTime()
{
try
{
var currentDateTime = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
return $"WiMigrator_Migrate_{currentDateTime}.log";
}
catch (Exception ex)
{
const string defaultFileName = "WiMigrator_Migrate_LogFile";
Console.WriteLine($"Could not give log file a special name due to below Exception. Naming the file: \"{defaultFileName}\" instead.\n{ex}");
return defaultFileName;
}
}
private void BulkLoggerCheck(object state)
{
if (LogIntervalHasElapsed() || LogItemsHasReachedLimit())
{
BulkLoggerLog();
}
}
private bool LogIntervalHasElapsed()
{
var elapsedMilliseconds = _stopwatch.ElapsedMilliseconds;
return elapsedMilliseconds >= LoggingConstants.LogInterval * 1000;
}
private bool LogItemsHasReachedLimit()
{
return _bulkLoggerLogItems.Count >= LoggingConstants.LogItemsUnLoggedLimit;
}
public void BulkLoggerLog()
{
_stopwatch.Restart();
var outputBatchSb = new StringBuilder();
if (_bulkLoggerLogItems.Count > 0)
{
// We will only dequeue and write the count of items determined
// at the beginning of iteration.
// Then we will have a predictable end in the case that items
// are being enqueued during the iteration.
var startingLength = _bulkLoggerLogItems.Count;
for (var i = 0; i < startingLength; i++)
{
if (_bulkLoggerLogItems.TryDequeue(out var logItem))
{
var output = logItem.OutputFormat(false, true);
if (logItem.Exception != null)
{
output = $"{output}\n{logItem.Exception}";
}
outputBatchSb.Append(output);
outputBatchSb.AppendLine();
}
}
WriteToFile(outputBatchSb.ToString());
}
}
private void WriteToFile(string content)
{
try
{
LoggingRetryHelper.Retry(() => { AppendToFile(content); }, 5);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Cannot write to the log file because you are not authorized to access it. Please try running this application as administrator or moving it to a folder location that does not require special access.");
throw;
}
catch (PathTooLongException)
{
Console.WriteLine("Cannot write to the log file because the file path is too long. Please store your files for this WiMigrator application in a folder location with a shorter path name.");
throw;
}
catch (Exception ex)
{
Console.WriteLine($"Cannot write to the log file: {ex.Message}");
}
}
private void AppendToFile(string content)
{
// since we support multi-threading, ensure only one thread
// accesses the file at a time.
lock (LockObject)
{
using (var streamWriter = File.AppendText(_filePath))
{
streamWriter.Write(content);
}
}
}
}
}