[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\n[Bb]in/\n[Oo]bj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n## TODO: Comment the next line if you want to checkin your\n## web deploy settings but do note that will include unencrypted\n## passwords\n#*.pubxml\n\n*.publishproj\n\n# NuGet Packages\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Windows Azure Build Output\ncsx/\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# LightSwitch generated files\nGeneratedArtifacts/\n_Pvt_Extensions/\nModelManifest.xml\n"
  },
  {
    "path": "Easy.Logger/AsyncBufferingForwardingAppender.cs",
    "content": "﻿namespace Easy.Logger\n{\n    using System;\n    using System.Threading;\n    using log4net.Appender;\n    using log4net.Core;\n    using log4net.Util;\n\n    /// <summary>\n    /// An appender which batches the log events and asynchronously forwards them to any configured appenders.\n    /// <seealso href=\"www.nimaara.com/2016/01/01/high-performance-logging-log4net/\"/>\n    /// </summary>\n    public sealed class AsyncBufferingForwardingAppender : BufferingForwardingAppender\n    {\n        private const int DEFAULT_IDLE_TIME = 500;\n\n        private readonly Sequencer<LoggingEvent[]> _sequencer;\n\n        private TimeSpan _idleTimeThreshold;\n        private Timer _idleFlushTimer;\n        private DateTime _lastFlushTime;\n\n        private bool IsIdle\n        {\n            get\n            {\n                if (DateTime.UtcNow - _lastFlushTime >= _idleTimeThreshold) { return true; }\n                return false;\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets the idle-time in milliseconds at which any pending logging events are flushed.\n        /// <value>The idle-time in milliseconds.</value>\n        /// <remarks>\n        /// <para>\n        /// The value should be a positive integer representing the maximum idle-time of logging events \n        /// to be collected in the <see cref=\"AsyncBufferingForwardingAppender\"/>. When this value is \n        /// reached, buffered events are then flushed. By default the idle-time is <c>500</c> milliseconds.\n        /// </para>\n        /// <para>\n        /// If the <see cref=\"IdleTime\"/> is set to a value less than or equal to <c>0</c>\n        /// then use the default value is used.\n        /// </para>\n        /// </remarks>\n        /// </summary>\n        public int IdleTime { get; set; } = DEFAULT_IDLE_TIME;\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"AsyncBufferingForwardingAppender\"/>\n        /// </summary>\n        public AsyncBufferingForwardingAppender()\n        {\n            _sequencer = new Sequencer<LoggingEvent[]>(Process);\n            _sequencer.OnException += (sender, args) \n                => LogLog.Error(GetType(), \"An exception occurred while processing LogEvents.\", args.Exception);\n        }\n\n        /// <summary>\n        /// Activates the options for this appender.\n        /// </summary>\n        public override void ActivateOptions()\n        {\n            base.ActivateOptions();\n\n            LogWarningIfLossy();\n\n            if (IdleTime <= 0) { IdleTime = DEFAULT_IDLE_TIME; }\n            \n            _idleTimeThreshold = TimeSpan.FromMilliseconds(IdleTime);\n            _idleFlushTimer = new Timer(InvokeFlushIfIdle, null, _idleTimeThreshold, _idleTimeThreshold);\n        }\n\n        /// <summary>\n        /// Forwards the events to every configured appender. \n        /// </summary>\n        /// <param name=\"events\">The events that need to be forwarded</param>\n        protected override void SendBuffer(LoggingEvent[] events)\n        {\n            if (!_sequencer.ShutdownRequested)\n            {\n                _sequencer.Enqueue(events);\n            } else\n            {\n                base.SendBuffer(events);\n            }\n        }\n\n        /// <summary>\n        /// Ensures that all pending logging events are flushed out before exiting.\n        /// </summary>\n        protected override void OnClose()\n        {\n            _idleFlushTimer?.Dispose();\n            _sequencer.Shutdown();\n\n            Flush();\n\n            base.OnClose();\n        }\n\n        private void Process(LoggingEvent[] logEvents)\n        {\n            base.SendBuffer(logEvents);\n            _lastFlushTime = DateTime.UtcNow;\n        }\n\n        /// <summary>\n        /// This only flushes if <see cref=\"BufferingAppenderSkeleton.Lossy\"/> is <c>False</c>.\n        /// </summary>\n        private void InvokeFlushIfIdle(object _)\n        {\n            if (!IsIdle) { return; }\n            if (_sequencer.ShutdownRequested) { return; }\n\n            Flush();\n        }\n\n        private void LogWarningIfLossy()\n        {\n            if (!Lossy) { return; }\n\n            var warning = new LoggingEvent(new LoggingEventData\n            {\n                Level = Level.Warn,\n                LoggerName = GetType().Name,\n                ThreadName = Thread.CurrentThread.ManagedThreadId.ToString(),\n                TimeStampUtc = DateTime.UtcNow,\n                Message = \"This is a 'lossy' appender therefore log messages may be dropped.\"\n            });\n\n            Lossy = false;\n            Append(warning);\n            Flush();\n            Lossy = true;\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger/Easy.Logger.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <PackageId>Easy.Logger</PackageId>\n    <Description>A modern high performance cross platform wrapper around Log4Net.</Description>\n    <Authors>Nima Ara</Authors>\n    <Copyright>2024 Nima Ara</Copyright>\n    <PackageTags>log4net;Logging;Easy;Logger;Log</PackageTags>\n    <PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>\n    <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    <PackageReleaseNotes>-</PackageReleaseNotes>\n    <RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>\n    <RepositoryType>git</RepositoryType>\n    <PackageTargetFallback Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <TargetFrameworks>netstandard1.3;net48</TargetFrameworks>\n    <AssemblyTitle>Easy Logger</AssemblyTitle>\n    <AssemblyName>Easy.Logger</AssemblyName>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"log4net\" Version=\"2.0.15\" />\n    <PackageReference Include=\"Easy.Logger.Interfaces\" Version=\"4.0.0\" />\n  </ItemGroup>\n  \n  <PropertyGroup Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">\n    <DefineConstants>$(DefineConstants);NET_STANDARD</DefineConstants>\n  </PropertyGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net40' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">\n    <PackageReference Include=\"System.IO\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Globalization\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Threading\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Threading.Tasks\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Collections.Concurrent\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Reflection\" Version=\"4.3.0\" />\n    <PackageReference Include=\"System.Diagnostics.Debug\" Version=\"4.3.0\" />\n  </ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n</Project>"
  },
  {
    "path": "Easy.Logger/Log4NetLogger.cs",
    "content": "﻿namespace Easy.Logger\n{\n    using System;\n    using System.Diagnostics;\n    using System.Globalization;\n    using Easy.Logger.Interfaces;\n    using log4net;\n    using log4net.Core;\n    using log4net.Util;\n\n    /// <summary>\n    /// A <c>log4net</c> implementation of the <see cref=\"IEasyLogger{T}\"/> interface.\n    /// </summary>\n    public sealed class Log4NetLogger<T> : Log4NetLogger, IEasyLogger<T>\n    {\n        // ReSharper disable once AssignmentIsFullyDiscarded\n        static Log4NetLogger() => _ = Log4NetService.Instance;\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"Log4NetLogger{T}\"/> where the name of the logger is\n        /// set as the name of the type of <typeparamref name=\"T\"/>.\n        /// </summary>\n        public Log4NetLogger() : base(LogManager.GetLogger(typeof(T))) {}\n    }\n    \n    /// <summary>\n    /// A <c>log4net</c> implementation of the <see cref=\"ILogger\"/> interface.\n    /// </summary>\n    public class Log4NetLogger : IEasyLogger\n    {\n\t\tprivate static readonly Type ThisDeclaringType = typeof(Log4NetLogger);\n        private readonly ILog _logger;\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"Log4NetLogger\"/>.\n        /// </summary>\n        /// <param name=\"logger\"></param>\n        protected internal Log4NetLogger(ILog logger) => _logger = logger; \n\n        /// <summary>\n        /// Gets the logger name.\n        /// </summary>\n        public string Name => _logger.Logger.Name;\n\n        /// <summary>\n        /// Returns an <see cref=\"IDisposable\"/> which allows the caller to specify a scope as\n        /// <paramref name=\"name\"/> which will then be rendered as part of the message.\n        /// </summary>\n        /// <param name=\"name\">The name of the scope</param>\n        public IDisposable GetScopedLogger(string name) => new Scope(name);\n\n    #region Levels Enabled\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Trace</c> messages.\n        /// </summary>\n        public bool IsTraceEnabled => IsEnabledFor(Level.Trace);\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Debug</c> messages.\n        /// </summary>\n        public bool IsDebugEnabled => _logger.IsDebugEnabled;\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Info</c> messages.\n        /// </summary>\n        public bool IsInfoEnabled => _logger.IsInfoEnabled;\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Warn</c> messages.\n        /// </summary>\n        public bool IsWarnEnabled => _logger.IsWarnEnabled;\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Error</c> messages.\n        /// </summary>\n        public bool IsErrorEnabled => _logger.IsErrorEnabled;\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Fatal</c> messages.\n        /// </summary>\n        public bool IsFatalEnabled => _logger.IsFatalEnabled;\n\n    #endregion\n\n    #region Trace\n        \n        /// <summary>\n        /// Logs a message object with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Debug</c> enabled by comparing the level of \n        /// this logger with the <c>Trace</c> level. If this logger is <c>Debug</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Debug(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Trace(object message) => LogImpl(Level.Trace, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Trace</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Trace(object message, Exception exception) => LogImpl(Level.Trace, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void TraceFormat(string format, object arg) \n            => LogImpl(Level.Trace, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void TraceFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Trace, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void TraceFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Trace, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void TraceFormat(string format, params object[] args)\n            => LogImpl(\n                Level.Trace, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Trace</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void TraceFormat(IFormatProvider provider, string format, params object[] args) \n            => LogImpl(\n                Level.Trace, \n                new SystemStringFormat(provider, format, args),\n                null);\n#endregion\n\n    #region Debug\n\n        /// <summary>\n        /// Logs a message object with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Debug</c> enabled by comparing the level of \n        /// this logger with the <c>Debug</c> level. If this logger is <c>Debug</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Debug(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Debug(object message) => LogImpl(Level.Debug, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Debug</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Debug(object message, Exception exception) => LogImpl(Level.Debug, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void DebugFormat(string format, object arg) \n            => LogImpl(Level.Debug, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void DebugFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Debug, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void DebugFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Debug, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void DebugFormat(string format, params object[] args) \n            => LogImpl(\n                Level.Debug, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Debug</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Debug(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void DebugFormat(IFormatProvider provider, string format, params object[] args) \n            => LogImpl(\n                Level.Debug, \n                new SystemStringFormat(provider, format, args),\n                null);\n\n    #endregion\n\n    #region Info\n            \n        /// <summary>\n        /// Logs a message object with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Info</c> enabled by comparing the level of \n        /// this logger with the <c>Info</c> level. If this logger is <c>Info</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Info(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Info(object message) => LogImpl(Level.Info, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Info</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Info(object message, Exception exception) => LogImpl(Level.Info, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Info(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void InfoFormat(string format, object arg) \n            => LogImpl(Level.Info, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Info(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void InfoFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Info, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Info(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void InfoFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Info, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Info(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void InfoFormat(string format, params object[] args) \n            => LogImpl(\n                Level.Info, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Info</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Info(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void InfoFormat(IFormatProvider provider, string format, params object[] args) \n            => LogImpl(\n                Level.Info, \n                new SystemStringFormat(provider, format, args),\n                null);\n\n    #endregion\n\n    #region Warn\n            \n        /// <summary>\n        /// Logs a message object with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Warn</c> enabled by comparing the level of \n        /// this logger with the <c>Warn</c> level. If this logger is <c>Warn</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Warn(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Warn(object message) => LogImpl(Level.Warn, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Warn</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Warn(object message, Exception exception) => LogImpl(Level.Warn, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Warn(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void WarnFormat(string format, object arg) \n            => LogImpl(Level.Warn, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Warn(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void WarnFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Warn, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Warn(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void WarnFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Warn, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Warn(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void WarnFormat(string format, params object[] args) \n            => LogImpl(\n                Level.Warn, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Warn</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Warn(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void WarnFormat(IFormatProvider provider, string format, params object[] args) => LogImpl(\n            Level.Warn, \n            new SystemStringFormat(provider, format, args),\n            null);\n\n    #endregion\n\n    #region Error\n\n        /// <summary>\n        /// Logs a message object with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Error</c> enabled by comparing the level of \n        /// this logger with the <c>Error</c> level. If this logger is <c>Error</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Error(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Error(object message) => LogImpl(Level.Error, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Error</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Error(object message, Exception exception) => LogImpl(Level.Error, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Error(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void ErrorFormat(string format, object arg) \n            => LogImpl(Level.Error, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Error(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void ErrorFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Error, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Error(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void ErrorFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Error, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Error(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void ErrorFormat(string format, params object[] args) \n            => LogImpl(\n                Level.Error, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Error</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Error(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void ErrorFormat(IFormatProvider provider, string format, params object[] args) \n            => LogImpl(\n                Level.Error, \n                new SystemStringFormat(provider, format, args),\n                null);\n\n    #endregion\n\n    #region Fatal\n\n        /// <summary>\n        /// Logs a message object with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <remarks>\n        /// <para>\n        /// This method first checks if this logger is <c>Fatal</c> enabled by comparing the level of \n        /// this logger with the <c>Fatal</c> level. If this logger is <c>Fatal</c> \n        /// enabled, then it converts the message object (passed as parameter) to a string by invoking the appropriate\n        /// <see cref=\"T:log4net.ObjectRenderer.IObjectRenderer\"/>. It then proceeds to call all the registered appenders \n        /// in this logger  and also higher in the hierarchy depending on the value of the additivity flag.\n        /// </para>\n        /// <para>\n        /// <b>WARNING</b> Note that passing an<see cref=\"T:System.Exception\"/> to this method \n        /// will print the name of the <see cref=\"T:System.Exception\"/> but no stack trace.\n        /// To print a stack trace use the <see cref=\"M:Fatal(object,Exception)\"/> form instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void Fatal(object message) => LogImpl(Level.Fatal, message, null);\n\n        /// <summary>\n        /// Logs a message object with the <c>Fatal</c> level including \n        /// the stack trace of the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        [DebuggerStepThrough]\n        public void Fatal(object message, Exception exception) => LogImpl(Level.Fatal, message, exception);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. \n        /// See <see cref = \"M:String.Format(string, object[])\" /> for details \n        /// of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Fatal(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void FatalFormat(string format, object arg) \n            => LogImpl(Level.Fatal, new SystemStringFormat(CultureInfo.InvariantCulture, format, arg), null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Fatal(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void FatalFormat(string format, object arg1, object arg2) \n            => LogImpl(\n                Level.Fatal, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2), \n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        /// <remarks>\n        /// <para>\n        /// This method does not take an <see cref= \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Fatal(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void FatalFormat(string format, object arg1, object arg2, object arg3) \n            => LogImpl(\n                Level.Fatal, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref = \"T:System.Exception\"/> object to include in the log event. \n        /// To pass an<see cref=\"T:System.Exception\"/> use one of the <see cref=\"M:Fatal(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void FatalFormat(string format, params object[] args) \n            => LogImpl(\n                Level.Fatal, \n                new SystemStringFormat(CultureInfo.InvariantCulture, format, args),\n                null);\n\n        /// <summary>\n        /// Logs a formatted message string with the <c>Fatal</c> level.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\" /> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        /// <remarks>\n        /// <para>\n        /// The message is formatted using the <c>String.Format</c> method. See <see cref=\"M:String.Format(string, object[])\"/> \n        /// for details of the syntax of the format string and the behavior of the formatting.\n        /// </para>\n        /// <para>\n        /// This method does not take an <see cref=\"T:System.Exception\"/> object to include in the log event. \n        /// To pass an <see cref=\"T:System.Exception\"/>, use one of the <see cref=\"M:Fatal(object,Exception)\"/> methods instead.\n        /// </para>\n        /// </remarks>\n        [DebuggerStepThrough]\n        public void FatalFormat(IFormatProvider provider, string format, params object[] args) \n            => LogImpl(\n                Level.Fatal, \n                new SystemStringFormat(provider, format, args),\n                null);\n\n    #endregion\n\n        private static string PrefixScopesIfAny(string message)\n        {\n            var scopeMsg = Scope.ScopeMessage;\n            return scopeMsg is null ? message : string.Concat(scopeMsg, \" \", message);\n        }\n\n        private bool IsEnabledFor(Level level) => _logger.Logger.IsEnabledFor(level);\n\n        private void LogImpl(Level level, object message, Exception exception)\n        {\n            if (!IsEnabledFor(level)) { return; }\n\n            _logger.Logger.Log(\n                ThisDeclaringType, \n                level, \n                PrefixScopesIfAny(message is string msgStr ? msgStr : message.ToString()), \n                exception);\n        }\n    }\n}\n"
  },
  {
    "path": "Easy.Logger/Log4NetService.cs",
    "content": "﻿namespace Easy.Logger\n{\n    using System;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Reflection;\n    using Easy.Logger.Interfaces;\n    using log4net;\n    using log4net.Config;\n    using log4net.Repository;\n\n    /// <summary>\n    /// An implementation of the <see cref=\"ILogService\"/> based on <c>log4net</c>.\n    /// </summary>\n    public sealed class Log4NetService : ILogService\n    {\n        private readonly ILoggerRepository _repository;\n\n        private static readonly Lazy<Log4NetService> Lazy = \n            new Lazy<Log4NetService>(() => new Log4NetService(), true);\n\n        /// <summary>\n        /// Returns a single instance of the <see cref=\"Log4NetService\"/>\n        /// </summary>\n        public static Log4NetService Instance => Lazy.Value;\n\n        /// <summary>\n        /// Creates and configures an instance of the <see cref=\"Log4NetService\"/> by looking for a \n        /// default <c>log4net.config</c> file in the executing directory and monitoring it for changes.\n        /// </summary>\n        /// <exception cref=\"FileNotFoundException\">\n        /// Thrown when a valid <c>log4net.config</c> file is not found.\n        /// </exception>\n        private Log4NetService()\n        {\n            string log4NetConfigDir;\n            Assembly assembly;\n#if NET_STANDARD\n            log4NetConfigDir = AppContext.BaseDirectory;\n            assembly = GetType().GetTypeInfo().Assembly;\n#else\n            log4NetConfigDir = AppDomain.CurrentDomain.BaseDirectory;\n            assembly = GetType().Assembly;\n#endif\n            _repository = LogManager.GetRepository(assembly);\n            \n            var defaultConfigFile = new FileInfo(Path.Combine(log4NetConfigDir, \"log4net.config\"));\n            if (!defaultConfigFile.Exists) { return; }\n            \n            ConfigureImpl(_repository, defaultConfigFile);\n        }\n        \n        /// <summary>\n        /// Gets the configuration file used to configure the <see cref=\"Log4NetService\"/>.\n        /// </summary>\n        public FileInfo Configuration { get; private set; }\n\n        /// <summary>\n        /// Provides an override to configure the <see cref=\"Log4NetService\"/> with a valid <c>log4net</c>\n        /// config file represented as <paramref name=\"configFile\"/> and monitor any changes to it.\n        /// </summary> \n        /// <param name=\"configFile\">Points to a valid log4net config file.</param>\n        /// <exception cref=\"ArgumentException\">Thrown when <c>log4net</c> config file is null.</exception>\n        /// <exception cref=\"FileNotFoundException\">Thrown when a valid <c>log4net.config</c> file is not found.</exception>\n        /// <remarks>\n        /// If this method is not used, the <see cref=\"Log4NetService\"/> will be configured by looking for a \n        /// default <c>log4net.config</c> file in the executing directory.\n        /// </remarks>\n        public void Configure(FileInfo configFile)\n        {\n            if (configFile is null)\n            {\n                throw new ArgumentException(nameof(configFile) + \" cannot be null\", nameof(configFile));\n            }\n\n            if (!configFile.Exists)\n            {\n                throw new FileNotFoundException(\n                    \"Could not find a valid log4net configuration file\", \n                    configFile.FullName);\n            }\n\n            ConfigureImpl(_repository, configFile);\n        }\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <paramref name=\"loggerName\"/>.\n        /// </summary>\n        /// <param name=\"loggerName\">The name for which an <see cref=\"IEasyLogger\"/> should be returned</param>\n        /// <exception cref=\"InvalidOperationException\">Thrown if <see cref=\"Log4NetService\"/> is not configured with a valid configuration file.</exception>\n        /// <returns>The <see cref=\"IEasyLogger\"/>An instance of the logger.</returns>\n        [DebuggerStepThrough]\n        public IEasyLogger GetLogger(string loggerName)\n        {\n            EnsureConfigured();\n            return new Log4NetLogger(LogManager.GetLogger(_repository.Name, loggerName));\n        }\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <paramref name=\"loggerType\"/>.\n        /// </summary>\n        /// <param name=\"loggerType\">The <see cref=\"Type\"/> for which an <see cref=\"IEasyLogger\"/> should be returned</param>\n        /// <exception cref=\"InvalidOperationException\">Thrown if <see cref=\"Log4NetService\"/> is not configured with a valid configuration file.</exception>\n        /// <returns>The <see cref=\"IEasyLogger\"/>An instance of the logger.</returns>\n        [DebuggerStepThrough]\n        public IEasyLogger GetLogger(Type loggerType)\n        {\n            EnsureConfigured();\n            return new Log4NetLogger(LogManager.GetLogger(loggerType));\n        }\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <typeparamref name=\"T\"/>.\n        /// </summary>\n        /// <typeparam name=\"T\">The type for which an <see cref=\"IEasyLogger\"/> should be returned</typeparam>\n        /// <exception cref=\"InvalidOperationException\">Thrown if <see cref=\"Log4NetService\"/> is not configured with a valid configuration file.</exception>\n        /// <returns>The <see cref=\"IEasyLogger\"/>An instance of the logger.</returns>\n        [DebuggerStepThrough]\n        public IEasyLogger GetLogger<T>()\n        {\n            EnsureConfigured();\n            return new Log4NetLogger<T>();\n        } \n\n        /// <summary>\n        /// Disposes the <see cref=\"Log4NetService\"/>\n        /// </summary>\n        [DebuggerStepThrough]\n        public void Dispose() => LogManager.Shutdown();\n\n        private void ConfigureImpl(ILoggerRepository repository, FileInfo configFile)\n        {\n            log4net.Util.SystemInfo.NullText = string.Empty;\n            \n            XmlConfigurator.ConfigureAndWatch(repository, configFile);\n            Configuration = configFile;\n        }\n\n        private void EnsureConfigured()\n        {\n            if (Configuration != null) { return; }\n\n            throw new InvalidOperationException(\n                GetType().Name + \" needs to be configured with a valid configuration file.\");\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Runtime.CompilerServices;\n\n[assembly: InternalsVisibleTo(\"Easy.Logger.Extensions\")]\n[assembly: InternalsVisibleTo(\"Easy.Logger.Tests.Unit\")]"
  },
  {
    "path": "Easy.Logger/Scope.cs",
    "content": "﻿namespace Easy.Logger\n{\n    using System;\n    using log4net;\n    using log4net.Util;\n\n    /// <summary>\n    /// Provides scoped logging.\n    /// </summary>\n    public struct Scope : IDisposable\n    {\n        private const string ContextName = \"Easy.Logger.Scope\";\n        private static readonly LogicalThreadContextStacks Stacks = LogicalThreadContext.Stacks;\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"Scope\"/> with the given <paramref name=\"name\"/>.\n        /// </summary>\n        internal Scope(string name) => Stacks[ContextName].Push(name);\n\n        internal static string ScopeMessage => Stacks[ContextName].ToString();\n\n        /// <summary>\n        /// Removes the scope.\n        /// </summary>\n        public void Dispose() => Stacks[ContextName].Pop();\n\n        /// <summary>\n        /// Returns the current context information for this scope.\n        /// </summary>\n        /// <returns></returns>\n        public override string ToString() => ScopeMessage;\n    }\n}"
  },
  {
    "path": "Easy.Logger/Sequencer.cs",
    "content": "﻿namespace Easy.Logger\n{\n    using System;\n    using System.Collections.Concurrent;\n    using System.Threading;\n    using System.Threading.Tasks;\n\n    /// <summary>\n    /// A single worker implementation of the <c>Producer-Consumer</c> pattern.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of the object to be produced/consumed</typeparam>\n    internal sealed class Sequencer<T>\n    {\n        private readonly CancellationTokenSource _cts;\n        private readonly BlockingCollection<T> _queue;\n        private readonly Task _worker;\n\n        /// <summary>\n        /// Creates an instance of <see cref=\"Sequencer{T}\"/>\n        /// </summary>\n        /// <param name=\"consumer\">The action to be executed when consuming the item.</param>\n        public Sequencer(Action<T> consumer) : this(-1, consumer) {}\n\n        /// <summary>\n        /// Creates an instance of <see cref=\"Sequencer{T}\"/>\n        /// </summary>\n        /// <param name=\"consumer\">The action to be executed when consuming the item.</param>\n        /// <param name=\"boundedCapacity\">\n        /// The bounded size of the queue.\n        /// Any more items added will block until there is more space available.\n        /// </param>\n        public Sequencer(Action<T> consumer, int boundedCapacity) : this(boundedCapacity, consumer)\n        {\n            if (boundedCapacity <= 0)\n            {\n                throw new ArgumentException(\"Bounded capacity should be greater than zero.\");\n            }\n        }\n\n        private Sequencer(int boundedCapacity, Action<T> consumer)\n        {\n            if (consumer == null)\n            {\n                throw new ArgumentNullException(nameof(consumer));\n            }\n\n            _queue = boundedCapacity > 0 ? new BlockingCollection<T>(boundedCapacity) : new BlockingCollection<T>();\n            _cts = new CancellationTokenSource();\n            _worker = GetConsumer(consumer);\n        }\n\n        /// <summary>\n        /// Returns the bounded capacity of the underlying queue. -1 for unbounded.\n        /// </summary>\n        public int Capacity => _queue.BoundedCapacity;\n\n        /// <summary>\n        /// Returns the count of items that are pending consumption.\n        /// </summary>\n        public uint PendingCount => (uint) _queue.Count;\n\n        /// <summary>\n        /// Returns the pending items in the queue. Note, the items are valid as\n        /// the snapshot at the time of invocation.\n        /// </summary>\n        public T[] PendingItems => _queue.ToArray();\n\n        /// <summary>\n        /// Gets whether <see cref=\"Sequencer{T}\"/> has started to shutdown.\n        /// </summary>\n        public bool ShutdownRequested { get; private set; }\n\n        /// <summary>\n        /// Thrown when the <see cref=\"_worker\"/> throws an exception.\n        /// </summary>\n        public event EventHandler<SequencerExceptionEventArgs> OnException;\n\n        /// <summary>\n        /// Adds the specified item to the <see cref=\"Sequencer{T}\"/>. \n        /// This method blocks if the queue is full and until there is more room.\n        /// </summary>\n        /// <param name=\"item\">The item to be added.</param>\n        public void Enqueue(T item)\n        {\n            try\n            {\n                _queue.Add(item);\n            } catch (Exception e)\n            {\n                OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException(\"Exception occurred when adding item.\", e)));\n            }\n        }\n\n        /// <summary>\n        /// Attempts to add the specified item to the <see cref=\"Sequencer{T}\"/>.\n        /// </summary>\n        /// <param name=\"item\">The item to be added.</param>\n        /// <returns>\n        /// <c>True</c> if item could be added; otherwise <c>False</c>. \n        /// If the item is a duplicate, and the underlying collection does \n        /// not accept duplicate items, then an InvalidOperationException is thrown.\n        /// </returns>\n        public bool TryEnqueue(T item)\n        {\n            try\n            {\n                return _queue.TryAdd(item);\n            } catch (Exception e)\n            {\n                OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException(\"Exception occurred when adding item.\", e)));\n                return false;\n            }\n        }\n\n        /// <summary>\n        /// Attempts to add the specified item to the <see cref=\"Sequencer{T}\"/>.\n        /// </summary>\n        /// <param name=\"item\">The item to be added.</param>\n        /// <param name=\"timeout\">\n        /// A <c>TimeSpan</c> that represents the number of milliseconds \n        /// to wait, or a <c>TimeSpan</c> that represents -1 milliseconds to wait indefinitely.\n        /// </param>\n        /// <returns>\n        /// <c>True</c> if the item could be added to the collection within the specified time span; otherwise, <c>False</c>.\n        /// </returns>\n        public bool TryEnqueue(T item, TimeSpan timeout)\n        {\n            try\n            {\n                return _queue.TryAdd(item, timeout);\n            } catch (Exception e)\n            {\n                OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException(\"Exception occurred when adding item.\", e)));\n                return false;\n            }\n        }\n\n        /// <summary>\n        /// Marks the <see cref=\"Sequencer{T}\"/> instance as not accepting \n        /// any more items. Any outstanding items will be consumed.\n        /// </summary>\n        /// <param name=\"waitForPendingItems\">\n        /// Flag indicating whether to wait for pending items to be processed.\n        /// </param>\n        public void Shutdown(bool waitForPendingItems = true)\n        {\n            lock (_queue)\n            {\n                _queue.CompleteAdding();\n                ShutdownRequested = true;\n            }\n\n            if (waitForPendingItems)\n            {\n                try { _worker.Wait(); } catch (Exception) { /* ignored */ } \n            }\n\n            _cts.Cancel();\n            _cts.Dispose();\n            _queue.Dispose();\n        }\n\n        private Task GetConsumer(Action<T> consumer)\n        {\n            var cToken = _cts.Token;\n\n            return Task.Factory.StartNew(() =>\n            {\n                foreach (var item in _queue.GetConsumingEnumerable(cToken))\n                {\n                    cToken.ThrowIfCancellationRequested();\n\n                    try\n                    {\n                        consumer(item);\n                    }\n                    catch (Exception e)\n                    {\n                        OnException?.Invoke(this, new SequencerExceptionEventArgs(new SequencerException(\"Exception occurred.\", e)));\n                    }\n                }\n            }, cToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);\n        }\n    }\n\n    /// <summary>\n    /// This class used as a container for when an <see cref=\"System.Exception\"/> \n    /// is raised by the <see cref=\"Sequencer{T}\"/>\n    /// </summary>\n    internal sealed class SequencerExceptionEventArgs : EventArgs\n    {\n        /// <summary>\n        /// Creates an instance of the <see cref=\"Sequencer{T}\"/>\n        /// </summary>\n        /// <param name=\"e\">The <see cref=\"System.Exception\"/></param>\n        public SequencerExceptionEventArgs(SequencerException e) => Exception = e;\n\n        /// <summary>\n        /// The <see cref=\"System.Exception\"/> raised by the <see cref=\"Sequencer{T}\"/>.\n        /// </summary>\n        public SequencerException Exception { get; }\n    }\n\n    /// <summary>\n    /// The <see cref=\"System.Exception\"/> thrown by the <see cref=\"Sequencer{T}\"/>.\n    /// </summary>\n    internal sealed class SequencerException : Exception\n    {\n        /// <summary>\n        /// Creates an instance of the <see cref=\"SequencerException\"/>.\n        /// </summary>\n        public SequencerException() { }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"SequencerException\"/>.\n        /// </summary>\n        /// <param name=\"message\">The message for the <see cref=\"Exception\"/></param>\n        public SequencerException(string message) : base(message) { }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"SequencerException\"/>.\n        /// </summary>\n        /// <param name=\"message\">The message for the <see cref=\"Exception\"/></param>\n        /// <param name=\"innerException\">The inner exception</param>\n        public SequencerException(string message, Exception innerException) : base(message, innerException) { }\n    }\n}\n"
  },
  {
    "path": "Easy.Logger/sample-log4net.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n  <root>\n    <!--\n    1.OFF - nothing gets logged\n    2.FATAL \n    3.ERROR\n    4.WARN\n    5.INFO\n    6.DEBUG\n    7.ALL - everything gets logged\n    \n    Max no. of appenders = 4\n    -->\n    <level value=\"ALL\"/>\n    <appender-ref ref=\"AsyncBufferingForwarder\"/>\n    <!--     <appender-ref ref=\"BufferingForwarder\" />-->\n  </root>\n\n  <appender name=\"AsyncBufferingForwarder\" type=\"Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger\">\n    <lossy value=\"false\" />\n    <bufferSize value=\"512\" />\n    \n    <!-- Value in milliseconds -->\n    <idleTime value=\"500\" />\n    \n    <!-- Must fix Message, ThreadName and Exception -->\n    <fix value=\"268\" />\n  <!-- Alternate syntax:\n    <fix value=\"Message, ThreadName, Exception\" /> \n  -->\n\n    <!--Any other appender or forwarder...-->\n    <appender-ref ref=\"RollingFileAppender\"/>\n    <appender-ref ref=\"HTTPAppender\" />\n  </appender>\n\n  <!--  http://stackoverflow.com/a/11351400/1226568 -->\n  <appender name=\"BufferingForwarder\" type=\"log4net.Appender.BufferingForwardingAppender\">\n    <bufferSize value=\"512\" />\n    <lossy value=\"false\" />\n\n    <fix value=\"268\" />\n  <!-- Alternate syntax:\n    <fix value=\"Message, ThreadName, Exception\" /> \n  -->\n\n    <evaluator type=\"log4net.Core.LevelEvaluator\">\n      <threshold value=\"WARN\"/>\n    </evaluator>\n\n    <appender-ref ref=\"RollingFileAppender-1\" />\n  </appender>\n\n  <!-- Preferred -->\n  <appender name=\"RollingFileAppender-1\" type=\"log4net.Appender.RollingFileAppender\">\n    <file value=\"Logs\\\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"false\"/>\n    <datePattern value=\"'App-'yyyy-MM-dd'.log'\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <!-- Also works -->\n  <appender name=\"RollingFileAppender-2\" type=\"log4net.Appender.RollingFileAppender\">\n    <file value=\"Logs\\App.log\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"false\"/>\n    <datePattern value=\"yyyy-MM-dd\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <appender name=\"RollingPerExecutionFileAppender\" type=\"log4net.Appender.RollingFileAppender\">\n    <file type=\"log4net.Util.PatternString\" value=\"App-%date{yyyy-MM-dd}.log\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"true\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <appender name=\"HTTPAppender\" type=\"Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions\">\n    <name value=\"Integration.Tests\" />\n    <endpoint value=\"http://localhost:1234\" />\n    <includeHost value=\"true\" />\n  </appender>\n\n  <appender name=\"ColoredConsoleAppender\" type=\"log4net.Appender.ColoredConsoleAppender\">\n    <mapping>\n      <level value=\"FATAL\"/>\n      <foreColor value=\"Red\"/>\n      <backColor value=\"White\"/>\n    </mapping>\n    <mapping>\n      <level value=\"ERROR\"/>\n      <foreColor value=\"Red, HighIntensity\" />\n    </mapping>\n    <mapping>\n      <level value=\"WARN\"/>\n      <foreColor value=\"Yellow\"/>\n    </mapping>\n    <mapping>\n      <level value=\"INFO\"/>\n      <foreColor value=\"Cyan\"/>\n    </mapping>\n    <mapping>\n      <level value=\"DEBUG\"/>\n      <foreColor value=\"Green\"/>\n    </mapping>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n</log4net>"
  },
  {
    "path": "Easy.Logger.Benchmarker/App.config",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <runtime>\n    <gcServer enabled=\"true\"/>\n  </runtime>\n\n  <startup>\n    <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/>\n  </startup>\n</configuration>\n"
  },
  {
    "path": "Easy.Logger.Benchmarker/Easy.Logger.Benchmarker.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{2B08D00F-F8E8-418E-9533-A50E92E9448F}</ProjectGuid>\n    <OutputType>Exe</OutputType>\n    <RootNamespace>Easy.Logger.Benchmarker</RootNamespace>\n    <AssemblyName>Easy.Logger.Benchmarker</AssemblyName>\n    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Program.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n    <None Include=\"the-log4net.config\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n      <SubType>Designer</SubType>\n    </None>\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Easy.Logger.Extensions\\Easy.Logger.Extensions.csproj\">\n      <Project>{84135fc0-fe53-47ac-ab47-fc5b0cdb2ecd}</Project>\n      <Name>Easy.Logger.Extensions</Name>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\Easy.Logger.Interfaces\\Easy.Logger.Interfaces.csproj\">\n      <Project>{80248AFD-52ED-47A0-B077-97AE8DC067CF}</Project>\n      <Name>Easy.Logger.Interfaces</Name>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\Easy.Logger\\Easy.Logger.csproj\">\n      <Project>{f1cfacd6-414e-4921-90fd-0949764e1756}</Project>\n      <Name>Easy.Logger</Name>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n</Project>"
  },
  {
    "path": "Easy.Logger.Benchmarker/Program.cs",
    "content": "﻿namespace Easy.Logger.Benchmarker\n{\n    using System;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using Easy.Logger.Interfaces;\n\n    internal class Program\n    {\n        private readonly TimeSpan _duration;\n        private readonly IEasyLogger _logger;\n\n        private static void Main(string[] args)\n        {\n            var duration = TimeSpan.FromSeconds(10);\n\n            if (args.Length > 0)\n            {\n                var seconds = int.Parse(args[0]);\n                duration = TimeSpan.FromSeconds(seconds);\n            }\n\n            new Program(duration).Run();\n\n            Console.WriteLine(\"[{0:HH:mm:ss.fff}] - [Program] - Disposing\", DateTimeOffset.UtcNow);\n            Log4NetService.Instance.Dispose();\n            Console.WriteLine(\"[{0:HH:mm:ss.fff}] - [Program] - Disposed\", DateTimeOffset.UtcNow);\n\n            Console.WriteLine(\"Gen 0: {0}\", GC.CollectionCount(0).ToString());\n            Console.WriteLine(\"Gen 1: {0}\", GC.CollectionCount(1).ToString());\n            Console.WriteLine(\"Gen 2: {0}\", GC.CollectionCount(2).ToString());\n        }\n\n        public Program(TimeSpan duration)\n        {\n            var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"the-log4net.config\"));\n            Log4NetService.Instance.Configure(configFile);\n            \n            _duration = duration;\n            _logger = Log4NetService.Instance.GetLogger<Program>();\n        }\n\n        private void Run()\n        {\n            _logger.Info(\"Benchmarking starting\");\n            TestThroughput();\n            //            TestMultiThreading();\n            //            TestIdle();\n            _logger.Warn(\"Benchmarking ended\");\n        }\n\n        private void TestThroughput()\n        {\n            Console.WriteLine(\"------------Testing Throughput------------\");\n\n            var sw = Stopwatch.StartNew();\n            long counter = 0;\n            while (sw.Elapsed < _duration)\n            {\n                counter++;\n                _logger.DebugFormat(\"Counter is: {0}\", counter.ToString());\n            }\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", counter, sw.Elapsed.ToString());\n        }\n\n        private void TestMultiThreading()\n        {\n            Console.WriteLine(\"------------Testing MultiThreading------------\");\n\n            const int WorkerCount = 6;\n\n            long totalCounter = 0;\n            Func<int> action = () =>\n            {\n                var localCounter = 0;\n                var sw = Stopwatch.StartNew();\n\n                while (sw.Elapsed < _duration)\n                {\n                    _logger.DebugFormat(\"Counter is: {0}\", (++localCounter).ToString());\n                }\n\n                return localCounter;\n            };\n\n            var totalSw = Stopwatch.StartNew();\n\n            Parallel.For(\n                0,\n                WorkerCount,\n                new ParallelOptions { MaxDegreeOfParallelism = WorkerCount },\n                () => 0,\n                (i, state, partial) => action(),\n                partialCounter => Interlocked.Add(ref totalCounter, partialCounter));\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", totalCounter, totalSw.Elapsed.ToString());\n        }\n\n        private void TestIdle()\n        {\n            Console.WriteLine(\"------------Testing Idle------------\");\n\n            var sw = Stopwatch.StartNew();\n            long counter = 0;\n            while (sw.Elapsed < _duration)\n            {\n                counter++;\n                _logger.DebugFormat(\"Counter is: {0}\", counter.ToString());\n                Thread.Sleep(TimeSpan.FromSeconds(1));\n            }\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", counter, sw.Elapsed.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "Easy.Logger.Benchmarker/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"Easy.Logger.Benchmarker\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Easy.Logger.Benchmarker\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2017\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"2b08d00f-f8e8-418e-9533-a50e92e9448f\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "Easy.Logger.Benchmarker/the-log4net.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n  <root>\n    <level value=\"ALL\"/>\n    <appender-ref ref=\"AsyncBufferingForwarder\"/>\n  </root>\n\n  <appender name=\"AsyncBufferingForwarder\" type=\"Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger\">\n    <lossy value=\"false\" />\n    <bufferSize value=\"512\" />\n    <idleTime value=\"200\" />\n    <fix value=\"Message, ThreadName, Exception\" />\n\n    <appender-ref ref=\"RollingFile\"/>\n    <!-- <appender-ref ref=\"HTTPAppender\"/> -->\n  </appender>\n\n  <appender name=\"RollingFile\" type=\"log4net.Appender.RollingFileAppender\">\n    <file value=\"Logs\\\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"false\"/>\n    <datePattern value=\"'Benchmarker-'yyyy-MM-dd'.log'\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <appender name=\"HTTPAppender\" type=\"Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions\">\n    <name value=\"Integration.Tests\" />\n    <endpoint value=\"http://localhost:5001\" />\n    <includeHost value=\"true\" />\n  </appender>\n</log4net>"
  },
  {
    "path": "Easy.Logger.Benchmarker.Core/Easy.Logger.Benchmarker.Core.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>net6.0</TargetFramework>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <ServerGarbageCollection>true</ServerGarbageCollection>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Easy.Logger\\Easy.Logger.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Update=\"App.config\">\n      <CopyToOutputDirectory>Never</CopyToOutputDirectory>\n    </None>\n    <None Update=\"log4net.config\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n\n</Project>"
  },
  {
    "path": "Easy.Logger.Benchmarker.Core/Program.cs",
    "content": "﻿namespace Easy.Logger.Benchmarker.Core\n{\n    using System;\n    using System.Diagnostics;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using Easy.Logger.Interfaces;\n\n    internal class Program\n    {\n        private readonly TimeSpan _duration;\n        private readonly IEasyLogger _logger;\n\n        private static void Main(string[] args)\n        {\n            var duration = TimeSpan.FromSeconds(10);\n\n            if (args.Length > 0)\n            {\n                var seconds = int.Parse(args[0]);\n                duration = TimeSpan.FromSeconds(seconds);\n            }\n\n            new Program(duration).Run();\n\n            Log4NetService.Instance.Dispose();\n        }\n\n        public Program(TimeSpan duration)\n        {\n            _duration = duration;\n            _logger = Log4NetService.Instance.GetLogger<Program>();\n        }\n\n        private void Run()\n        {\n            _logger.Info(\"Benchmarking starting\");\n            TestThroughput();\n            //            TestMultiThreading();\n            //            TestIdle();\n            _logger.Warn(\"Benchmarking ended\");\n\n            Console.WriteLine(\"Gen 0: {0}\", GC.CollectionCount(0).ToString());\n            Console.WriteLine(\"Gen 1: {0}\", GC.CollectionCount(1).ToString());\n            Console.WriteLine(\"Gen 2: {0}\", GC.CollectionCount(2).ToString());\n        }\n\n        private void TestThroughput()\n        {\n            Console.WriteLine(\"------------Testing Throughput------------\");\n\n            var sw = Stopwatch.StartNew();\n            long counter = 0;\n            while (sw.Elapsed < _duration)\n            {\n                counter++;\n                _logger.DebugFormat(\"Counter is: {0}\", counter.ToString());\n            }\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", counter, sw.Elapsed.ToString());\n        }\n\n        private void TestMultiThreading()\n        {\n            Console.WriteLine(\"------------Testing MultiThreading------------\");\n\n            const int WorkerCount = 6;\n\n            long totalCounter = 0;\n            Func<int> action = () =>\n            {\n                var localCounter = 0;\n                var sw = Stopwatch.StartNew();\n\n                while (sw.Elapsed < _duration)\n                {\n                    _logger.DebugFormat(\"Counter is: {0}\", (++localCounter).ToString());\n                }\n\n                return localCounter;\n            };\n\n            var totalSw = Stopwatch.StartNew();\n\n            Parallel.For(\n                0,\n                WorkerCount,\n                new ParallelOptions { MaxDegreeOfParallelism = WorkerCount },\n                () => 0,\n                (i, state, partial) => action(),\n                partialCounter => Interlocked.Add(ref totalCounter, partialCounter));\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", totalCounter, totalSw.Elapsed.ToString());\n        }\n\n        private void TestIdle()\n        {\n            Console.WriteLine(\"------------Testing Idle------------\");\n\n            var sw = Stopwatch.StartNew();\n            long counter = 0;\n            while (sw.Elapsed < _duration)\n            {\n                counter++;\n                _logger.DebugFormat(\"Counter is: {0}\", counter.ToString());\n                Thread.Sleep(TimeSpan.FromSeconds(1));\n            }\n\n            Console.WriteLine(\"Counter reached: {0:n0}, Time Taken: {1}\", counter, sw.Elapsed.ToString());\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Benchmarker.Core/log4net.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n  <root>\n    <level value=\"ALL\"/>\n    <appender-ref ref=\"AsyncBufferingForwarder\"/>\n  </root>\n\n  <appender name=\"AsyncBufferingForwarder\" type=\"Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger\">\n    <lossy value=\"false\" />\n    <bufferSize value=\"512\" />\n    <idleTime value=\"200\" />\n    <fix value=\"Message, ThreadName, Exception\" />\n\n    <appender-ref ref=\"RollingFile\"/>\n  </appender>\n\n  <appender name=\"RollingFile\" type=\"log4net.Appender.RollingFileAppender\">\n    <file type=\"log4net.Util.PatternString\" value=\"Benchmarker.Core-%date{yyyy-MM-dd}.log\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"true\"/>\n    <datePattern value=\"yyyy-MM-dd\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n</log4net>"
  },
  {
    "path": "Easy.Logger.Extensions/Easy.Logger.Extensions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <PackageId>Easy.Logger.Extensions</PackageId>\n    <Description>Additional features and functionalities to extend Easy.Logger</Description>\n    <Authors>Nima Ara</Authors>\n    <Copyright>2024 Nima Ara</Copyright>\n    <PackageTags>log4net;Logging;Easy;Logger;Log;Extensions</PackageTags>\n    <PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>\n    <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    <PackageReleaseNotes>-</PackageReleaseNotes>\n    <RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>\n    <RepositoryType>git</RepositoryType>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <TargetFrameworks>net48;netstandard1.3;netstandard2.0</TargetFrameworks>\n    <AssemblyTitle>Easy Logger Extensions</AssemblyTitle>\n    <AssemblyName>Easy.Logger.Extensions</AssemblyName>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n  </PropertyGroup>\n\n  <PropertyGroup Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">\n    <DefineConstants>NETSTANDARD1_3</DefineConstants>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"log4net\" Version=\"2.0.15\" />    \n  </ItemGroup>\n  \n  <ItemGroup Condition=\" '$(TargetFramework)' != 'netstandard1.3' \">\n    <PackageReference Include=\"Utf8Json\" Version=\"1.3.7\" />\n  </ItemGroup>\n  \n  <ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n  </ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "Easy.Logger.Extensions/HTTPAppender.cs",
    "content": "﻿namespace Easy.Logger.Extensions\n{\n    using System;\n    using System.Diagnostics;\n    using System.Diagnostics.CodeAnalysis;\n    using System.IO;\n    using System.Net;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using log4net.Appender;\n    using log4net.Core;\n#if NETSTANDARD1_3\n    using System.Text;\n    using Newtonsoft.Json;\n    using JsonSerializer = Newtonsoft.Json.JsonSerializer;\n    using Newtonsoft.Json.Serialization;    \n#else\n    using Utf8Json;\n    using Utf8Json.Formatters;\n    using Utf8Json.Resolvers;\n#endif\n    \n    /// <summary>\n    /// An appender for <c>POSTing</c> log events to a given endpoint.\n    /// </summary>\n    public sealed class HTTPAppender : AppenderSkeleton\n    {\n        private static readonly ThreadLocal<LoggingEvent[]> _singleLogEventPool;\n\n#if NETSTANDARD1_3\n        private static readonly JsonSerializer Serializer;\n#endif\n        private readonly int _pid;\n        private readonly string _processName;\n        \n        private int _counter;\n        private string _host, _sender;\n\n        static HTTPAppender()\n        {\n            _singleLogEventPool = new ThreadLocal<LoggingEvent[]>(() => new LoggingEvent[1]);\n\n#if NETSTANDARD1_3\n            Serializer = new JsonSerializer\n            {\n                ContractResolver = new CamelCasePropertyNamesContractResolver(),\n                DateFormatHandling = DateFormatHandling.IsoDateFormat,\n                DateTimeZoneHandling = DateTimeZoneHandling.Utc,\n                Formatting = Formatting.None\n            };\n#endif\n        }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"HTTPAppender\"/>.\n        /// </summary>\n        public HTTPAppender()\n        {\n            using (var p = Process.GetCurrentProcess())\n            {\n                _pid = p.Id;\n                _processName = p.ProcessName;\n            }\n        }\n\n        /// <summary>\n        /// Gets the endpoint to which log events are <c>POSTed</c> to.\n        /// </summary>\n        public string Endpoint { get; set; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the host name should be included in the payload.\n        /// </summary>\n        public bool IncludeHost { get; set; }\n\n        /// <summary>\n        /// Activates the appender options.\n        /// </summary>\n        public override void ActivateOptions()\n        {\n            base.ActivateOptions();\n\n            if (string.IsNullOrWhiteSpace(Name))\n            {\n                Name = Guid.NewGuid().ToString();\n            }\n\n            if (string.IsNullOrWhiteSpace(Endpoint)\n                || !Endpoint.StartsWith(\"HTTP\", StringComparison.OrdinalIgnoreCase))\n            {\n                throw new ArgumentException(\"Invalid endpoint.\");\n            }\n\n            _host = IncludeHost ? Dns.GetHostName() : null;\n            _sender = Name;\n        }\n\n        /// <summary>\n        /// Serializes and <c>POST</c>s the given <paramref name=\"logEvent\"/> to <see cref=\"Endpoint\"/>.\n        /// </summary>\n        protected override void Append(LoggingEvent logEvent)\n        {\n            var pool = _singleLogEventPool.Value;\n            pool[0] = logEvent;\n            Append(pool);\n        }\n\n        /// <summary>\n        /// Serializes and <c>POST</c>s the given <paramref name=\"logEvents\"/> to <see cref=\"Endpoint\"/>.\n        /// </summary>\n        protected override void Append(LoggingEvent[] logEvents) => Post(logEvents);\n\n        private async void Post(LoggingEvent[] logEvents)\n        {\n            var payload = new Payload\n            {\n                PID = _pid,\n                ProcessName = _processName,\n                Host = _host,\n                Sender = _sender,\n                TimestampUTC = DateTimeOffset.UtcNow,\n                BatchNo = Interlocked.Increment(ref _counter),\n                Entries = GetEntries(logEvents)\n            };\n\n            if (!await DoPost(payload).ConfigureAwait(false))\n            {\n                // Try once more\n                await DoPost(payload).ConfigureAwait(false);\n            }\n        }\n\n        private async Task<bool> DoPost(Payload payload)\n        {\n            var req = (HttpWebRequest)WebRequest.Create(Endpoint);\n\n            /* ToDo - Consider setting the proxy\n             * string proxyUrl = \"proxy.myproxy.com\";\n             * int proxyPort = 8080;\n             * WebProxy myProxy = new WebProxy(proxyUrl, proxyPort);\n             *\n             * HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(Endpoint);\n             * req.Proxy = myProxy;\n             */\n            req.Proxy = null;\n            req.Method = \"POST\";\n            req.ContentType = \"application/json\";\n\n            try\n            {\n                using (var stream = await req.GetRequestStreamAsync().ConfigureAwait(false))\n                {\n#if NETSTANDARD1_3\n                    using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))\n                    using (JsonTextWriter jsonWriter = new JsonTextWriter(writer))\n                    {\n                        Serializer.Serialize(jsonWriter, payload);\n                    }\n#else\n                    JsonSerializer.Serialize(stream, payload, StandardResolver.AllowPrivateCamelCase);\n#endif\n                    using (var resp = (HttpWebResponse) await req.GetResponseAsync().ConfigureAwait(false))\n                    {\n                        return IsSuccessStatusCode(resp);\n                    }\n                }\n            } catch (WebException)\n            {\n                return false;\n            }\n        }\n\n        private static Entry[] GetEntries(LoggingEvent[] logEvents)\n        {\n            var result = new Entry[logEvents.Length];\n            for (var i = 0; i < logEvents.Length; i++)\n            {\n                var curr = logEvents[i];\n\n                result[i] = new Entry\n                {\n                    DateTimeOffset = new DateTimeOffset(curr.TimeStamp),\n                    LoggerName = curr.LoggerName,\n                    Level = curr.Level?.DisplayName,\n                    ThreadID = curr.ThreadName,\n                    Message = curr.RenderedMessage,\n                    Exception = curr.ExceptionObject\n                };\n            }\n            return result;\n        }\n\n        private static bool IsSuccessStatusCode(HttpWebResponse response)\n        {\n            if (response.StatusCode >= HttpStatusCode.OK)\n            {\n                return response.StatusCode <= (HttpStatusCode)299;\n            }\n            return false;\n        }\n\n        [SuppressMessage(\"ReSharper\", \"UnusedAutoPropertyAccessor.Local\")]\n        private sealed class Payload\n        {\n            // ReSharper disable once InconsistentNaming\n            public int PID { get; set; }\n            public string ProcessName { get; set; }\n            public string Host { get; set; }\n            public string Sender { get; set; }\n            public DateTimeOffset TimestampUTC { get; set; }\n            public int BatchNo { get; set; }\n            public Entry[] Entries { get; set; }\n        }\n\n        [SuppressMessage(\"ReSharper\", \"UnusedAutoPropertyAccessor.Local\")]\n        private struct Entry\n        {\n            public DateTimeOffset DateTimeOffset { get; set; }\n            public string LoggerName { get; set; }\n            public string Level { get; set; }\n            // ReSharper disable once InconsistentNaming\n            public string ThreadID { get; set; }\n            public string Message { get; set; }\n            public Exception Exception { get; set; }\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Runtime.CompilerServices;\n\n[assembly: InternalsVisibleTo(\"Easy.Logger.Tests.Unit\")]"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/Easy.Logger.Extensions.Microsoft.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <PackageId>Easy.Logger.Extensions.Microsoft</PackageId>\n    <Description>Easy.Logger implementation of the Microsoft.Extensions.Logging package which can also be used in ASP.NET Core.</Description>\n    <Authors>Nima Ara</Authors>\n    <Copyright>2024 Nima Ara</Copyright>\n    <PackageTags>log4net;Logging;Easy;Logger;Log;Microsoft.Extensions.Logging</PackageTags>\n    <PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>\n    <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    <PackageReleaseNotes>-</PackageReleaseNotes>\n    <RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>\n    <RepositoryType>git</RepositoryType>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <TargetFramework>netstandard2.0</TargetFramework>\n    <AssemblyTitle>Easy Logger ASP.NET Core</AssemblyTitle>\n    <AssemblyName>Easy.Logger.Extensions.Microsoft</AssemblyName>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Easy.Logger\" Version=\"4.0.0\" />\n    <PackageReference Include=\"log4net\" Version=\"2.0.15\" />\n    <PackageReference Include=\"Microsoft.Extensions.Logging\" Version=\"8.0.0\" />\n  </ItemGroup>\n\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/EasyLogger.cs",
    "content": "﻿namespace Easy.Logger.Extensions.Microsoft\n{\n    using System;\n    using Easy.Logger.Interfaces;\n    using global::Microsoft.Extensions.Logging;\n\n    /// <summary>\n    /// An implementation of the <see cref=\"ILogger\"/> based on\n    /// <see href=\"https://github.com/NimaAra/Easy.Logger\"/>.\n    /// </summary>\n    public sealed class EasyLogger : ILogger\n    {\n        private readonly IEasyLogger _logger;\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"EasyLogger\"/>.\n        /// </summary>\n        // ReSharper disable once UnusedMember.Global\n        public EasyLogger() { }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"EasyLogger\"/>.\n        /// </summary>\n        /// <param name=\"easyLogger\">An instance of the <see cref=\"IEasyLogger\"/>.</param>\n        public EasyLogger(IEasyLogger easyLogger) => _logger = easyLogger;\n        \n        /// <summary>\n        /// Gets the flag indicating whether the given <paramref name=\"logLevel\"/> is enabled.\n        /// </summary>\n        public bool IsEnabled(LogLevel logLevel)\n        {\n            switch (logLevel)\n            {\n                case LogLevel.Trace:\n                    return _logger.IsTraceEnabled;\n                case LogLevel.Debug:\n                    return _logger.IsDebugEnabled;\n                case LogLevel.Information:\n                    return _logger.IsInfoEnabled;\n                case LogLevel.Warning:\n                    return _logger.IsWarnEnabled;\n                case LogLevel.Error:\n                    return _logger.IsErrorEnabled;\n                case LogLevel.Critical:\n                    return _logger.IsFatalEnabled;\n                default:\n                    throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);\n            }\n        }\n\n        /// <summary>\n        /// Returns an <see cref=\"T:System.IDisposable\" /> which allows the caller to specify a scope as\n        /// <paramref name=\"state\" /> which will then be rendered as part of the message.\n        /// </summary>\n        /// <param name=\"state\">The scope identifier.</param>\n        public IDisposable BeginScope<TState>(TState state) => _logger.GetScopedLogger(state.ToString());\n\n        /// <summary>\n        /// Does the logging.\n        /// </summary>\n        public void Log<TState>(\n            LogLevel logLevel, \n            EventId eventId, \n            TState state, \n            Exception exception, \n            Func<TState, Exception, string> formatter)\n        {\n            if (!IsEnabled(logLevel)) { return; }\n\n            if (formatter is null) { throw new ArgumentNullException(nameof(formatter)); }\n\n            var message = formatter(state, exception);\n\n            if (message is null) { return; }\n\n            switch (logLevel)\n            {\n                case LogLevel.Trace:\n                    _logger.Trace(message, exception);\n                    break;\n                case LogLevel.Debug:\n                    _logger.Debug(message, exception);\n                    break;\n                case LogLevel.Information:\n                    _logger.Info(message, exception);\n                    break;\n                case LogLevel.Warning:\n                    _logger.Warn(message, exception);\n                    break;\n                case LogLevel.Error:\n                    _logger.Error(message, exception);\n                    break;\n                case LogLevel.Critical:\n                    _logger.Fatal(message, exception);\n                    break;\n                default:\n                    _logger.WarnFormat(\"Invalid {0}: {1} | {2}\", nameof(logLevel), logLevel, message);\n                    break;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/EasyLoggerConfig.cs",
    "content": "﻿namespace Easy.Logger.Extensions.Microsoft\n{\n    using System.IO;\n\n    /// <summary>\n    /// An abstraction for configuring <see cref=\"EasyLoggerProvider\"/>.\n    /// </summary>\n    public sealed class EasyLoggerConfig : IEasyLoggerConfig\n    {\n        /// <summary>\n        /// Gets the configuration file.\n        /// </summary>\n        public FileInfo ConfigFile { get; }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"EasyLoggerConfig\"/>.\n        /// </summary>\n        /// <param name=\"file\">The configuration file.</param>\n        public EasyLoggerConfig(FileInfo file) => ConfigFile = file;\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/EasyLoggerProvider.cs",
    "content": "﻿namespace Easy.Logger.Extensions.Microsoft\n{\n    using System.Collections.Concurrent;\n    using System.Diagnostics.CodeAnalysis;\n    using global::Microsoft.Extensions.Logging;\n\n    /// <summary>\n    /// An implementation of the <see cref=\"ILoggerProvider\"/>.\n    /// </summary>\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n    public sealed class EasyLoggerProvider : ILoggerProvider\n    {\n        private readonly ConcurrentDictionary<string, EasyLogger> _loggerCache = \n            new ConcurrentDictionary<string, EasyLogger>();\n        \n        /// <summary>\n        /// Creates an instance of the <see cref=\"EasyLoggerProvider\"/>.\n        /// <remarks>A valid <c>log4net</c> configuration file must be present in the working directory.</remarks>\n        /// </summary>\n        public EasyLoggerProvider() { }\n\n        /// <summary>\n        /// Creates an instance of the <see cref=\"EasyLoggerProvider\"/>.\n        /// </summary>\n        /// <param name=\"config\">A valid <c>log4net</c> configuration file.</param>\n        public EasyLoggerProvider(IEasyLoggerConfig config) => \n            Log4NetService.Instance.Configure(config.ConfigFile);\n\n        /// <summary>\n        /// Creates an <see cref=\"ILogger\"/> with the given <paramref name=\"categoryName\"/>.\n        /// </summary>\n        public ILogger CreateLogger(string categoryName) =>\n            _loggerCache.GetOrAdd(categoryName, x => new EasyLogger(Log4NetService.Instance.GetLogger(x)));\n\n        /// <summary>\n        /// Disposes the instance flushing any pending log entries.\n        /// </summary>\n        public void Dispose() => Log4NetService.Instance.Dispose();\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/IEasyLoggerConfig.cs",
    "content": "﻿namespace Easy.Logger.Extensions.Microsoft\n{\n    using System.IO;\n\n    /// <summary>\n    /// Specifies the contract for an implementation of <see cref=\"IEasyLoggerConfig\"/>.\n    /// </summary>\n    public interface IEasyLoggerConfig\n    {\n        /// <summary>\n        /// Gets the configuration file.\n        /// </summary>\n        FileInfo ConfigFile { get; }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/LoggingBuilderExtensions.cs",
    "content": "﻿namespace Easy.Logger.Extensions.Microsoft\n{\n    using System.IO;\n    using global::Microsoft.Extensions.DependencyInjection;\n    using global::Microsoft.Extensions.Logging;\n\n    /// <summary>\n    /// A set of methods to extend the behavior of <see cref=\"ILoggingBuilder\"/>.\n    /// </summary>\n    public static class LoggingBuilderExtensions\n    {\n        /// <summary>\n        /// Adds <see cref=\"EasyLoggerProvider\"/> to <paramref name=\"builder\"/>.\n        /// </summary>\n        public static void AddEasyLogger(this ILoggingBuilder builder) =>\n            builder.Services.AddSingleton<ILoggerProvider, EasyLoggerProvider>();\n\n        /// <summary>\n        /// Adds <see cref=\"EasyLoggerProvider\"/> to <paramref name=\"builder\"/>.\n        /// </summary>\n        /// <param name=\"builder\">An instance of the <see cref=\"ILoggingBuilder\"/>.</param>\n        /// <param name=\"log4netConfig\">A valid configuration file.</param>\n        public static void AddEasyLogger(this ILoggingBuilder builder, FileInfo log4netConfig)\n        {\n            builder.Services.AddSingleton<IEasyLoggerConfig>(new EasyLoggerConfig(log4netConfig));\n            builder.AddEasyLogger();\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Extensions.Microsoft/README.md",
    "content": "﻿### Setting up in ASP.NET Core:\n\nThe current implementation is based on [log4net](https://logging.apache.org/log4net/).\n\n```csharp\ninternal class Program\n{\n    private static void Main()\n    {\n        using (var host = new WebHostBuilder()\n            .UseContentRoot(Directory.GetCurrentDirectory())\n            .UseKestrel()\n            .UseUrls(\"http://*:8081\")                \n            .ConfigureLogging(Startup.ConfigureLogging)\n            .Configure(Startup.Configure)\n            .Build())\n        {\n            host.Run();\n        }\n    }\n}\n\ninternal static class Startup\n{\n    public static void ConfigureLogging(WebHostBuilderContext ctx, ILoggingBuilder builder)\n    {\n        builder.AddConfiguration(ctx.Configuration.GetSection(\"Logging\"));\n        if (ctx.HostingEnvironment.IsDevelopment()) \n        { \n            builder.AddConsole(); \n        }\n\n        // Use the default log\n        builder.AddEasyLogger();\n\n        /*\n         var configFile = new FileInfo(\"sample-log4net.config\");\n         builder.AddEasyLogger(configFile);\n        */\n    }\n\n    public static void Configure(IApplicationBuilder app) { ... }\n}\n```"
  },
  {
    "path": "Easy.Logger.Interfaces/Easy.Logger.Interfaces.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  \n  <PropertyGroup>\n    <PackageId>Easy.Logger.Interfaces</PackageId>\n    <Description>Contains a set of interfaces to be implemented by any logger.</Description>\n    <Authors>Nima Ara</Authors>\n    <Copyright>2024 Nima Ara</Copyright>\n    <PackageTags>log4net;Logging;Easy;Logger;Log;Interfaces</PackageTags>\n    <PackageProjectUrl>https://github.com/NimaAra/EasyLogger</PackageProjectUrl>\n    <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    <PackageReleaseNotes>-</PackageReleaseNotes>\n    <RepositoryUrl>https://github.com/NimaAra/EasyLogger</RepositoryUrl>\n    <RepositoryType>git</RepositoryType>\n    <PackageTargetFallback Condition=\" '$(TargetFramework)' == 'netstandard1.3' \">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <TargetFrameworks>netstandard1.3;net48</TargetFrameworks>\n    <AssemblyTitle>Easy Logger Interfaces</AssemblyTitle>\n    <AssemblyName>Easy.Logger.Interfaces</AssemblyName>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n  </PropertyGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net40' \">\n    <Reference Include=\"System\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n\t\n  <PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n  \n</Project>"
  },
  {
    "path": "Easy.Logger.Interfaces/IEasyLogger.cs",
    "content": "﻿namespace Easy.Logger.Interfaces\n{\n    using System;\n\n    /// <summary>\n    /// The <see cref=\"IEasyLogger\"/> interface specifying \n    /// available methods for different levels of logging.\n    /// </summary>\n    public interface IEasyLogger\n    {\n        /// <summary>\n        /// Gets the logger name.\n        /// </summary>\n        string Name { get; }\n\n        /// <summary>\n        /// Returns an <see cref=\"IDisposable\"/> which allows the caller to specify a scope as\n        /// <paramref name=\"name\"/> which will then be rendered as part of the message.\n        /// </summary>\n        /// <param name=\"name\">The name of the scope</param>\n        IDisposable GetScopedLogger(string name);\n\n    #region Levels Enabled\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Trace</c> messages.\n        /// </summary>\n        bool IsTraceEnabled { get; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <see cref=\"System.Diagnostics.Debug\"/> messages.\n        /// </summary>\n        bool IsDebugEnabled { get; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Info</c> messages.\n        /// </summary>\n        bool IsInfoEnabled { get; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Warn</c> messages.\n        /// </summary>\n        bool IsWarnEnabled { get; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Error</c> messages.\n        /// </summary>\n        bool IsErrorEnabled { get; }\n\n        /// <summary>\n        /// Gets the flag indicating whether the logger is enabled for \n        /// <c>Fatal</c> messages.\n        /// </summary>\n        bool IsFatalEnabled { get; }\n    #endregion\n\n    #region Trace\n        /// <summary>\n        /// Logs a <c>Trace</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Trace(object message);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Trace(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void TraceFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void TraceFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void TraceFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void TraceFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Trace</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void TraceFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n\n    #region Debug\n        /// <summary>\n        /// Logs a <c>Debug</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Debug(object message);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Debug(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void DebugFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void DebugFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void DebugFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void DebugFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Debug</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void DebugFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n\n    #region Info\n        /// <summary>\n        /// Logs a <c>Info</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Info(object message);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Info(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void InfoFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void InfoFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void InfoFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void InfoFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Info</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void InfoFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n\n    #region Warn\n        /// <summary>\n        /// Logs a <c>Warn</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Warn(object message);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Warn(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void WarnFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void WarnFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void WarnFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void WarnFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Warn</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void WarnFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n\n    #region Error\n        /// <summary>\n        /// Logs a <c>Error</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Error(object message);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Error(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void ErrorFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void ErrorFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void ErrorFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void ErrorFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Error</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void ErrorFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n\n    #region Fatal\n        /// <summary>\n        /// Logs a <c>Fatal</c> level message object.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        void Fatal(object message);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level message object including the stack trace of \n        /// the <see cref=\"T:System.Exception\"/> passed as a parameter.\n        /// </summary>\n        /// <param name=\"message\">The message object to be logged.</param>\n        /// <param name=\"exception\">The exception to be logged, including its stack trace.</param>\n        void Fatal(object message, Exception exception);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level formatted message string with the given <paramref name=\"arg\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg\">The object to format</param>\n        void FatalFormat(string format, object arg);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        void FatalFormat(string format, object arg1, object arg2);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level formatted message string with the given arguments.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"arg1\">The first object to format</param>\n        /// <param name=\"arg2\">The second object to format</param>\n        /// <param name=\"arg3\">The third object to format</param>\n        void FatalFormat(string format, object arg1, object arg2, object arg3);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level formatted message string with the given <paramref name=\"args\"/>.\n        /// </summary>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void FatalFormat(string format, params object[] args);\n\n        /// <summary>\n        /// Logs a <c>Fatal</c> level formatted message string with the \n        /// given <paramref name=\"args\"/> and a given <paramref name=\"provider\"/>.\n        /// </summary>\n        /// <param name=\"provider\">An <see cref= \"T:System.IFormatProvider\"/> that supplies culture-specific formatting information</param>\n        /// <param name=\"format\">A String containing zero or more format items</param>\n        /// <param name=\"args\">An Object array containing zero or more objects to format</param>\n        void FatalFormat(IFormatProvider provider, string format, params object[] args);\n    #endregion\n    }\n\n    /// <summary>\n    /// The <see cref=\"IEasyLogger{T}\"/> interface specifying \n    /// available methods for different levels of logging.\n    /// </summary>\n    // ReSharper disable once UnusedTypeParameter\n    public interface IEasyLogger<T> : IEasyLogger { }\n}"
  },
  {
    "path": "Easy.Logger.Interfaces/ILogService.cs",
    "content": "﻿namespace Easy.Logger.Interfaces\n{\n    using System;\n    using System.IO;\n\n    /// <summary>\n    /// The <see cref=\"ILogService\"/> specifying the methods relating\n    /// to configuring and obtaining an instance of <see cref=\"IEasyLogger\"/>.\n    /// </summary>\n    public interface ILogService : IDisposable\n    {\n        /// <summary>\n        /// Gets the configuration file used to configure the <see cref=\"ILogService\"/>.\n        /// </summary>\n        FileInfo Configuration { get; }\n        \n        /// <summary>\n        /// Configures the logging service by using the specified configuration file.\n        /// </summary>\n        void Configure(FileInfo configFile);\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <paramref name=\"loggerName\"/>.\n        /// </summary>\n        /// <param name=\"loggerName\">The name for which an <see cref=\"IEasyLogger\"/> should be returned</param>\n        /// <returns>The <see cref=\"IEasyLogger\"/></returns>\n        IEasyLogger GetLogger(string loggerName);\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <paramref name=\"loggerType\"/>.\n        /// </summary>\n        /// <param name=\"loggerType\">The <see cref=\"Type\"/> for which an <see cref=\"IEasyLogger\"/> should be returned</param>\n        /// <returns>The <see cref=\"IEasyLogger\"/></returns>\n        IEasyLogger GetLogger(Type loggerType);\n\n        /// <summary>\n        /// Obtains an <see cref=\"IEasyLogger\"/> for the given <typeparamref name=\"T\"/>.\n        /// </summary>\n        /// <typeparam name=\"T\">The type for which an <see cref=\"IEasyLogger\"/> should be returned</typeparam>\n        /// <returns>The <see cref=\"IEasyLogger\"/></returns>\n        IEasyLogger GetLogger<T>();\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Integration/Context.cs",
    "content": "﻿namespace Easy.Logger.Tests.Integration\n{\n    using System;\n    using System.Collections.Concurrent;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Linq;\n    using System.Net;\n    using System.Text.RegularExpressions;\n    using System.Threading;\n    using System.Xml.Linq;\n    using Easy.Logger.Interfaces;\n    using Easy.Logger.Tests.Integration.Models;\n    using NUnit.Framework;\n    using Shouldly;\n\n    [TestFixture]\n    internal class Context\n    {\n        private ILogService _logService;\n        private FileInfo _configFile, _logFile;\n        private EasyLogListener _listener;\n        private ConcurrentQueue<LogPayload> _receivedPayloads;\n\n        [SetUp]\n        public void Setup()\n        {\n            _logService = Log4NetService.Instance;\n\n            _configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"integration.tests-log4net.config\"));\n            _logFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"Logs\", \"Integration.Tests.log\"));\n            \n            if (_logFile.Exists) { _logFile.Delete(); }\n\n            _logService.Configure(_configFile);\n\n            _receivedPayloads = new ConcurrentQueue<LogPayload>();\n            \n            StartHttpServer();\n        }\n\n        [Test]\n        public void Run()\n        {\n            IEasyLogger logger = _logService.GetLogger(GetType());\n            logger.Debug(\"Something is about to happen...\");\n\n            // Should not be replaced by Task.Delay as\n            // Thread name will be changed after await\n            // which will fail the log content assertions\n            Thread.Sleep(TimeSpan.FromSeconds(1.5));\n            \n            logger.InfoFormat(\"What's your number? It's: {0}\", 1234.ToString());\n\n            logger.Error(\"Ooops I did it again!\", new ArgumentNullException(\"cooCoo\", \"Parameter cannot be null.\"));\n\n            logger.FatalFormat(\"Going home now - {0}\", new ApplicationException(\"CiaoCiao\"));\n\n            Thread.Sleep(TimeSpan.FromSeconds(1.5));\n\n            using (logger.GetScopedLogger(\"[Scope 1]\"))\n            {\n                logger.Debug(\"This should be inside scope 1.\");\n\n                using (logger.GetScopedLogger(\"[Scope 2]\"))\n                {\n                    logger.WarnFormat(\"This should be inside scope 1 and scope 2. {0}\", \"Bar\");\n                }\n\n                logger.Error(\"This should still be inside scope 1.\");\n            }\n\n            logger.Fatal(\"This should not be inside any scope.\");\n\n            Thread.Sleep(TimeSpan.FromSeconds(1.5));\n\n            IEasyLogger anotherLogger = _logService.GetLogger<Context>();\n            anotherLogger.ShouldNotBeNull();\n            anotherLogger.Name.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n        }\n\n        [TearDown]\n        public void TestTearDown()\n        {\n            _logService.Dispose();\n            _listener.Dispose();\n\n            CheckLogFileContent(_logFile);\n            CheckReceivedPayloads(_receivedPayloads.ToArray());\n        }\n\n        private static void CheckLogFileContent(FileSystemInfo logFile)\n        {\n            Regex dateTimeRegex = new(@\"^\\[\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\,\\d{3}\\]\\s\\[\", RegexOptions.Compiled);\n\n            string[] lines = File.ReadAllLines(logFile.FullName);\n            lines.Length.ShouldBe(9);\n\n            foreach (string line in lines.Where(x => x != \"System.ArgumentNullException: Parameter cannot be null. (Parameter 'cooCoo')\"))\n            {\n                dateTimeRegex.IsMatch(line).ShouldBeTrue();\n            }\n\n            lines[0].ShouldEndWith(\" [DEBUG] [NonParallelWorker] [Context] Something is about to happen...\");\n            lines[1].ShouldEndWith(\" [INFO ] [NonParallelWorker] [Context] What's your number? It's: 1234\");\n            lines[2].ShouldEndWith(\" [ERROR] [NonParallelWorker] [Context] Ooops I did it again!\");\n            lines[3].ShouldBe(\"System.ArgumentNullException: Parameter cannot be null. (Parameter 'cooCoo')\");\n            lines[4].ShouldEndWith(\" [FATAL] [NonParallelWorker] [Context] Going home now - System.ApplicationException: CiaoCiao\");\n            lines[5].ShouldEndWith(\" [DEBUG] [NonParallelWorker] [Context] [Scope 1] This should be inside scope 1.\");\n            lines[6].ShouldEndWith(\" [WARN ] [NonParallelWorker] [Context] [Scope 1] [Scope 2] This should be inside scope 1 and scope 2. Bar\");\n            lines[7].ShouldEndWith(\" [ERROR] [NonParallelWorker] [Context] [Scope 1] This should still be inside scope 1.\");\n            lines[8].ShouldEndWith(\" [FATAL] [NonParallelWorker] [Context] This should not be inside any scope.\");\n        }\n\n        private static void CheckReceivedPayloads(LogPayload[] payloads)\n        {\n            int pid;\n            string processName;\n            using (Process p = Process.GetCurrentProcess())\n            {\n                pid = p.Id;\n                processName = p.ProcessName;\n            }\n            \n            payloads.Length.ShouldBe(3);\n\n            payloads[0].BatchNo.ShouldBe(1);\n            payloads[0].PID.ShouldBe(pid);\n            payloads[0].ProcessName.ShouldBe(processName);\n            payloads[0].Host.ShouldBe(Dns.GetHostName());\n            payloads[0].TimestampUTC.ShouldBeOfType<DateTimeOffset>();\n            payloads[0].TimestampUTC.ShouldNotBe(default(DateTimeOffset));\n            payloads[0].Sender.ShouldBe(\"Integration.Tests\");\n            payloads[0].Entries.Length.ShouldBe(1);\n\n            payloads[1].BatchNo.ShouldBe(2);\n            payloads[1].PID.ShouldBe(pid);\n            payloads[1].ProcessName.ShouldBe(processName);\n            payloads[1].Host.ShouldBe(Dns.GetHostName());\n            payloads[1].TimestampUTC.ShouldBeOfType<DateTimeOffset>();\n            payloads[1].TimestampUTC.ShouldNotBe(default(DateTimeOffset));\n            payloads[1].Sender.ShouldBe(\"Integration.Tests\");\n            payloads[1].Entries.Length.ShouldBe(3);\n\n            payloads[2].BatchNo.ShouldBe(3);\n            payloads[2].PID.ShouldBe(pid);\n            payloads[2].ProcessName.ShouldBe(processName);\n            payloads[2].Host.ShouldBe(Dns.GetHostName());\n            payloads[2].TimestampUTC.ShouldBeOfType<DateTimeOffset>();\n            payloads[2].TimestampUTC.ShouldNotBe(default(DateTimeOffset));\n            payloads[2].Sender.ShouldBe(\"Integration.Tests\");\n            payloads[2].Entries.Length.ShouldBe(4);\n\n            LogEntry[] allEntries = payloads.SelectMany(p => p.Entries).ToArray();\n            \n            allEntries[0].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[0].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[0].Level.ShouldBe(\"DEBUG\");\n            allEntries[0].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[0].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[0].Message.ShouldBe(\"Something is about to happen...\");\n            allEntries[0].Exception.ShouldBeNull();\n\n            allEntries[1].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[1].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[1].Level.ShouldBe(\"INFO\");\n            allEntries[1].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[1].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[1].Message.ShouldBe(\"What's your number? It's: 1234\");\n            allEntries[1].Exception.ShouldBeNull();\n\n            allEntries[2].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[2].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[2].Level.ShouldBe(\"ERROR\");\n            allEntries[2].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[2].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[2].Message.ShouldBe(\"Ooops I did it again!\");\n            allEntries[2].Exception.ShouldNotBeNull();\n            \n            allEntries[2].Exception.Count.ShouldBe(6);\n\n            allEntries[2].Exception[\"className\"].ShouldBe(\"System.ArgumentNullException\");\n            allEntries[2].Exception[\"paramName\"].ShouldBe(\"cooCoo\");\n            allEntries[2].Exception[\"stackTrace\"].ShouldBeNull();\n            allEntries[2].Exception[\"innerException\"].ShouldBeNull();\n            allEntries[2].Exception[\"message\"].ShouldBe(\"Parameter cannot be null. (Parameter 'cooCoo')\");\n            allEntries[2].Exception[\"source\"].ShouldBeNull();\n\n            allEntries[3].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[3].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[3].Level.ShouldBe(\"FATAL\");\n            allEntries[3].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[3].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[3].Message.ShouldBe(\"Going home now - System.ApplicationException: CiaoCiao\");\n            allEntries[3].Exception.ShouldBeNull();\n\n            allEntries[4].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[4].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[4].Level.ShouldBe(\"DEBUG\");\n            allEntries[4].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[4].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[4].Message.ShouldBe(\"[Scope 1] This should be inside scope 1.\");\n            allEntries[4].Exception.ShouldBeNull();\n\n            allEntries[5].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[5].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[5].Level.ShouldBe(\"WARN\");\n            allEntries[5].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[5].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[5].Message.ShouldBe(\"[Scope 1] [Scope 2] This should be inside scope 1 and scope 2. Bar\");\n            allEntries[5].Exception.ShouldBeNull();\n\n            allEntries[6].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[6].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[6].Level.ShouldBe(\"ERROR\");\n            allEntries[6].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[6].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[6].Message.ShouldBe(\"[Scope 1] This should still be inside scope 1.\");\n            allEntries[6].Exception.ShouldBeNull();\n\n            allEntries[7].DateTimeOffset.ShouldBeOfType<DateTimeOffset>();\n            allEntries[7].DateTimeOffset.ShouldNotBe(default(DateTimeOffset));\n            allEntries[7].Level.ShouldBe(\"FATAL\");\n            allEntries[7].ThreadID.ShouldBe(\"NonParallelWorker\");\n            allEntries[7].LoggerName.ShouldBe(\"Easy.Logger.Tests.Integration.Context\");\n            allEntries[7].Message.ShouldBe(\"This should not be inside any scope.\");\n            allEntries[7].Exception.ShouldBeNull();\n\n            Array.ForEach(payloads, Console.Write);\n        }\n\n        private void StartHttpServer()\n        {\n            string endpointStr = XDocument.Load(_configFile.FullName)\n                .Descendants()\n                .Elements()\n                .Single(e => e.Attributes().Any(a => a.Name == \"name\" && a.Value == \"HTTPAppender\"))\n                .Elements(\"endpoint\")\n                .Single()\n                .Attribute(\"value\")?.Value;\n\n            _listener = new EasyLogListener(new Uri(endpointStr ?? throw new InvalidOperationException()));\n\n            _listener.OnError += (sender, exception) => throw exception;\n            _listener.OnPayload += (sender, payload) => _receivedPayloads.Enqueue(payload);\n            _listener.Start();\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Integration/Easy.Logger.Tests.Integration.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n      <TargetFramework>net8.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.8.0\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n    <PackageReference Include=\"NUnit\" Version=\"4.0.1\" />\n    <PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.5.0\" />\n    <PackageReference Include=\"Shouldly\" Version=\"4.2.1\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Easy.Logger.Extensions\\Easy.Logger.Extensions.csproj\" />\n    <ProjectReference Include=\"..\\Easy.Logger\\Easy.Logger.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Update=\"integration.tests-log4net.config\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "Easy.Logger.Tests.Integration/EasyLogListener.cs",
    "content": "﻿namespace Easy.Logger.Tests.Integration\n{\n    using System;\n    using System.IO;\n    using System.Net;\n    using System.Text;\n    using System.Text.RegularExpressions;\n    using Easy.Logger.Tests.Integration.Models;\n    using System.Threading;\n    using Newtonsoft.Json;\n    using Newtonsoft.Json.Serialization;\n\n    /// <summary>\n    /// A simple <c>HTTP</c> listener for receiving <see cref=\"LogPayload\"/>s.\n    /// </summary>\n    public sealed class EasyLogListener : IDisposable\n    {\n        private const int StatusCodeAccepted = 202;\n        private const int StatusCodeBadRequest = 400;\n        private const int StatusCodeNotAcceptable = 406;\n\n        private static readonly byte[] InvalidRequestMethodMessage = Encoding.UTF8.GetBytes(\"You can only POST a valid JSON payload to this server.\");\n        private static readonly byte[] InvalidPayloadMessage = Encoding.UTF8.GetBytes(\"Invalid payload. Payload should be a valid JSON.\");\n\n        private static readonly Regex JSONMediaTypeRegex =\n            new Regex(@\"([aA][pP][pP][lL][iI][cC][aA][tT][iI][oO][nN]/[jJ][sS][oO][nN]|[tT][eE][xX][tT]/[jJ][sS][oO][nN]|[aA][pP][pP][lL][iI][cC][aA][tT][iI][oO][nN]/[vV][nN][dD]\\.\\S*\\+[jJ][sS][oO][nN])\", RegexOptions.Compiled);\n\n        private readonly HttpListener _listener;\n        private readonly JsonSerializer _serializer;\n        private int _isDisposing;\n\n        /// <summary>\n        /// Invoked when a new <see cref=\"LogPayload\"/> is <c>POST</c>ed.\n        /// </summary>\n        public event EventHandler<LogPayload> OnPayload;\n        \n        /// <summary>\n        /// Invoked when there is an error during the deserialization of <see cref=\"LogPayload\"/>.\n        /// </summary>\n        public event EventHandler<Exception> OnError;\n\n        /// <summary>\n        /// Creates an instance of <see cref=\"EasyLogListener\"/>.\n        /// </summary>\n        /// <param name=\"prefix\">The prefix the listener will be listening on.</param>\n        public EasyLogListener(Uri prefix)\n        {\n            _listener = new HttpListener();\n            _listener.Prefixes.Add(prefix.AbsoluteUri);\n\n            _serializer = new JsonSerializer\n            {\n                ContractResolver = new CamelCasePropertyNamesContractResolver(),\n                DateFormatHandling = DateFormatHandling.IsoDateFormat,\n                DateTimeZoneHandling = DateTimeZoneHandling.Utc,\n                Formatting = Newtonsoft.Json.Formatting.None\n            };\n        }\n\n        /// <summary>\n        /// Starts listening for payloads.\n        /// </summary>\n        public void Start() => StartListening();\n\n        /// <summary>\n        /// Stops and releases resources used by the instance.\n        /// </summary>\n        public void Dispose()\n        {\n            Interlocked.Increment(ref _isDisposing);\n\n            _listener.Stop();\n            _listener.Close();\n        }\n\n        private async void StartListening()\n        {\n            _listener.Start();\n            while (_listener.IsListening)\n            {\n                HttpListenerContext ctx = null;\n\n                try\n                {\n                    ctx = await _listener.GetContextAsync().ConfigureAwait(false);\n                } \n                catch (HttpListenerException e) { HandleException(e); }\n                catch (ObjectDisposedException) { /* Shutting down */ }\n\n                if (ctx == null) { return; }\n\n                var req = ctx.Request;\n                var resp = ctx.Response;\n\n                if (IsValidRequest(req))\n                {\n                    try\n                    {\n                        var payload = Deserialize(req.InputStream);\n                        OnPayload?.Invoke(this, payload);\n                        resp.StatusCode = StatusCodeAccepted;\n                    } \n                    catch (HttpListenerException) { continue; }\n                    catch (Exception e)\n                    {\n                        HandleException(e);\n                        resp.StatusCode = StatusCodeBadRequest;\n                        await resp.OutputStream.WriteAsync(\n                            InvalidPayloadMessage, 0, InvalidPayloadMessage.Length);\n                    }\n                } else\n                {\n                    resp.StatusCode = StatusCodeNotAcceptable;\n                    await resp.OutputStream.WriteAsync(\n                        InvalidRequestMethodMessage, 0, InvalidRequestMethodMessage.Length);\n                }\n\n                resp.OutputStream.Close();\n            }\n        }\n\n        private LogPayload Deserialize(Stream stream)\n        {\n            using (var sr = new StreamReader(stream, Encoding.UTF8, true, 1024, true))\n            using (var jsonTextReader = new JsonTextReader(sr) { CloseInput = false })\n            {\n                return _serializer.Deserialize<LogPayload>(jsonTextReader);\n            }\n        }\n\n        private void HandleException(Exception e)\n        {\n            // If we are NOT disposing, then we should report exception\n            if (Interlocked.CompareExchange(ref _isDisposing, 0, 0) == 0)\n            {\n                OnError?.Invoke(this, e);\n            }\n        }\n\n        private static bool IsValidRequest(HttpListenerRequest req)\n        {\n            return req.HttpMethod.Equals(\"POST\", StringComparison.Ordinal) \n                && IsJSONMediaType(req.ContentType);\n        }\n\n        private static bool IsJSONMediaType(string contentType) => \n            !string.IsNullOrEmpty(contentType) && JSONMediaTypeRegex.IsMatch(contentType);\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Integration/Models/LogPayload.cs",
    "content": "﻿// ReSharper disable InconsistentNaming\nnamespace Easy.Logger.Tests.Integration.Models\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Text;\n\n    /// <summary>\n    /// Represents a log payload.\n    /// </summary>\n    public sealed class LogPayload\n    {\n        /// <summary>\n        /// Gets or sets the host.\n        /// </summary>\n        public string Host { get; set; }\n\n        /// <summary>\n        /// Gets or sets the process id.\n        /// </summary>\n        // ReSharper disable once InconsistentNaming\n        public int PID { get; set; }\n\n        /// <summary>\n        /// Gets or sets the process name.\n        /// </summary>\n        public string ProcessName { get; set; }\n\n        /// <summary>\n        /// Gets or sets the sender name.\n        /// </summary>\n        public string Sender { get; set; }\n\n        /// <summary>\n        /// Gets or sets the time stamp at which the payload was generated.\n        /// </summary>\n        public DateTimeOffset TimestampUTC { get; set; }\n\n        /// <summary>\n        /// Gets or sets the batch number to which the payload belong.\n        /// </summary>\n        public int BatchNo { get; set; }\n\n        /// <summary>\n        /// Gets or sets the entries contained in the payload.\n        /// </summary>\n        public LogEntry[] Entries { get; set; }\n\n        /// <summary>\n        /// Returns a textual representation of the payload.\n        /// </summary>\n        /// <returns></returns>\n        public override string ToString()\n        {\n            var builder = new StringBuilder();\n\n            foreach (var e in Entries)\n            {\n                builder.AppendFormat(\"[{0:yyyy-MM-dd HH:mm:ss,fff}] [{1}] [{2}] [{3}] [{4}] [B:{5,5}] [{6,-5}] [{7,-2}] [{8}] - {9}\",\n                    e.DateTimeOffset,\n                    Host,\n                    Sender,\n                    PID,\n                    ProcessName,\n                    BatchNo,\n                    e.Level,\n                    e.ThreadID,\n                    e.LoggerName,\n                    e.Message);\n\n                builder.AppendLine();\n\n                if (e.Exception != null)\n                {\n                    builder.Append(\"\\t\").Append(e.Exception).AppendLine();\n                }\n            }\n\n            return builder.ToString();\n        }\n    }\n\n    /// <summary>\n    /// Represents a log entry.\n    /// </summary>\n    public class LogEntry\n    {\n        /// <summary>\n        /// Gets or sets the date and time the entry was generated.\n        /// </summary>\n        public DateTimeOffset DateTimeOffset { get; set; }\n\n        /// <summary>\n        /// Gets or sets the logger name.\n        /// </summary>\n        public string LoggerName { get; set; }\n\n        /// <summary>\n        /// Gets or sets the level.\n        /// </summary>\n        public string Level { get; set; }\n\n        /// <summary>\n        /// Gets or sets the thread ID.\n        /// </summary>\n        // ReSharper disable once InconsistentNaming\n        public string ThreadID { get; set; }\n\n        /// <summary>\n        /// Gets or sets the message.\n        /// </summary>\n        public string Message { get; set; }\n\n        /// <summary>\n        /// Gets or sets the exception if any.\n        /// </summary>\n        public Dictionary<string, object> Exception { get; set; }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Integration/integration.tests-log4net.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n  <root>\n    <level value=\"ALL\"/>\n    <appender-ref ref=\"AsyncBufferingForwarder\"/>\n  </root>\n\n  <appender name=\"AsyncBufferingForwarder\" type=\"Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger\">\n    <lossy value=\"false\" />\n    <bufferSize value=\"512\" />\n    <idleTime value=\"200\" />\n    <fix value=\"Message, ThreadName, Exception\" />\n\n    <appender-ref ref=\"FileAppender\"/>\n    <appender-ref ref=\"HTTPAppender\"/>\n  </appender>\n\n  <appender name=\"FileAppender\" type=\"log4net.Appender.FileAppender\">\n    <file value=\"Logs\\Integration.Tests.log\" />\n    <appendToFile value=\"false\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <appender name=\"HTTPAppender\" type=\"Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions\">\n    <name value=\"Integration.Tests\" />\n    <endpoint value=\"http://localhost:1234\" />\n    <includeHost value=\"true\" />\n  </appender>\n</log4net>"
  },
  {
    "path": "Easy.Logger.Tests.Unit/AsyncBufferingForwardingAppenderTests.cs",
    "content": "﻿namespace Easy.Logger.Tests.Unit\n{\n    using System.Collections.Generic;\n    using System.Threading.Tasks;\n    using log4net.Appender;\n    using log4net.Core;\n    using Moq;\n    using NUnit.Framework;\n    using Shouldly;\n\n    [TestFixture]\n    public sealed class AsyncBufferingForwardingAppenderTests\n    {\n        [Test]\n        public async Task When_testing_a_non_lossy_forwarder()\n        {\n            var forwarder = new AsyncBufferingForwardingAppender();\n            forwarder.BufferSize.ShouldBe(512);\n            forwarder.Lossy.ShouldBeFalse();\n            forwarder.Name.ShouldBeNull();\n            forwarder.Appenders.Count.ShouldBe(0);\n            forwarder.Threshold.ShouldBeNull();\n\n            var loggedEvents = new List<LoggingEvent>();\n            var mockedAppender = new Mock<IAppender>();\n            mockedAppender\n                .Setup(s => s.DoAppend(It.IsAny<LoggingEvent>()))\n                .Callback<LoggingEvent>(loggingEvent => loggedEvents.Add(loggingEvent));\n\n            forwarder.AddAppender(mockedAppender.Object);\n\n            forwarder.ActivateOptions();\n\n            loggedEvents.Count.ShouldBe(0);\n\n            forwarder.DoAppend(new LoggingEvent(new LoggingEventData()));\n\n            loggedEvents.Count.ShouldBe(0);\n\n            await Task.Delay(1000);\n\n            loggedEvents.Count.ShouldBe(1);\n\n            forwarder.DoAppend(new[]\n            {\n                new LoggingEvent(new LoggingEventData()),\n                new LoggingEvent(new LoggingEventData())\n            });\n\n            await Task.Delay(1000);\n\n            loggedEvents.Count.ShouldBe(3);\n\n            forwarder.DoAppend(new LoggingEvent(new LoggingEventData()));\n            loggedEvents.Count.ShouldBe(3);\n            forwarder.Close();\n            loggedEvents.Count.ShouldBe(4);\n        }\n\n        [Test]\n        public async Task When_testing_a_lossy_forwarder()\n        {\n            var forwarder = new AsyncBufferingForwardingAppender\n            {\n                Lossy = true,\n                LossyEvaluator = new LevelEvaluator(Level.Error),\n                Fix = FixFlags.ThreadName | FixFlags.Exception | FixFlags.Message\n            };\n\n            forwarder.BufferSize.ShouldBe(512);\n            forwarder.Lossy.ShouldBeTrue();\n            forwarder.Name.ShouldBeNull();\n            forwarder.Appenders.Count.ShouldBe(0);\n            forwarder.Threshold.ShouldBeNull();\n            forwarder.LossyEvaluator.ShouldBeOfType<LevelEvaluator>();\n\n            var loggedEvents = new List<LoggingEvent>();\n            var mockedAppender = new Mock<IAppender>();\n            mockedAppender\n                .Setup(s => s.DoAppend(It.IsAny<LoggingEvent>()))\n                .Callback<LoggingEvent>(loggingEvent => loggedEvents.Add(loggingEvent));\n\n            forwarder.AddAppender(mockedAppender.Object);\n\n            forwarder.ActivateOptions();\n\n            await Task.Delay(1000);\n\n            loggedEvents.Count.ShouldBe(1);\n            loggedEvents[0].Level.ShouldBe(Level.Warn);\n            loggedEvents[0].LoggerName.ShouldBe(\"AsyncBufferingForwardingAppender\");\n            loggedEvents[0].RenderedMessage\n                .ShouldContain(\"This is a 'lossy' appender therefore log messages may be dropped.\");\n\n            var event1 = new LoggingEvent(new LoggingEventData { Level = Level.Info });\n            var event2 = new LoggingEvent(new LoggingEventData { Level = Level.Debug });\n            var event3 = new LoggingEvent(new LoggingEventData { Level = Level.Error });\n            var event4 = new LoggingEvent(new LoggingEventData { Level = Level.Warn });\n            var event5 = new LoggingEvent(new LoggingEventData { Level = Level.Fatal });\n\n            forwarder.LossyEvaluator.IsTriggeringEvent(event1).ShouldBeFalse();\n            forwarder.LossyEvaluator.IsTriggeringEvent(event2).ShouldBeFalse();\n            forwarder.LossyEvaluator.IsTriggeringEvent(event3).ShouldBeTrue();\n            forwarder.LossyEvaluator.IsTriggeringEvent(event4).ShouldBeFalse();\n            forwarder.LossyEvaluator.IsTriggeringEvent(event5).ShouldBeTrue();\n\n            forwarder.DoAppend(event1);\n\n            await Task.Delay(1000);\n\n            loggedEvents.Count.ShouldBe(1);\n            loggedEvents[0].Level.ShouldBe(Level.Warn);\n\n            forwarder.DoAppend(new[] { event2, event3, event4 });\n            forwarder.Flush(true);\n\n            await Task.Delay(1000);\n\n            loggedEvents.Count.ShouldBe(2);\n            loggedEvents[0].Level.ShouldBe(Level.Warn);\n            loggedEvents[1].Level.ShouldBe(Level.Error);\n\n            forwarder.DoAppend(event5);\n            loggedEvents.Count.ShouldBe(2);\n\n            loggedEvents[0].Level.ShouldBe(Level.Warn);\n            loggedEvents[1].Level.ShouldBe(Level.Error);\n\n            forwarder.Close();\n\n            loggedEvents.Count.ShouldBe(3);\n            loggedEvents[0].Level.ShouldBe(Level.Warn);\n            loggedEvents[1].Level.ShouldBe(Level.Error);\n            loggedEvents[2].Level.ShouldBe(Level.Fatal);\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Unit/CreatingNewLog4NetServiceTests.cs",
    "content": "﻿namespace Easy.Logger.Tests.Unit\n{\n    using System;\n    using System.Collections.Generic;\n    using System.IO;\n    using System.IO.Compression;\n    using System.Text;\n    using System.Threading.Tasks;\n    using Helpers;\n    using NUnit.Framework;\n    using Shouldly;\n\n    [TestFixture]\n    public sealed class CreatingNewLog4NetServiceTests\n    {\n        private const string SampleAppName = \"Easy.Logger.Tests.SampleLoggerApp.exe\";\n        private const string LogfileName = \"--LOGFILE--.log\";\n\n        [Test]\n        public async Task When_creating_a_log4net_service_with_default_configuration()\n        {\n            DirectoryInfo extractedApp = null;\n            try\n            {\n                extractedApp = ExtractSampleApp();\n                var pathToSampleApp = Path.Combine(extractedApp.FullName, SampleAppName);\n\n                var log4NetConfigFile = new FileInfo(Path.Combine(extractedApp.FullName, \"log4net.config\"));\n                log4NetConfigFile.Exists.ShouldBeFalse();\n\n                File.WriteAllText(log4NetConfigFile.FullName, GetLog4NetConfiguration(), Encoding.UTF8);\n\n                log4NetConfigFile.Refresh();\n                log4NetConfigFile.Exists.ShouldBeTrue();\n\n                var logFile = new FileInfo(Path.Combine(extractedApp.FullName, LogfileName));\n                logFile.Exists.ShouldBeFalse();\n\n                var errorMessages = new List<string>();\n                using (var process = ProcessHelper.GetProcess(pathToSampleApp, errorMessages))\n                {\n                    process.WaitForExit();\n                    process.ExitCode.ShouldBe(0);\n\n                    logFile.Refresh();\n                    logFile.Exists.ShouldBeTrue();\n\n                    var entries = File.ReadAllLines(logFile.FullName);\n                    entries.Length.ShouldBe(2);\n                    entries[0].ShouldContain(\" [INFO ] [ 1] Program - I am logging....\");\n                    entries[1].ShouldContain(\" [WARN ] [ 1] Program - I am done logging!\");\n                }\n\n                await Task.Delay(TimeSpan.FromSeconds(2));\n            } finally\n            {\n                extractedApp?.Delete(true);\n            }\n        }\n\n        [Test]\n        public async Task When_creating_a_log4net_service_with_default_configuration_and_then_confuguring_it_again()\n        {\n            var baseDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);\n\n            var defaultLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, LogfileName));\n            defaultLogFile.Delete();\n\n            // Let's start by default config\n            var log4NetConfigFile = new FileInfo(Path.Combine(baseDirectory.FullName, \"log4net.config\"));\n            log4NetConfigFile.Delete();\n            File.WriteAllText(log4NetConfigFile.FullName, GetLog4NetConfiguration(), Encoding.UTF8);\n\n            log4NetConfigFile.Refresh();\n            log4NetConfigFile.Exists.ShouldBeTrue();\n\n            var logService = Log4NetService.Instance;\n            var logger = logService.GetLogger(\"Test\");\n\n            defaultLogFile.Refresh();\n            defaultLogFile.Exists.ShouldBeTrue();\n\n            logger.Debug(\"I am in the default log file.\");\n\n            // Let's create a new log4net config file\n            const string NewLogFileName = \"NewLogFile.log\";\n            var newLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, NewLogFileName));\n            newLogFile.Delete();\n\n            var newConfigContent = GetLog4NetConfiguration().Replace(LogfileName, NewLogFileName);\n            var newConfigFile = new FileInfo(Path.Combine(baseDirectory.FullName, \"log4net.config\"));\n            File.WriteAllText(newConfigFile.FullName, newConfigContent, Encoding.UTF8);\n\n            logService.Configure(newConfigFile);\n\n            newLogFile.Refresh();\n            newLogFile.Exists.ShouldBeTrue();\n\n            logger = logService.GetLogger(GetType());\n            logger.Debug(\"I am in the new log file.\");\n\n            // Now let's test updating an existing config file\n            const string NewerLogFileName = \"NewerLogFile.log\";\n            var newerLogFile = new FileInfo(Path.Combine(baseDirectory.FullName, NewerLogFileName));\n            newerLogFile.Delete();\n\n            var updatedConfigContent = GetLog4NetConfiguration().Replace(LogfileName, NewerLogFileName);\n            File.WriteAllText(newConfigFile.FullName, updatedConfigContent, Encoding.UTF8);\n\n            logService.Configure(newConfigFile);\n\n            logger = logService.GetLogger<CreatingNewLog4NetServiceTests>();\n            logger.Debug(\"I am in the newer log file.\");\n\n            newerLogFile.Refresh();\n            newerLogFile.Exists.ShouldBeTrue();\n\n            logService.Dispose();\n\n            var defaultLogFileContent = File.ReadAllText(defaultLogFile.FullName);\n            var newLogFileContent = File.ReadAllText(newLogFile.FullName);\n            var newerLogFileContent = File.ReadAllText(newerLogFile.FullName);\n\n            defaultLogFileContent.ShouldContain(\"Test - I am in the default log file.\");\n            newLogFileContent.ShouldContain(\"CreatingNewLog4NetServiceTests - I am in the new log file.\");\n            newerLogFileContent.ShouldContain(\"CreatingNewLog4NetServiceTests - I am in the newer log file.\");\n\n            await Task.Delay(TimeSpan.FromSeconds(2));\n            \n            log4NetConfigFile.Delete();\n            newConfigFile.Delete();\n            defaultLogFile.Delete();\n            newLogFile.Delete();\n            newerLogFile.Delete();\n        }\n\n        private static DirectoryInfo ExtractSampleApp()\n        {\n            var pathToArchive = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"SampleLoggerApp.zip\");\n\n            var tmpDir = Path.GetTempPath();\n            var randomDir = Path.GetRandomFileName();\n\n            var extractDir = new DirectoryInfo(Path.Combine(tmpDir, randomDir));\n\n            Console.WriteLine($\"Extracting to: {extractDir.FullName}\");\n            ZipFile.ExtractToDirectory(pathToArchive, extractDir.FullName);\n\n            return extractDir;\n        }\n        \n        private static string GetLog4NetConfiguration()\n        {\n            return @\"<?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\" ?>\n                     <log4net>\n                         <root>\n                         <level value=\"\"ALL\"\"/>\n                         <appender-ref ref=\"\"Default\"\"/>\n                         </root>\n                     \n                         <appender name=\"\"Default\"\" type=\"\"Easy.Logger.AsyncBufferingForwardingAppender\"\">\n                         <appender-ref ref=\"\"RollingFile\"\"/>\n                         </appender>\n                     \n                         <appender name=\"\"RollingFile\"\" type=\"\"log4net.Appender.RollingFileAppender\"\">\n                         <file type=\"\"log4net.Util.PatternString\"\" value=\"\"--LOGFILE--.log\"\" />\n                         <appendToFile value=\"\"false\"\"/>\n                         <rollingStyle value=\"\"Composite\"\"/>\n                         <maxSizeRollBackups value=\"\"-1\"\"/>\n                         <maximumFileSize value=\"\"50MB\"\"/>\n                         <staticLogFileName value=\"\"true\"\"/>\n                         <datePattern value=\"\"yyyy-MM-dd\"\"/>\n                         <layout type=\"\"log4net.Layout.PatternLayout\"\">\n                             <conversionPattern value=\"\"%date{ISO8601} [%-5level] [%2thread] %logger{1} - %message%newline%exception\"\"/>\n                         </layout>\n                         </appender>\n                     </log4net>\";\n        }\n    }\n}\n"
  },
  {
    "path": "Easy.Logger.Tests.Unit/Easy.Logger.Tests.Unit.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net8.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Castle.Core\" Version=\"5.1.1\" />\n    <PackageReference Include=\"log4net\" Version=\"2.0.15\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.8.0\" />\n    <PackageReference Include=\"Moq\" Version=\"4.20.70\" />\n    <PackageReference Include=\"NUnit\" Version=\"4.0.1\" />\n    <PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.5.0\" />\n    <PackageReference Include=\"Shouldly\" Version=\"4.2.1\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\Easy.Logger.Extensions\\Easy.Logger.Extensions.csproj\" />\n    <ProjectReference Include=\"..\\Easy.Logger\\Easy.Logger.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Update=\"SampleLoggerApp.zip\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "Easy.Logger.Tests.Unit/HTTPAppenderTests.cs",
    "content": "﻿namespace Easy.Logger.Tests.Unit\n{\n    using System;\n    using System.Threading.Tasks;\n    using Easy.Logger.Extensions;\n    using log4net.Core;\n    using NUnit.Framework;\n    using Shouldly;\n\n    [TestFixture]\n    internal sealed class HTTPAppenderTests\n    {\n        [Test]\n        public void When_creating_appender_without_name()\n        {\n            var appender = new HTTPAppender\n            {\n                Endpoint = \"http://localhost:1234\"\n            };\n\n            appender.Name.ShouldBeNull();\n            appender.Endpoint.ShouldBe(\"http://localhost:1234\");\n\n            appender.ActivateOptions();\n\n            appender.Name.ShouldNotBeNullOrWhiteSpace();\n        }\n        \n        [Test]\n        public void When_activating_appender()\n        {\n            var appender = new HTTPAppender\n            {\n                Name = \"FooBar\",\n                Endpoint = \"http://localhost:1234\"\n            };\n\n            appender.Name.ShouldBe(\"FooBar\");\n            appender.Endpoint.ShouldBe(\"http://localhost:1234\");\n\n            appender.ActivateOptions();\n\n            appender.Name.ShouldBe(\"FooBar\");\n        }\n\n        [Test]\n        public void When_activating_appender_with_invalid_endpoint()\n        {\n            var invalidEndpoints = new[]\n            {\n                null,\n                string.Empty,\n                \" \",\n                \"ftp://localhost:1234\",\n                \"tcp://localhost:1234\"\n            };\n\n            Array.ForEach(invalidEndpoints, e =>\n            {\n                Should.Throw<ArgumentException>(() => new HTTPAppender\n                {\n                    Endpoint = e\n                }.ActivateOptions())\n                    .Message.ShouldBe(\"Invalid endpoint.\");\n            });\n        }\n\n        [Test]\n        public async Task When_logging_events_through_appender()\n        {\n            var appender = new HTTPAppender\n            {\n                Name = \"Tester\",\n                Endpoint = \"http://localhost:1234\"\n            };\n\n            appender.DoAppend(new LoggingEvent(new LoggingEventData\n            {\n                Message = \"Foo\",\n                ExceptionString = \"foo\",\n                LoggerName = \"boo\",\n                Level = Level.Debug,\n                TimeStampUtc = DateTime.UtcNow,\n                ThreadName = \"12\"\n                \n            }));\n            \n            appender.DoAppend(new LoggingEvent(new LoggingEventData { Message = \"Bar\" }));\n            appender.DoAppend(new LoggingEvent(new LoggingEventData { Message = \"Baz\" }));\n\n            await Task.Delay(TimeSpan.FromSeconds(1));\n\n            appender.Close();\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.Tests.Unit/Helpers/ProcessHelper.cs",
    "content": "﻿namespace Easy.Logger.Tests.Unit.Helpers\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n\n    internal static class ProcessHelper\n    {\n        public static Process GetProcess(string processPath, IList<string> outputMessages)\n        {\n            var processInfo = new ProcessStartInfo(processPath)\n            {\n                UseShellExecute = false,\n                CreateNoWindow = true,\n                RedirectStandardOutput = true,\n                RedirectStandardError = true\n            };\n\n            var process = new Process\n            {\n                EnableRaisingEvents = true,\n                StartInfo = processInfo\n            };\n\n            var locker = new object();\n\n            process.OutputDataReceived += (sender, args) =>\n            {\n                if (args?.Data == null) { return; }\n\n                lock (locker)\n                {\n                    outputMessages.Add(args.Data);\n                }\n\n                Debug.WriteLine(args.Data);\n            };\n            process.ErrorDataReceived += (sender, args) =>\n            {\n                var errMsg = \"[Process Error] - \";\n\n                if (args?.Data != null)\n                {\n                    errMsg += args.Data;\n                } else\n                {\n                    errMsg += \"No data available\";\n                }\n\n                lock (locker)\n                {\n                    outputMessages.Add(errMsg);\n                }\n\n                Console.WriteLine(errMsg);\n            };\n\n            process.Start();\n            process.BeginOutputReadLine();\n            process.BeginErrorReadLine();\n\n            return process;\n        }\n    }\n}\n"
  },
  {
    "path": "Easy.Logger.Tests.Unit/Log4NetLoggerTests.cs",
    "content": "﻿namespace Easy.Logger.Tests.Unit\n{\n    using System;\n    using System.Globalization;\n    using Easy.Logger.Interfaces;\n    using log4net;\n    using log4net.Core;\n    using Moq;\n    using NUnit.Framework;\n    using Shouldly;\n    \n    [TestFixture]\n    public sealed class Log4NetLoggerTests\n    {\n        private Mock<ILog> _mockedLogger;\n        private Mock<ILogger> _mockedInnerLogger;\n        private IEasyLogger _logger;\n\n        [OneTimeSetUp]\n        public void SetUp()\n        {\n            _mockedInnerLogger = new Mock<ILogger>();\n            _mockedInnerLogger.Setup(l => l.Name).Returns(\"Inner Logger\");\n\n            _mockedLogger = new Mock<ILog>();\n            _mockedLogger.Setup(l => l.Logger).Returns(_mockedInnerLogger.Object);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Trace)).Returns(true);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Debug)).Returns(true);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Info)).Returns(true);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Warn)).Returns(true);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Error)).Returns(true);\n            _mockedInnerLogger.Setup(l => l.IsEnabledFor(Level.Fatal)).Returns(true);\n\n            _logger = new Log4NetLogger(_mockedLogger.Object);\n            _logger.Name.ShouldBe(\"Inner Logger\");\n        }\n\n        [Test]\n        public void When_logging_using_trace()\n        {\n            _logger.Trace(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Trace(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"Ooops\", ex), Times.Once);\n        \n            _logger.TraceFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"E:1\", null), Times.Once);\n\n            _logger.TraceFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"E:1, 2\", null), Times.Once);\n\n            _logger.TraceFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"E:1, 2, 3\", null), Times.Once);\n\n            _logger.TraceFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"E:1, 2, 3, 4, 5\", null), Times.Once);\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.TraceFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Trace, \"Date: 28/12/2000 01:04:43\", null), Times.Once);\n        }\n\n        [Test]\n        public void When_logging_using_debug()\n        {\n            _logger.Debug(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Debug(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, \"Ooops\", ex), Times.Once);\n            \n            _logger.DebugFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(2));\n\n            _logger.DebugFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(3));\n\n            _logger.DebugFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(4));\n\n            _logger.DebugFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(5));\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.DebugFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Debug, It.IsAny<string>(), null), Times.Exactly(6));\n        }\n\n        [Test]\n        public void When_logging_using_info()\n        {\n            _logger.Info(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Info(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, \"Ooops\", ex), Times.Once);\n\n            _logger.InfoFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(2));\n\n            _logger.InfoFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(3));\n\n            _logger.InfoFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(4));\n\n            _logger.InfoFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(5));\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.InfoFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Info, It.IsAny<string>(), null), Times.Exactly(6));\n        }\n\n        [Test]\n        public void When_logging_using_warn()\n        {\n            _logger.Warn(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Warn(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, \"Ooops\", ex), Times.Once);\n        \n            _logger.WarnFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(2));\n\n            _logger.WarnFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(3));\n\n            _logger.WarnFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(4));\n\n            _logger.WarnFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(5));\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.WarnFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Warn, It.IsAny<string>(), null), Times.Exactly(6));\n        }\n\n        [Test]\n        public void When_logging_using_error()\n        {\n            _logger.Error(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Error(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, \"Ooops\", ex), Times.Once);\n        \n            _logger.ErrorFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(2));\n\n            _logger.ErrorFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(3));\n\n            _logger.ErrorFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(4));\n\n            _logger.ErrorFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(5));\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.ErrorFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Error, It.IsAny<string>(), null), Times.Exactly(6));\n        }\n\n        [Test]\n        public void When_logging_using_fatal()\n        {\n            _logger.Fatal(\"Hi\");\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, \"Hi\", null), Times.Once);\n\n            var ex = new InvalidOperationException();\n            _logger.Fatal(\"Ooops\", ex);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, \"Ooops\", ex), Times.Once);\n        \n            _logger.FatalFormat(\"E:{0}\", 1);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(2));\n\n            _logger.FatalFormat(\"E:{0}, {1}\", 1, 2);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(3));\n\n            _logger.FatalFormat(\"E:{0}, {1}, {2}\", 1, 2, 3);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(4));\n\n            _logger.FatalFormat(\"E:{0}, {1}, {2}, {3}, {4}\", 1, 2, 3, 4, 5);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(5));\n\n            var italianCulture = new CultureInfo(\"it-It\");\n            var date = new DateTime(2000, 12, 28, 1, 4, 43, 0);\n            _logger.FatalFormat(italianCulture, \"Date: {0}\", date);\n            _mockedInnerLogger.Verify(l => l.Log(typeof(Log4NetLogger), Level.Fatal, It.IsAny<string>(), null), Times.Exactly(6));\n        }\n\n        [Test]\n        public void When_checking_logger_levels()\n        {\n            _logger.IsDebugEnabled.ShouldBeFalse();\n            _mockedLogger.Verify(l => l.IsDebugEnabled, Times.Once);\n\n            _logger.IsInfoEnabled.ShouldBeFalse();\n            _mockedLogger.Verify(l => l.IsInfoEnabled, Times.Once);\n\n            _logger.IsWarnEnabled.ShouldBeFalse();\n            _mockedLogger.Verify(l => l.IsWarnEnabled, Times.Once);\n\n            _logger.IsErrorEnabled.ShouldBeFalse();\n            _mockedLogger.Verify(l => l.IsErrorEnabled, Times.Once);\n\n            _logger.IsFatalEnabled.ShouldBeFalse();\n            _mockedLogger.Verify(l => l.IsFatalEnabled, Times.Once);\n        }\n    }\n}"
  },
  {
    "path": "Easy.Logger.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29102.190\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Easy.Logger\", \"Easy.Logger\\Easy.Logger.csproj\", \"{F1CFACD6-414E-4921-90FD-0949764E1756}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Easy.Logger.Benchmarker\", \"Easy.Logger.Benchmarker\\Easy.Logger.Benchmarker.csproj\", \"{2B08D00F-F8E8-418E-9533-A50E92E9448F}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Easy.Logger.Benchmarker.Core\", \"Easy.Logger.Benchmarker.Core\\Easy.Logger.Benchmarker.Core.csproj\", \"{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Easy.Logger.Tests.Unit\", \"Easy.Logger.Tests.Unit\\Easy.Logger.Tests.Unit.csproj\", \"{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Easy.Logger.Interfaces\", \"Easy.Logger.Interfaces\\Easy.Logger.Interfaces.csproj\", \"{80248AFD-52ED-47A0-B077-97AE8DC067CF}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Tests\", \"Tests\", \"{BF77A4B4-7A20-4D72-8E5D-4CA911B5773C}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Easy.Logger.Extensions\", \"Easy.Logger.Extensions\\Easy.Logger.Extensions.csproj\", \"{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Easy.Logger.Tests.Integration\", \"Easy.Logger.Tests.Integration\\Easy.Logger.Tests.Integration.csproj\", \"{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Benchmarkers\", \"Benchmarkers\", \"{14D6F543-A644-4A00-A740-9DE5EDE5CA30}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{CAC5E6B4-BDDA-4373-A2F4-EAE0B825182C}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tREADME.md = README.md\n\tEndProjectSection\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Easy.Logger.Extensions.Microsoft\", \"Easy.Logger.Extensions.Microsoft\\Easy.Logger.Extensions.Microsoft.csproj\", \"{DAAFA936-DA6B-4BF5-8681-107923EACFE9}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{F1CFACD6-414E-4921-90FD-0949764E1756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F1CFACD6-414E-4921-90FD-0949764E1756}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F1CFACD6-414E-4921-90FD-0949764E1756}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F1CFACD6-414E-4921-90FD-0949764E1756}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{2B08D00F-F8E8-418E-9533-A50E92E9448F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{428CEA5C-6692-4D4C-8C68-CE1E7826B69F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{80248AFD-52ED-47A0-B077-97AE8DC067CF}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{84135FC0-FE53-47AC-AB47-FC5B0CDB2ECD}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{DAAFA936-DA6B-4BF5-8681-107923EACFE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{DAAFA936-DA6B-4BF5-8681-107923EACFE9}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{DAAFA936-DA6B-4BF5-8681-107923EACFE9}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{DAAFA936-DA6B-4BF5-8681-107923EACFE9}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{2B08D00F-F8E8-418E-9533-A50E92E9448F} = {14D6F543-A644-4A00-A740-9DE5EDE5CA30}\n\t\t{428CEA5C-6692-4D4C-8C68-CE1E7826B69F} = {14D6F543-A644-4A00-A740-9DE5EDE5CA30}\n\t\t{3F18ECA6-B525-469C-BCE6-A75E2ED9F5D5} = {BF77A4B4-7A20-4D72-8E5D-4CA911B5773C}\n\t\t{F9A7EB15-6EA6-4512-A6F9-A9A23C78B10F} = {BF77A4B4-7A20-4D72-8E5D-4CA911B5773C}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {6C763796-27F3-432F-8218-C713731C45E3}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Nima Ara\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "NuGet-Pack-Easy.Logger.Extensions.Microsoft.bat",
    "content": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Extensions.Microsoft\ndotnet pack .\\Easy.Logger.Extensions.Microsoft\\Easy.Logger.Extensions.Microsoft.csproj --output .\\nupkgs --configuration Release /p:Version=%releaseVersion% --include-symbols --include-source"
  },
  {
    "path": "NuGet-Pack-Easy.Logger.Extensions.bat",
    "content": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Extensions\ndotnet pack .\\Easy.Logger.Extensions\\Easy.Logger.Extensions.csproj --output .\\nupkgs --configuration Release /p:Version=%releaseVersion% --include-symbols --include-source"
  },
  {
    "path": "NuGet-Pack-Easy.Logger.Interfaces.bat",
    "content": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger.Interfaces\ndotnet pack .\\Easy.Logger.Interfaces\\Easy.Logger.Interfaces.csproj --output .\\nupkgs --configuration Release /p:Version=%releaseVersion% --include-symbols --include-source"
  },
  {
    "path": "NuGet-Pack-Easy.Logger.bat",
    "content": "@echo off\nset releaseVersion=%1\n\ndotnet restore .\\Easy.Logger\ndotnet pack .\\Easy.Logger\\Easy.Logger.csproj --output .\\nupkgs --configuration Release /p:Version=%releaseVersion% --include-symbols --include-source"
  },
  {
    "path": "README.md",
    "content": "[![Build status](https://ci.appveyor.com/api/projects/status/k6ng7qdsd30c3nep?svg=true)](https://ci.appveyor.com/project/NimaAra/easy-logger)<br>\n\n### NuGet\n[![NuGet](https://img.shields.io/nuget/v/Easy.Logger.svg?label=Easy.Logger)](https://www.nuget.org/packages/Easy.Logger)\n[![NuGet](https://img.shields.io/nuget/v/Easy.Logger.Interfaces.svg?label=Easy.Logger.Interfaces)](https://www.nuget.org/packages/Easy.Logger.Interfaces)\n[![NuGet](https://img.shields.io/nuget/v/Easy.Logger.Extensions.svg?label=Easy.Logger.Extensions)](https://www.nuget.org/packages/Easy.Logger.Extensions)\n[![NuGet](https://img.shields.io/nuget/v/Easy.Logger.Extensions.Microsoft.svg?label=Easy.Logger.Extensions.Microsoft)](https://www.nuget.org/packages/Easy.Logger.Extensions.Microsoft)\n\n# Easy Logger\nA modern, high performance, cross platform wrapper for Log4Net.\n\nSupports _.Net Core_ (_.Net 4_ & _netstandard1.3_) running on:\n* .Net Core\n* .Net Framework 4 and above\n* Mono & Xamarin\n\nDetails and benchmarks [HERE](http://www.nimaara.com/2016/01/01/high-performance-logging-log4net/).\n\n##### If you enjoy what I build then please <a href=\"https://www.buymeacoffee.com/sP0BhM9n6\" target=\"_blank\"><img src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" width=\"120px\" ></a> :-)\n___\n\n\n### Usage example:\n\nStart by getting the singleton instance which by convention expects a valid `log4net.config` file at the root of your application:\n```csharp\nILogService logService = Log4NetService.Instance;\n```\nIf you need to configure **log4net** using an alternative configuration file, you can do so by:\n```csharp\nlogService.Configure(new FileInfo(\"path-to-your-log4net-config-file\"));\n```\n\n\\* Any change to the log4net configuration file will be reflected immediately without the need to restart the application.\n\nNow that you have an instance of the `ILogService`, you can get a logger using the `GetLogger()` method in three different ways:\n\n##### Generics:\n```csharp\nIEasyLogger logger = logService.GetLogger<Program>();\nlogger.Debug(\"I am in Program!\");\n```\n##### Type:\n```csharp\nlogger = logService.GetLogger(typeof(MyService));\nlogger.Debug(\"I am in MyService!\");\n```\n##### Plain string:\n```csharp\nlogger = logService.GetLogger(\"Paradise\");\nlogger.Debug(\"I am in Paradise!\");\n```\n\nThe above logging results in the the following log entries (based on the [sample](https://github.com/NimaAra/Easy.Logger/blob/master/Easy.Logger/sample-log4net.config) config file):\n\n```\n[2016-06-29 00:11:24,590] [DEBUG] [ 1] [Program] - I am in Main!\n[2016-06-29 00:11:24,595] [DEBUG] [ 1] [MyService] - I am in MyService!\n[2016-06-29 00:11:24,595] [DEBUG] [ 1] [Paradise] - I am in Paradise!\n```\n\n### Scoped logging:\nSometimes you need to add context to your log entries for example when adding a _Correlation ID_ to identify a request. Instead of having to do this manually, we can leverage the scoping feature of the library:\n\n```csharp\nconst string REQUEST_ID = \"M1351\";\nusing (logger.GetScopedLogger($\"[{REQUEST_ID}]\"))\n{\n    logger.Info(\"Foo is awesome.\");\n    logger.Debug(\"Bar is even more awesome!\");\n}\n```\nwhich produces the following log entries:\n```\n[2017-09-17 17:39:16,573] [INFO ] [ 1] [Program] - [M1351] Foo is awesome.\n[2017-09-17 17:39:16,575] [DEBUG] [ 1] [Program] - [M1351] Bar is even more awesome!\n```\nThe scoping feature has been implemented with performance in mind and is encouraged to be used instead of the [_log4net_'s _Nested Diagnostic Context (NDC)_](https://logging.apache.org/log4net/log4net-1.2.11/release/sdk/log4net.NDC.html). _NDC_ requires `Fixing` the _Properties_ which results in a significantly lower performance.\n\n### Dependency Injection:\n\nThe library has been designed with _DI_ in mind so as an example, given the service class and its corresponding interface below:\n\n```csharp\npublic class MyService : IService\n{\n    private readonly IEasyLogger _logger;\n\n    public MyService(ILogService logService) \n        => _logger = logService.GetLogger(this.GetType());\n\n    public void Start(() => _logger.Debug(\"I am started\"));\n}\n\npublic interface IService\n{\n    void Start();\n}\n```\n\nUsing your favorite _IoC_ container you can do:\n\n```csharp\nvar container = new Container();\ncontainer.RegisterSingleton<ILogService>(logService);\ncontainer.Register<IService, MyService>();\n\nvar service = container.GetInstance<IService>();\nservice.Start();\n```\n\nThe above can be even further simplified by injecting the [`IEasyLogger<T>`](https://github.com/NimaAra/Easy.Logger/blob/bf8e0e4caa1443562438c18a2d29f4bc09407ec0/Easy.Logger.Interfaces/IEasyLogger.cs#L403) directly instead of the `ILogService`:\n\n```csharp\npublic class MyService : IService\n{\n    private readonly IEasyLogger _logger;\n\n    public MyService(IEasyLogger<MyService> logger) => _logger = logger;\n\n    public void Start(() => _logger.Debug(\"I am started\"));\n}\n```\n\nIf your _IoC_ container supports [mapping generic interfaces to generic implementations](https://simpleinjector.readthedocs.io/en/latest/advanced.html#registration-of-open-generic-types), e.g. [Simple Injector](https://simpleinjector.org), then you can further simplify the registration by:\n\n```csharp\ncontainer.Register(typeof(IEasyLogger<>), typeof(Log4NetLogger<>));\n```\n\nRunning any of the flavors above results in the following log entry:\n\n```\n[2016-06-29 00:15:51,661] [DEBUG] [ 1] [MyService] - I am started\n```\n\n### Disposal\nThe library does not need to be disposed explicitly as the framework takes care of the flushing of any pending log entries. In any case you can explicitly dispose the `Log4NetService` by:\n\n```csharp\nlogService.Dispose();\n```\n\n## ASP.NET Core Integration\n_Easy.Logger_ can be used in *ASP.NET Core* by referencing the [_Easy.Logger.Extensions.Microsoft_]((https://www.nuget.org/packages/Easy.Logger.Extensions.Microsoft)) _NuGet_ package. For more details on how to get started, take a look at [HERE](https://github.com/NimaAra/Easy.Logger/blob/master/Easy.Logger.Extensions.Microsoft/README.md).\n\n## Easy Logger Extensions\nThe [_Easy.Logger.Extensions_*](https://github.com/NimaAra/Easy.Logger/tree/master/Easy.Logger.Extensions) package offers more functionality to extend _log4net_. The package currently contains the [HTTPAppender](https://github.com/NimaAra/Easy.Logger/blob/master/Easy.Logger.Extensions/HTTPAppender.cs) which uses the `HTTPClient` to _POST_ _JSON_ payloads of log events to an endpoint. Take the following configuration as an example:\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<log4net>\n  <root>\n    <level value=\"ALL\"/>\n    <appender-ref ref=\"AsyncBufferingForwarder\"/>\n  </root>\n\n  <appender name=\"AsyncBufferingForwarder\" type=\"Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger\">\n    <lossy value=\"false\" />\n    <bufferSize value=\"512\" />\n    \n    <idleTime value=\"500\" />\n    <fix value=\"Message, ThreadName, Exception\" />\n  \n    <appender-ref ref=\"RollingFile\"/>\n    <appender-ref ref=\"HTTPAppender\"/>\n  </appender>\n\n  <appender name=\"RollingFileAppender\" type=\"log4net.Appender.RollingFileAppender\">\n    <file type=\"log4net.Util.PatternString\" value=\"App-%date{yyyy-MM-dd}.log\" />\n    <appendToFile value=\"false\"/>\n    <rollingStyle value=\"Composite\"/>\n    <maxSizeRollBackups value=\"-1\"/>\n    <maximumFileSize value=\"50MB\"/>\n    <staticLogFileName value=\"true\"/>\n    <datePattern value=\"yyyy-MM-dd\"/>\n    <preserveLogFileNameExtension value=\"true\"/>\n    <countDirection value=\"1\"/>\n    <layout type=\"log4net.Layout.PatternLayout\">\n      <conversionPattern value=\"[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception\"/>\n    </layout>\n  </appender>\n\n  <appender name=\"HTTPAppender\" type=\"Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions\">\n    <name value=\"SampleApp\" />\n    <endpoint value=\"http://localhost:1234\" />\n    <includeHost value=\"true\" />\n  </appender>\n```\n\nThe configuration specifies two appenders:\n1. RollingFileAppender\n2. HTTPAppender\n\nBoth of these appenders have been placed inside the `AsyncBufferingForwardingAppender` which buffers and batches up the events then pushes the entries to each of them in order.\n\nSo in addition to the log events being persisted to a log file they are also serialized to _JSON_ and asynchronously _POSTed_ to an endpoint specified at: `http://localhost:1234`. All of this has been implemented in an efficient manner to reduce _GC_ and overhead.\n\nA sample Web server (not suitable for production) which only accepts valid log payloads has been implemented as [`EasyLogListener`](https://github.com/NimaAra/Easy.Logger/blob/master/Easy.Logger.Tests.Integration/EasyLogListener.cs) and the models for deserializing the _JSON_ payload can be found in [`LogPayload`](https://github.com/NimaAra/Easy.Logger/blob/master/Easy.Logger.Tests.Integration/Models/LogPayload.cs). For more info on sending and receiving the log messages, take a look at the [Easy.Logger.Tests.Integration](https://github.com/NimaAra/Easy.Logger/tree/master/Easy.Logger.Tests.Integration) project.\n\nGiven the following log entries:\n\n```csharp\nlogger.Debug(\"Something is about to happen...\");\nlogger.InfoFormat(\"What's your number? It's: {0}\", 1234);\nlogger.Error(\"Ooops I did it again!\", new ArgumentNullException(\"cooCoo\"));\nlogger.FatalFormat(\"Going home now {0}\", new ApplicationException(\"CiaoCiao\"));\n```\n\nThe following payload will then be received at the endpoint:\n\n```json\n{\n  \"pid\": \"12540\",\n  \"processName\": \"SampleApp.x32\",\n  \"host\": \"TestServer-01\",\n  \"sender\": \"SampleApp\",\n  \"timestampUTC\": \"2017-09-17T16:27:14.9377168+00:00\",\n  \"batchNo\": 1,\n  \"entries\": [\n    {\n      \"dateTimeOffset\": \"2017-09-17T17:27:14.4107128+01:00\",\n      \"loggerName\": \"Sample.Program\",\n      \"level\": \"DEBUG\",\n      \"threadID\": \"1\",\n      \"message\": \"Something is about to happen...\",\n      \"exception\": null\n    },\n    {\n      \"dateTimeOffset\": \"2017-09-17T17:27:14.4147491+01:00\",\n      \"loggerName\": \"Sample.Program\",\n      \"level\": \"INFO\",\n      \"threadID\": \"1\",\n      \"message\": \"What's your number? It's: 1234\",\n      \"exception\": null\n    },\n    {\n      \"dateTimeOffset\": \"2017-09-17T17:27:14.4182507+01:00\",\n      \"loggerName\": \"Sample.Program\",\n      \"level\": \"ERROR\",\n      \"threadID\": \"1\",\n      \"message\": \"Ooops I did it again!\",\n      \"exception\": {\n        \"ClassName\": \"System.ArgumentNullException\",\n        \"Message\": \"Value cannot be null.\",\n        \"Data\": null,\n        \"InnerException\": null,\n        \"HelpURL\": null,\n        \"StackTraceString\": null,\n        \"RemoteStackTraceString\": null,\n        \"RemoteStackIndex\": 0,\n        \"ExceptionMethod\": null,\n        \"HResult\": -2147467261,\n        \"Source\": null,\n        \"WatsonBuckets\": null,\n        \"ParamName\": \"cooCoo\"\n      }\n    },\n    {\n      \"dateTimeOffset\": \"2017-09-17T17:27:14.4187508+01:00\",\n      \"loggerName\": \"Sample.Program\",\n      \"level\": \"FATAL\",\n      \"threadID\": \"1\",\n      \"message\": \"Going home now System.ApplicationException: CiaoCiao\",\n      \"exception\": null\n    }\n  ]\n}\n```\n\n<p>* Requires minimum <i>NET 4.5</i> or <i>netstandard1.3</i>.</p>\n"
  }
]