LogMagic: Unique Logging Library for .NET

As a creator of LogMagic I’ve been watching the progress of Serilog for quite some time and switched to using it personally, therefore this project will not be maintained actively anymore. Contibuting to open-source is a tedious task to do in your own time for no financial benefit, and logging is a really hard problem to solve. Therefore in future my contibution to logging will be only in a form of contributing to the awesome Serilog project.

Today (year 2021) I realise that the library had very unique capabilities, probably ahed of it’s time, and it would be great to continue working on it. However, I’m moving on and don’t want to spend a lot of time on .NET development.

LogMagic is a field-tested .NET Framework and .NET Core library that helps with logging (including Structured logging).

Use it to

To extend functionality, a healthy set of Writers and Enrichers in the form of NuGet packages are maintained along side the core library. Hence the core library has been designed and architected to be easily extensible.

Why LogMagic

It’s probably the easiest framework to setup, has a clean API and is extremely extensible.

Index

Installation

Installing from NuGet

The core logging package is LogMagic. Supported frameworks are:

Note to ASP.NET Core users: LogMagic does not integrate with ASP.NET Core logging system, it’s a separate logging framework you can use in conjunction with or instead of it. Both system have their pros and cons.

PM> Install-Package LogMagic

Setup

Types are in LogMagic namespace

using LogMagic;

An ILog instance is the one used to log events and can be created in one of the following ways by calling to global static L class.

Create by specifying type explicitly

ILog _log = L.G<T>;			//using generics
ILog _log = L.G(typeof(T));	//passing type

Or by specifying name explicitly

ILog _log = L.G("instance name");

LogMagic needs to be configured with at least one Writer, otherwise it won’t write to anywhere:

L.Config.WriteTo.Console();

This is typically done once at application startup.

Example application

The complete example below shows logging in a simple console application, with events sent to the console.

  1. Create a new Console Application project
  2. Install the core LogMagic package

In Visual Studio, open Package Manager Console and type:

Install-Package LogMagic
  1. Add the following code to Program.cs
using System;
using LogMagic;

namespace LogMagicExample
{
   public class Program
   {
      private readonly ILog _log = L.G<Program>();

      public static void Main(string[] args)
      {
         L.Config
            .WriteTo.Console()
            .EnrichWith.ThreadId();

         new Program().Run();

         Console.ReadLine();
      }

      private void Run()
      {
         _log.Trace("hello, LogMagic!");

         int a = 10, b = 0;

         try
         {
            _log.Trace("dividing {a} by {b}", a, b);
            Console.WriteLine(a / b);
         }
         catch(Exception ex)
         {
            _log.Trace("unexpected error when attempting to divide {a} by {b}", a, b, ex);
         }

         _log.Trace("bye, LogMagic!");
      }

   }
}
  1. Run the program

Example output

Logging Exceptions

LogMagic always checks the last parameter of Trace() arguments whether it’s an exception class and uses that to include additional exception message. Note that you can have other parameters before the exception which can be used to format the message, as shown in the example application above.

Configuration Basics

LogMagic uses C# API to configure logging.

Log writers

Log event writers generally record log events to some external representation, typically console, file, or external data store. LogMagic has a few built-in writers to get you started. More writers are redistributed via NuGet.

A curated list of available packages are listed below on this page.

Writers are configure using WriteTo congiguration object.

L.Config.WriteTo.PoshConsole();

Multiple writers can be active at the same time.

L.Config
	.WriteTo.PoshConsole()
	.WriteTo.Trace();

Formatting

Text-based writers support message formatting. Whenever a format parameter appears in writer configuration you can specify your own one:

L.Config
	.EnrichWith.ThreadId()
	.WriteTo.Console("{time:H:mm:ss,fff}|{threadId}{level,-7}|{source}|{message}{error}");

Enriched properties can also appear in the output. In the example above a threadId comes from thread ID ennricher.

Formatting syntax

Built-in property names:

All of the properties support standard .NET formatting used in string.Format().

Enrichers

Enrichers are simple components that add properties to a log event. This can be used for the purpose of attaching a thread id, machine IP address etc. for example.

L.Config.EnrichWith.ThreadId();

Context Information

