.NET logging and tracing
Code can be instrumented to produce a log, which serves as a record of interesting events that occurred while the program was running. To understand the application's behavior, logs are reviewed. Logging and tracing both encapsulate this technique. .NET has accumulated several different logging APIs over its history and this article will help you understand what options are available.
Key distinctions in logging APIs
Logging APIs can either be structured or unstructured:
- Unstructured: Log entries have free-form text content that is intended to be viewed by humans.
- Structured: Log entries have a well defined schema and can be encoded in different binary and textual formats. These logs are designed to be machine translatable and queryable so that both humans and automated systems can work with them easily.
APIs that support structured logging are preferable for non-trivial usage. They offer more functionality, flexibility, and performance with little difference in usability.
For simple use cases, you might want to use APIs that directly write messages to the console or a file. But most software projects will find it useful to configure which log events get recorded and how they are persisted. For example, when running in a local dev environment, you might want to output plain text to the console for easy readability. Then when the application is deployed to a production environment, you might switch to have the logs stored in a dedicated database or a set of rolling files. APIs with good configuration options will make these transitions easy, whereas less configurable options would require updating the instrumentation code everywhere to make changes.
Most logging APIs allow log messages to be sent to different destinations called sinks. Some APIs have a large number of pre-made sinks, whereas others only have a few. If no pre-made sink exists, there's usually an extensibility API that will let you author a custom sink, although this requires writing a bit more code.
.NET logging APIs
For most cases, whether adding logging to an existing project or creating a new project, the ILogger interface is a good default choice.
ILogger supports fast structured logging, flexible configuration, and a collection of common sinks. Additionally, the
ILogger interface can also serve as a facade over many third party logging implementations that offer more functionality and extensibility.
EventSource is an older high performance structured logging API. It was originally designed to integrate well with Event Tracing for Windows (ETW), but was later extended to support EventPipe cross-platform tracing and EventListener for custom sinks. In comparison to
EventSource has relatively few pre-made logging sinks and there is no built-in support to configure via separate configuration files.
EventSource is excellent if you want tighter control over ETW or EventPipe integration, but for general purpose logging,
ILogger is more flexible and easier to use.
System.Diagnostics.Trace and System.Diagnostics.Debug are .NET's oldest logging APIs. These classes have flexible configuration APIs and a large ecosystem of sinks, but only support unstructured logging. On .NET Framework they can be configured via an app.config file, but in .NET Core, there's no built-in, file-based configuration mechanism. The .NET team continues to support these APIs for backward-compatibility purposes, but no new functionality will be added. These APIs are a fine choice for applications that are already using them. For newer apps that haven't already committed to a logging API,
ILogger may offer better functionality.
Specialized logging APIs
The System.Console class has the Write and WriteLine methods that can be used in simple logging scenarios. These APIs are very easy to get started with but the solution won't be as flexible as a general purpose logging API. Console only allows unstructured logging and there is no configuration support to select which log messages are enabled or to retarget to a different sink. Using the ILogger or Trace APIs with a console sink doesn't take much additional effort and keeps the logging configurable.
System.Diagnostics.DiagnosticSource is intended for logging where the log messages will be analyzed synchronously in-process rather than serialized to any storage. This allows the source and listener to exchange arbitrary .NET objects as messages, whereas most logging APIs require the log event to be serializable. This technique can also be extremely fast, handling log events in tens of nanoseconds if the listener is implemented efficiently. Tools that use these APIs often act more like in-process profilers, though the API doesn't impose any constraint here.
System.Diagnostics.EventLog is a Windows only API that writes messages to the Windows EventLog. In many cases using ILogger with an optional EventLog sink when running on Windows may give similar functionality without coupling the app tightly to the Windows OS.
Submit and view feedback for