Message Tracer Library

In the world of software development, logging often takes a backseat to unit testing and documentation. But logging is a powerful tool for debugging in production, and it provides vital data on the real-world use of your application. When things stop working, it’s the data in the logs that both the dev and operations teams use to troubleshoot the issue and quickly fix the problem.

Clearly, logs are important. Unfortunately, not all logs are created equal. You need to follow best practices when creating them so that they contain the right information when you need it. In this article, we will share seven best practices to take your C# logging to the next level.

To trace an incoming web request you then need to create a Tracer object. Make sure you provide all HTTP headers from the request to the SDK by calling AddRequestHeader. This allows both sides of the web requests to be linked together for end-to-end tracing. IIncomingWebRequestTracer tracer = oneAgentSdk. This class is the default Trace Listener that is added to the TraceListenerCollection object of the Trace and Debug classes. The Fail method displays a message box provided that the application is running in user-interface mode. This class is used when we want to see the tracing output in the Visual Studio Environmen.

1. Don’t Reinvent the Wheel

While it’s possible to write your own logging library from scratch, most developers benefit from using a battle-tested option. Fortunately, Microsoft bundles a decent native library called TraceSource with the .NET SDK. To use TraceSource, you need to enable this feature in your configuration file, which is named application.config and located in the same folder as your application executable.

You can enable TraceSource by adding the code below to your application.config file:

Once TraceSource is enabled, use this tool to write messages to the console like this:

2. Write Logs to Files, Not the Console

Logging to the console is useful, but ephemeral. What if you need to run a series of debug tests and compare the results, or maintain an event log? Writing log messages to different locations allows you to route each message to the most appropriate place(s).

TraceSource easily meets these needs. This logging library writes its messages to a listener object which then sends those messages to a predetermined location, such as the console, the Windows Event Log, or a file.

To add a listener that writes to a file, add this code to your configuration:

This snippet adds a listener object called myListener and writes your messages to a log file titled TextWriterOutput.log. Then you can use the Trace class to write text to the file:

It’s also possible to dynamically add listeners at run time. This method is perfect for avoiding unnecessary overhead by only enabling logging when specific code paths are hit, such as the first time an error condition occurs.

The following code example shows how to dynamically add a new listener to the Trace.Listeners collection instead of using the static configuration file:

The .NET framework provides a whole host of listener classes for sending log messages to various destinations in a range of formats. You can check out the TraceListener Class documentation for more details.

3. Use A Third-Party Logging Library

The TraceSource library provided with the .NET SDK is useful in lots of situations, but ultimately, it’s a library focused on tracing and debugging, not logging. Which means that TraceSource is missing the kinds of high-level APIs you’d expect in a standard logging framework.

Message Tracer Library Login

Libraries such as such as Log4Net, Nlog, and serilog can handle the heavy lifting involved with the advanced features of System.Diagnostic.Tracesource so you don’t have to. Which library you choose will depend on your unique needs, though for the most part they are all similar in functionality. For our examples, we’ll use log4net.

Just as with the TraceSource library, you need to configure Log4Net in your app’s configuration file. Here’s a sample configuration that creates a logger that writes to a file and rolls it—moves the current file and creates a new empty one—whenever the file reaches 1MB in size.

The log4net configuration documentation provides more details on each configuration element and its attributes.

With the configuration file written, you can now write messages to myLoggerFile.log using the following code:

Thanks to the layout element in the configuration file, the log messages contain far more context is possible with TraceSource.

This log message provides us with a timestamp, the thread, the logging level, the class that wrote the message, and the log message itself. While not bad, it would be better if we could filter messages so that we only see the ones we’re interested in.

4. Make Use of Logging Levels

One of the most useful features of a logging library like log4net is the ability to ignore messages that are unimportant for a specific scenario. For example, when debugging your application in a pre-production environment you probably want to see all the log messages of DEBUG priority and above. Conversely, you definitely don’t want to see those messages when the application is running in production because they can obscure far more important messages.

log4net offers the following log levels, in increasing order of priority: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF. The ALL level logs everything and the OFF level logs nothing. You can assign these log levels to each logger in your configuration file. Any message sent at a lower priority than the configured level will be ignored.

The advantage here is that you can configure the logging levels directly in your configuration file, and you don’t need to modify your code to change the types of log messages you care about; customizing your logs for production simply requires a separate config file.

To assign an output destination for your messages based on the logging level, create multiple appenders in your config file. For example, this configuration creates two loggers: one that sends all messages at or above INFO to info.log and a second logger that sends messages at or above ERROR to error.log.

Each file is written by a separate logger, so you need to use both inside of your code:

While using more than one logger for small code bases is manageable, things quickly get out of hand as application size grows and as the number of loggers increase. A far more maintainable solution is to add filters.

