Adding logging to a library using LibLog


We needed to add logging support to our library Platron.Client - API client to payment service https://platron.ru. What we decided not do: invent a new logger. Therefore there is not so much opportunities. Adding one more constraint - don’t add nuget dependency - and there is LibLog appears. As for me - it’s code due of supporting several platforms and adding compile time options is not readable and easily supportable. But it can be seamlessly integrated with several popular loggers, and one more thing to mention - it was used in IdentityServer.

First of install package LibLog. It will place single file in App_Packages\LibLog.4.2\LibLog.cs.

Installed LibLog

One nice thing is namespace - it was just what doctor ordered:

Default namespace of LibLog

In our case we needed to log only couple of things: request, response, exception details. So all integration took several minutes.

public sealed class HttpRequestEncoder
{
        private static readonly ILog Logger = LogProvider.For<HttpRequestEncoder>();
        
        // ....
        
        private void LogQueryString(ApiRequest apiRequest, HttpRequestMessage httpRequest)
        {
            Logger.DebugFormat(
                "Executing request: MerchantId={0}, Salt={1}: HttpMethod={2}, RequestUri={3}",
                apiRequest.Plain.MerchantId,
                apiRequest.Plain.Salt,
                httpRequest.Method,
                httpRequest.RequestUri);
        }
}

Most interesting thing here is how to test it. Test correctness of logged information and make sure that it will really help to find out reasons of errors using library. We already have a bunch of integrational tests on XUnit. To see results of our fresh logger we need to integrate LibLog with ITestOutputHelper which provides method to write to test result log.

public sealed class LibLogXUnitLogger : ILogProvider
    {
        private readonly ITestOutputHelper _output;

        public LibLogXUnitLogger(ITestOutputHelper output)
        {
            // based on https://github.com/damianh/LibLog/blob/master/src/LibLog.Example.ColoredConsoleLogProvider/ColoredConsoleLogProvider.cs
            _output = output;
        }

        public Logger GetLogger(string name)
        {
            return (logLevel, messageFunc, exception, formatParameters) =>
            {
                if (messageFunc == null)
                {
                    // verifies logging level is enabled
                    return true;
                }

                string message = string.Format(CultureInfo.InvariantCulture, messageFunc(), formatParameters);
                string record = string.Format(
                    CultureInfo.InvariantCulture,
                    "{0} {1} {2}",
                    DateTime.Now,
                    logLevel,
                    message);

                if (exception != null)
                {
                    record = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}:{1}{2}",
                        record,
                        Environment.NewLine,
                        exception);
                }

                _output.WriteLine(record);
                return true;
            };
        }

        public IDisposable OpenMappedContext(string key, string value)
        {
            return NullDisposable.Instance;
        }

        public IDisposable OpenNestedContext(string message)
        {
            return NullDisposable.Instance;
        }

        private class NullDisposable : IDisposable
        {
            internal static readonly IDisposable Instance = new NullDisposable();

            public void Dispose()
            { }
        }
    }

Actually it was trickier then I thought, without sample it would be painfull.

And one last step:

public sealed class InitPaymentTests
{
    public InitPaymentTests(ITestOutputHelper output)
    {
        LogProvider.SetCurrentLogProvider(new LibLogXUnitLogger(output));
    }
    
    // Tests ...
}

Let’s run couple of tests and see expected profit :).

public sealed class InitPaymentTests
{
        [Theory]
        [InlineData("https://platrondoesnotlivehere.com", "DNS cann't be resolved")]
        [InlineData("http://google.com:3434", "Valid address, but service not available")]
        public async Task InitPayment_PlatronNotAvailableOrNotResolvable_ThrowsServiceNotAvailable(
            string notAvailableUrl, string description)
        {
            var initPayment = new InitPaymentRequest(1.Rur(), "sample description");

            var connection = new Connection(new Uri(notAvailableUrl), new Credentials("0000", "secret"), TimeSpan.FromSeconds(5));
            var client = new PlatronClient(connection);

            await Assert.ThrowsAsync<ServiceNotAvailableApiException>(() => client.InitPaymentAsync(initPayment));
        }
}

XUnit output with LibLog integration

All sources have been published as a part of Platron.Client. Feel free to use it in your projects :).

Related Posts

The perils of Github pages on Jekyll

Bumps and little noisy side-effects learned by a man who just wants to write predictable markdown.

Integration testing on top of NancyFx and ngrok tunnel

We will combine emulation server on NancyFx tunnelled thru ngrok to implement tricky integration test of payment service with callbacks.

Yet another "Hello, World!"

Praise to jekyll, github pages and pixyll as an entry point to blogging.