A modern, high performance cross platform wrapper for Log4Net.

README.md

Build status

NuGet

NuGet NuGet NuGet NuGet

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.


Usage example:

Start by getting the singleton instance which by convention expects a valid log4net.config file at the root of your application:

ILogService logService = Log4NetService.Instance;

If you need to configure log4net using an alternative configuration file, you can do so by:

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:
IEasyLogger logger = logService.GetLogger<Program>();
logger.Debug("I am in Program!");
Type:
logger = logService.GetLogger(typeof(MyService));
logger.Debug("I am in MyService!");
Plain string:
logger = logService.GetLogger("Paradise");
logger.Debug("I am in Paradise!");

The above logging results in the the following log entries (based on the sample 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:

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). 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:

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:

var container = new Container();
container.RegisterSingleton<ILogService>(logService);
container.Register<IService, MyService>();

var service = container.GetInstance<IService>();
service.Start();

The above can be even further simplified by injecting the IEasyLogger<T> directly instead of the ILogService:

public class MyService : IService
{
    private readonly IEasyLogger _logger;

    public MyService(IEasyLogger<MyService> logger) => _logger = logger;

    public void Start(() => _logger.Debug("I am started"));
}

If your IoC container supports mapping generic interfaces to generic implementations, e.g. Simple Injector, then you can further simplify the registration by:

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:

logService.Dispose();

ASP.NET Core Integration

Easy.Logger can be used in ASP.NET Core by referencing the Easy.Logger.Extensions.Microsoft NuGet package. For more details on how to get started, take a look at HERE.

Easy Logger Extensions

The Easy.Logger.Extensions* package offers more functionality to extend log4net. The package currently contains the HTTPAppender which uses the HTTPClient to POST JSON payloads of log events to an endpoint. Take the following configuration as an example:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <root>
    <level value="ALL"/>
    <appender-ref ref="AsyncBufferingForwarder"/>
  </root>

  <appender name="AsyncBufferingForwarder" type="Easy.Logger.AsyncBufferingForwardingAppender, Easy.Logger">
    <lossy value="false" />
    <bufferSize value="512" />
    
    <idleTime value="500" />
    <fix value="Message, ThreadName, Exception" />
  
    <appender-ref ref="RollingFile"/>
    <appender-ref ref="HTTPAppender"/>
  </appender>

  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file type="log4net.Util.PatternString" value="App-%date{yyyy-MM-dd}.log" />
    <appendToFile value="false"/>
    <rollingStyle value="Composite"/>
    <maxSizeRollBackups value="-1"/>
    <maximumFileSize value="50MB"/>
    <staticLogFileName value="true"/>
    <datePattern value="yyyy-MM-dd"/>
    <preserveLogFileNameExtension value="true"/>
    <countDirection value="1"/>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="[%date{ISO8601}] [%-5level] [%2thread] [%logger{1}] - %message%newline%exception"/>
    </layout>
  </appender>

  <appender name="HTTPAppender" type="Easy.Logger.Extensions.HTTPAppender, Easy.Logger.Extensions">
    <name value="SampleApp" />
    <endpoint value="http://localhost:1234" />
    <includeHost value="true" />
  </appender>

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 and the models for deserializing the JSON payload can be found in LogPayload. For more info on sending and receiving the log messages, take a look at the Easy.Logger.Tests.Integration project.

Given the following log entries:

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:

{
  "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.