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

* Requires minimum NET 4.5 or netstandard1.3.