-
Notifications
You must be signed in to change notification settings - Fork 204
Common.Logging.ETWLogger
Common.Logging.ETWLogger
provides an adapter for connecting Common.Logging to the Event-Tracing for Windows logging subsystem that is available on later editions of the Windows Operating System (both Servers and Clients). See MSDN for more information on Event-Tracing for Windows.
- Add the
Common.Logging.ETWLogger
NuGet package to your project. - To configure the logger in code, simply create an instance of the
Common.Logging.ETW.ETWLoggerAdapter
and call the.GetLogger(...)
method on the adapter as follows:
var adapter = new ETWLoggerAdapter();
var logger = adapter.GetLogger(typeof(MyClass));
logger.Warn("Message to Log here!");
- To Configure the logger via e.g.,
app.config
,web.config
files, add the relevant sections to your.config
file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.ETW.ETWLoggerFactoryAdapter, Common.Logging.ETWLogger">
</factoryAdapter>
</logging>
</common>
</configuration>
Common.Logging.ETWLogger
offers several configuration options to control its behavior as follows:
Common.Logging.ETWLogger
makes use of the Microsoft.Diagnostics.Tracing.EventSource
NuGet package to expose an API for managed (.NET) code to communicate with the underlying native Event-Tracing for Windows (ETW) subsystem. Among other things, this library provides an EventSource
super-class from which developers must derive their own sub-class to communicate with ETW. Common.Logging.ETWLogger
provides a default sub-class derived from this EventSource
super-class and it is intended to meet the needs of most adopters of Common.Logging.ETWLogger
.
For more fine-grained control over the behavior of Common.Logging.ETWLogger
, developers can derive their own sub-class from the EventSource
super-class and configure Common.Logging.ETWLogger
to use your own custom type instead.
The Common.Logging.ETW.ETWLoggerAdapter
exposes a property that can be configured to contain an instance of your custom EventSource
-derived sub-class in code as follows:
var adapter = new ETWLoggerAdapter();
adapter.ETWEventSource = new MyCustomDerivedEventSource();
var logger = adapter.GetLogger(typeof(MyClass));
logger.Warn("Message to Log here!");
This can be configured in e.g., app.config
, web.config
files as follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.ETW.ETWLoggerFactoryAdapter, Common.Logging.ETWLogger">
<arg key='commonLoggingEventSourceType' value='MyNamespace.MyCustomDerivedEventSource,MyAssemblyContainingTheCustomEventSource'/>
</factoryAdapter>
</logging>
</common>
</configuration>
In order for Common.Logging.ETWLogger
to properly interact with your custom EventSource
-derived sub-class, the following requirements must be satisfied:
- Your custom sub-class must derive from
Microsoft.Diagnostics.Tracing.EventSource
. Note: theMicrosoft.Diagnostics.Tracing.EventSource
package also places a number of other restrictions on types derived fromEventSource
and of course your own sub-class must satisfy all of these as well. There are two MS-WORD documents contained in theMicrosoft.Diagnostics.Tracing.EventSource
package that cover these additional requirements in full detail. - Your custom sub-class must implement the
Common.Logging.ETW.ICommonLoggingEventSource
interface. This is aCommon.Logging
-specific interface thatCommon.Logging.ETWLogger
expects any customEventSource
-derived type to fully-implement. For more detail, see the full interface definition and refer to the default implementation of this interface provided withCommon.Logging.ETWLogger
as an example of this. - If you intend the
ETWLoggerAdapter
to be configured to use your custom sub-class via e.g.,app.config
,web.config
, your custom sub-class must expose an empty/zero-arg constructor. At run-time, theETWLoggerAdapter
will use this ctor to instantiate an instance of your custom sub-class from the type declaration in the.config
file provided in thecommonLoggingEventSourceType
argument. If this empty/zero-arg ctor isn't present,Common.Logging.ETWLogger
will throw an exception. Note: if you intend to only configure theETWLoggerAdapter
via code (by setting the property on theETWLoggerAdapter
directly), then this empty/zero-arg ctor is not required.
The minimum logging level for Common.Logging.ETWLogger
can be controlled in code. The ETWLoggerAdapter
exposes a .LogLevel
property to control this behavior:
var adapter = new ETWLoggerAdapter();
adapter.LogLevel = LogLevel.Error;
var logger = adapter.GetLogger(typeof(MyClass));
logger.Warn("Message to Log here!");
This can also be controlled in e.g., app.config
, web.config
files as follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.ETW.ETWLoggerFactoryAdapter, Common.Logging.ETWLogger">
<arg key='level' value='error'/>
</factoryAdapter>
</logging>
</common>
</configuration>
Note that the LogLevel
setting is considered a minimum setting, with greater logging levels implicitly included. Consider the following example:
-
INFO
is a lowerLogLevel
thanERROR
- setting the
LogLevel
toERROR
will log allERROR
events but notINFO
events - setting the
LogLevel
toINFO
will log allINFO
events but also allERROR
events
The known limitations of Common.Logging.ETWLogger
all stem from several of the inherent limitations of the Microsoft.Diagnostics.Tracing.EventSource
package upon which Common.Logging.ETWLogger
depends for its ability to communicate with the native ETW subsystem.
One of the consequences of using the Microsoft.Diagnostics.Tracing.EventSource
package as the basis for communicating with the ETW subsystem is that all logging will be reported to ETW in events of the form EventSourceDerivedTypeName/LoggingMethodName
. The Microsoft.Diagnostics.Tracing.EventSource
package supports using an attribute to override the EventSourceDerivedTypeName
portion of the event name to be reported, and this is the approach used in the default EventSource
-derived type provided by Common.Logging.ETWLogger
.
As you can see here, the name of the EventSource
that will appear in ETW logs when using this default class has been set to Common.Logging.ETWLogger
. When combined with the methodnames in that same type, events reported to ETW will appear in e.g., the following form:
- Common.Logging.ETWLogger/Warn ...
- Common.Logging.ETWLogger/Info ...
Because the name that is reported to ETW by Microsoft.Diagnostics.Tracing.EventSource
is a combination of the name of the EventSource
-derived type (or the value of the attribute) and the method name invoked in the EventSource
-derived type, there is no opportunity for Common.Logging.ETWLogger
to influence these values at runtime. This means that all events reported to ETW from any project using Common.Logging
(and the default EventSource
-derived type) will have the same event names.
If you desire to have different/unique event names reported to ETW (for your project or even different subsystems of your project), the only mechanism for developers to influence these values is to provide your own custom EventSource
-derived type and "switch them out" as needed using the techniques noted in the earlier Advanced Configuration section.
One of the limitations of using the Microsoft.Diagnostics.Tracing.EventSource
package as the basis for communicating with the ETW subsystem is that in this form ETW can only accept primitive types (e.g., int
, string
) and DateTime
values to record.
In the case of logging simple text messages (e.g., diagnostic scenarios), this isn't an issue since we can easily pass a string
to ETW to record. However, in the case of recording an Exception
, we cannot pass the Exception object through to ETW to record. To mitigate this as best we can within the constraints of the Microsoft.Diagnostics.Tracing.EventSource
package, Common.Logging.ETWLogger
will report the Exception
in its string
representation by first calling .ToString()
on the logged Exception
and then reporting it to ETW.
This will result in a recording of the type of the Exception
being logged and its .Message
property, but may not preserve fully-detailed stack-traces in all cases (due to the inherent limitations of the default implementation of the .Tostring()
method in the .NET System.Exception
class). If you desire more detailed logging of exceptions in ETW, you're only recourse is to throw your own Exceptions, overriding the .ToString()
method to provide the detail you're seeking.
Another limitation of the Microsoft.Diagnostics.Tracing.EventSource
package is that all logging methods in the EventSource
-derived type must have unique names -- overloaded methods (those with identical methodnames but whose param signature differs) are not permitted. This means that while the Common.Logging.ILog
interface can offer methods like the following:
logger.Warn(string message)
logger.Warn(string message, Exception exception)
...such overloads cannot be reproduceed in EventSource
-derived types (since the methodname is a component of the reported event name in ETW, the name of each method that logs to ETW must be unique -- regardless of param signature).
To mitigate this limitation, Common.Logging.ETWLogger
must use two different methods in its EventSource
-derived type to handle logging with and without an Exception
. This strategy is illustrated in the definition of the ICommonLoggingEventSource
interface that offers e.g., the following typical duality of method to handle this situation:
.Warn(string message)
.WarnException(string message, string exception)
Note that unless you are implementing your own EventSource
-derived type, much of this complexity is hidden from the consumer of Common.Logging.ETWLogger
because you would (typically) code against the Common.Logging.ILog
interface which of course continues to expose a richer set of overloaded method signatures e.g.,
ILog.Warn(string message)
ILog.Warn(string message, Exception exception)