Skip to main content

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);
                }
            }
        }
    }
}