5. Control Log Messages With Filters

The log4net package provides a number of filters that can finely control which messages are logged by which appenders. Here are a few examples of existing filters:

  • log4net.Filter.DenyAllFilter – deny all messages
  • log4net.Filter.StringMatchFilter – match messages containing a string
  • log4net.Filter.LevelRangeFilter – match messages within a range of log levels

It’s not hard to think of uses for each of these options, and log4net.Filter.StringMatchFilter in particular is useful for sending log messages from specific application modules to a dedicated destination.

But it’s the last filter in the list that we’ll use to improve the config file from the previous section. By filtering messages based on their log level, one logger can send messages to multiple destinations.

The following example configuration sends messages at or above INFO to the info.log file but only messages at ERROR and FATAL to error.log.

Now all messages can be written to the root logger, which will in turn forward those messages to the appenders, and the appenders will either ignore those messages or write them to a file based on the message’s log level.

Message Tracer Library Free

6. Log Contextual and Diagnostic Data

Using the standard contextual items available within the app, such as the timestamp, provides much-needed context to our log message. But what if you need specific data from a class or method? What if you need to know the parameters of a request for your web app? There’s a whole host of details that could be useful, from user location to HTTPRequest parameters.

With frameworks such as log4net, there’s no need to place the burden on developers to add all of those details to each log message—log4net can add them automatically. That’s because log4net provides a class, LogicalThreadContext, which lets you store event-specific data and print it in your log files. To use this, update the conversionPattern element in your configuration file to include the “%property{data} pattern.

For example, the following code stores a user ID in the “user” property which you can print in your log files by adding “%property{user}” to your conversionPattern.

Message Tracer Library App

7. Use Structured Logging

Using the best practices covered so far, it’s now possible to write log messages filled with rich, contextual data. The next and final step is to use structured logging. Logs containing structured data are easily filtered by log management tools such as SolarWinds®Papertrail and SolarWinds Loggly®, which makes it easier to search through your log data when hunting for clues to help diagnose application issues.

Frustration-free log management. (It’s a thing.)

Aggregate, organize, and manage your logs with Papertrail

In particular, log4net offers a JSON package, log4net.Ext.Json, which lets you log any object as JSON by serializing that object. To write JSON to your logs, add log4net.Ext.Json as a serialized layout to your appender.

As a result, writing the following DEBUG message:

Now displays this very parseable log message:

Conclusion

Logging is a powerful tool for both development and production debugging. To ensure your application provides the level of logging necessary for your development and operations teams to track down and fix bugs quickly, follow these steps:

  1. Instead of reinventing the wheel, use an existing logging framework such as TraceSource or log4net.
  2. Use context-rich logging so that you’ll have all the information you might need when troubleshooting.
  3. Make your logs easily parseable by other tools, by writing structured logs.

NLog is a flexible and free logging platform for various .NET platforms, including .NET standard. NLog makes it easy to write to several targets. (database, file, console) and change the logging configuration on-the-fly.

NLog has support for structured and traditional logging.

The focus for NLog: high performance, easy-to-use, easy to extend and flexible to configure.

Free

Easy to configure

NLog is very easy to configure, both through configuration file and programmatically. Even without restarting the application, the configuration can be changed.

Templatable

Every log message can be templated with various layout renders

Extensible

Even though NLog has targets and pre-defined layouts, you can write custom targets or pass custom values

Structured logging

Fully support for structured logging

Targets are used to display, store or pass log messages to another destination. NLog can dynamically write to one of multiple targets for each log message.

There are more than 30 targets provided out-of-the-box, including:

Files

Write logs to any number of files, with automatic file naming and archival. NLog won't lock your files by default.

Database

Store your logs in databases supported by .NET

Console

Write real-time to the command-line console including color coding of messages

E-mail

You can send emails whenever application errors occur

ASP.NET core Logging

Write log messages to the ASP.NET Core Logging

There are also wrapper targets which provide buffering, load balancing, failover situations, asynchronous writing and many more scenarios. The full list of targets is available on the config options page. If you didn't find the target to fit your needs, you can easily write a custom one.

NLog supports the following platforms:
  • .NET 5
  • .NET Framework 3.5, 4, 4.5 - 4.8
  • .NET Framework 4 client profile
  • Xamarin Android
  • Xamarin iOs
  • Windows Phone 8
  • Silverlight 4 and 5
  • Mono 4
  • ASP.NET 4 (NLog.Web package)
  • ASP.NET Core (NLog.Web.AspNetCore package)
  • .NET Core (NLog.Extensions.Logging package)
  • .NET Standard 1.x - NLog 4.5
  • .NET Standard 2.x - NLog 4.5
  • UWP - NLog 4.5

The source code is available under the terms of BSD license.