Sometimes it’s useful to add context information to a logging session during a call duration scope. LogMagic achieves it by dynamically adding and removing propeties from the ambient “execution context”. For example, all messages during a transaction might carry the id of that transaction, and so on.

The feature does not need any special configuration, and properties can be added an removed using L.Context():

log.Trace("no properties");

using(L.Context("A", "id1"))
{
	log.Trace("carries property A=id1");

	using(L.Context("B", "id2"))
	{
		log.Trace("carries A=id1 and B=id2");
	}

	log.Trace("carries property A=id1");

	using(L.Context("A", "id3"))
	{
		log.Trace("carries property A=id3");
	}

	log.Trace("carries property A=id1");
}

log.Trace("no properties");

Pushing property onto the context will override any existing properties with the same name, until the object returned from L.Context() is disposed, as the property A in the example demonstrates.

L.Context() accepts multiple properties at once if you need to:

using(L.Context("A", "id1",  "B", "id2"))
{
	//...
}

Logging context is used extensively by LogMagic plugins to carry execution context information across the application. For instance, web applications need to known which request the code is related to etc.

Important: properties must be popped from the context in the precise order in which they were added. Behavior otherwise is undefined.

You can also get context property value by name at any time in any place in your code by calling to L.GetContextValue(propertyName) which returns null if property doesn’t exist.

Important: Log context is not available when you target for .NET Framework 4.5 due to the reason that this version doesn’t have any options to track execution. If you are still using .NET 4.5 or earlier it’s time to upgrade to .NET 4.6.

Writing log events

Log events are written to writers using the ILog interface. Typically you will instantiate it on top of your class definition you want to log from.

private readonly ILog _log = L.G<Class>();

// ...

_log.Trace("application v{version} started on {date}", "1.0", DateTime.UtcNow);

Message template syntax

the string above "application v{version} started on {date}" is a message template. Message templates are superset of standard .NET format string, so any format string acceptable to string.Format() will also be correctly processed by LogMagic.

Log event levels

LogMagic doesn’t have the classic logging levels (i.e. debug, info, warn etc.) as this is proven to be rarely used. Instead you only need one single Trace() method. Due to the fact that structured logging is supported and promoted there is no need to have logging levels as you can always filter based on a custom property if you ever need to.

Tracking performance

You can track system or process performance by using the performance counter feature from LogMagic. This feature essentially emits a certain value on schedule, like average memory usage, average CPU usage etc. To configure a performance counter you would use CollecPerformanceCounter sentence in the configuration like so:

L.Config
   .WriteTo.PoshConsole()
   .CollectPerformanceCounters.WindowsCounter("Machine CPU Load (%)", "Processor", "% Processor Time", "_Total");

In this case LogMagic will periodicall (every 10 seconds) collect windows performance counter value and emit a log event.

If you don’t know which counters you need you can always use .CollectPerformanceCounters.PlatformDefault() which on Windows will add the following counters to the collection list:

where processName is current executing process name.

Performance counters in this version of LogMagic only work on Windows and only if you are using Desktop .NET framework or .NET Standard 2.0 and higher. In other cases performance counters are ignored. However, we are actively trying to support more platforms.

Known Writers and Enrichers

Name Description
System Console Simplest logger that outputs events to the system console.
Posh Console Simplest logger that outputs events to the system console, but also supports colorisation.
System Trace Writes to the system trace.
File on disk A really simple writer that outputs to a file on disk.
Azure Application Insights Emits telemetry into Azure Application Insights.
Azure Service Fabric Integrates with Azure Service Fabric by building correlating proxies, enrichers, and emitting cluster health events
ASP.NET Core Provides a custom middleware that automatically logs requests.
Azure Functions v2 Integrates with Azure Functions v2 Runtime.

Built-in enrichers

Writer Syntax Meaning
.Constant() adds a constant value to every log event
.MachineIp() current machine IP address
.MachineName() current machine DNS name
.MethodName() caller’s method name
.ThreadId() managed thread id

Note that external packages may add more enrichers which are not listed here but documented on specific package page.

Async Helpers

Often tracking dependencies involves measuring time and catching exception whcih is a bit tedious.

Outro

Open-Source, archived on GitHub (archived). Archived source code download.