Recently, I needed a simple way to redirect all Log4Net messages, that an application logs, to be redirected as ETW events. Now, usually that is not what you want to do. ETW events should be typed and specific. However, simply sending all log messages of an application as ETW events also has some merit. For example, you can correlate them with other events (like memory allocations, file I/O, .NET CLR events, etc.) that an application produces to get a better understanding of certain issues.
There are lots of tutorials and examples on the internet that explain what ETW is, how it works, how you use it and how you can view events and other data produced by it. Hence, here I will only show the implementation of the appender itself.
First we need a class that represents the event source (you may want to use a better name, here we go wit “MyApp-Logging”):
using System.Diagnostics.Tracing;
namespace EtwSample
{
[EventSource(Name = "MyApp-Logging")]
internal class LoggingEventSource : EventSource
{
public class Keywords
{
public const EventKeywords FormattedMessage = (EventKeywords)1;
}
internal static LoggingEventSource Instance = new LoggingEventSource();
[Event(1, Keywords = Keywords.FormattedMessage, Level = EventLevel.LogAlways)]
internal void FormattedMessage(string Level, string AppDomain, string LoggerName, string FormattedMessage, string ExceptionInfo)
{
WriteEvent(1, AppDomain, Level, LoggerName, FormattedMessage, ExceptionInfo);
}
}
}
Then, we need the actual appender implementation, that makes use of this event source:
using log4net.Appender;
using log4net.Core;
namespace EtwSample
{
public class EtwAppender : AppenderSkeleton
{
protected override void Append(LoggingEvent loggingEvent)
{
if (loggingEvent.Level == Level.Off)
{
// This should not really happen.
return;
}
var checkLevel = EventLevel.Verbose;
if (loggingEvent.Level >= Level.Critical)
{
checkLevel = EventLevel.Critical;
}
else if (loggingEvent.Level >= Level.Error)
{
checkLevel = EventLevel.Error;
}
else if (loggingEvent.Level == Level.Warn)
{
checkLevel = EventLevel.Warning;
}
else if (loggingEvent.Level < Level.Warn && loggingEvent.Level > Level.Debug)
{
checkLevel = EventLevel.Informational;
}
else if (loggingEvent.Level <= Level.Debug)
{
checkLevel = EventLevel.Verbose;
}
if (LoggingEventSource.Instance.IsEnabled(checkLevel, LoggingEventSource.Keywords.FormattedMessage))
{
// "null" event arguments cause "<EventSource>/WriteEventString" events themselves, so
// make sure that this is not the case here.
// Note that the ExceptionObject will only by filled if the caller did use one of the
// Log*() overloads that have a separate Exception parameter.
var exceptionInfo = loggingEvent.ExceptionObject?.ToString() ?? "";
LoggingEventSource.Instance.FormattedMessage(
loggingEvent.Level.Name,
loggingEvent.Domain,
loggingEvent.LoggerName,
loggingEvent.RenderedMessage,
exceptionInfo);
}
}
}
}
As you can see, the most work is actually mapping the Log4Net levels to the respective ETW event levels and issuing the events based on the information of the logging event.
Use this is a starting point for more involved events.