How to work with trace listeners in ASP.NET Core 6

When working on applications built using ASP.NET Core 6, you might often want to use tracing and logging to monitor your application’s performance and to diagnose errors. You can also use tracing in a production environment to measure how your application is performing at run time.

This article discusses how we can use tracing in ASP.NET Core 6. We will examine how to use trace listeners to collect trace messages and direct the trace output to an event log using ILogger.

To work with the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.

Create an ASP.NET Core Web API project in Visual Studio 2022

First off, let’s create an ASP.NET Core project in Visual Studio 2022. Following these steps will create a new ASP.NET Core 6 Web API project in Visual Studio 2022:

  1. Launch the Visual Studio 2022 IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  7. Click Next.
  8. In the “Additional Information” window shown next, ensure that the checkbox that says “Use controllers…” is checked since we’ll not be using minimal APIs in this example. Leave the “Authentication Type” as “None” (default).
  9. Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Open API Support” are unchecked as we won’t be using any of those features here.
  10. Click Create.

We’ll use this ASP.NET Core 6 Web API project to work with trace listeners in the subsequent sections of this article.

What is tracing?

Compared to event logging, which tracks major events, tracing allows for a much more complete view of the running application and its components. Logs comprise structured or unstructured time stamped data that shows a record of the events that occur in your application. Tracing provides much more visibility into the individual request and how it is processed.

The System.Diagnostics namespace contains the Trace and the Debug classes. While the Trace class is used in production environments, the Debug class is used at development time.

Tracing typically involves the following three phases:

  • Instrumentation: We write the necessary code to capture relevant information
  • Tracing: We write the trace messages to a specified target, i.e., an event log, a text file, a database table, etc.
  • Analysis: We analyze the information gathered from traces to determine the bottlenecks in the application.

What are trace listeners? Why are they needed?

Trace listeners collect trace messages, store them, and direct them to an appropriate target such as a text file. .NET provides several trace listeners including the following:

  • ConsoleTraceListener – sends trace messages to the console window.
  • DefaultTraceListener – sends trace messages to standard debug output.
  • DelimitedListTraceListener – sends trace output in a delimited format to a stream, a stream writer, or a text writer.
  • EventLogTraceListener – sends trace messages to event logs.
  • TextWriterTraceListener – sends trace messages to a text file.
  • XmlWriterTraceListener – converts trace messages to XML.

The System.Diagnostics.Debug and System.Diagnostics.Trace classes can send messages to trace listeners, which in turn route the messages to an appropriate target.

Create a trace listener using a config file in ASP.NET Core 6

You can create a trace listener either by using a config file or by writing custom code. The code snippet shown below illustrates how to create a trace listener using your application configuration file.

<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="MyFirstListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="TraceOutput.txt" />
<remove name="Default" />
</listeners>
</trace>
</system.diagnostics>
</configuration>

All listeners added to the Listeners collection will receive trace output. However, you can use a listener without adding it to the Listeners collection. In this case, you send output using the Write or WriteLine method within the listener.

The following code illustrates a listener that is not added to the Listeners collection but is still capable of sending trace messages to an output window, a file, or any pre-configured output.

TextWriterTraceListener myFirstListener = new
TextWriterTraceListener("Output.txt", "myFirstListener");
myFirstListener.WriteLine("This is a test message.");
myFirstListener.Flush();

Create a custom trace listener in ASP.NET Core 6

The trace listeners that come with .NET 6 by default will meet your requirements in most cases. However, if you want to output your trace messages to a different destination, you can implement your own trace listener.

To build a custom trace listener, you should create a class that extends the TraceListener abstract class. There are several virtual and abstract methods in the TraceListener class. You should at least implement the Write and the WriteLine methods. At a bare minimum, your custom trace listener should look like this:

public class CustomTraceListener : TraceListener
{
public CustomTraceListener(ILoggerFactory loggerFactory)
{
}
public override void Write(string? message, string? category)
{
}
public override void Write(string? message)
{
}
public override void WriteLine(string? message)
{
}
}

So, your custom trace listener class must have an argument constructor and the Write and WriteLine methods.

You will also need an ILogger instance that represents the logger, a logger factory to create the logger, and a StringBuilder to store the trace messages before they are sent to the log target. 

private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _iLogger;
private readonly StringBuilder _stringBuilder = new();

You can take advantage of dependency injection to inject an instance of ILoggerFactory in the constructor and then use the instance to create an instance of ILogger.

public CustomTraceListener(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
_iLogger = loggerFactory.CreateLogger(nameof(CustomTraceListener));
}

Here is a minimal implementation of the Write and the WriteLine methods:

public override void Write(string? message, string? category)
{
_stringBuilder.Append(message + "-" + category);
}
public override void Write(string? message)
{
_stringBuilder.Append(message);
}
public override void WriteLine(string? message)
{
_stringBuilder.AppendLine(message);
_iLogger.LogInformation(_stringBuilder.ToString());
_stringBuilder.Clear();
}

Complete custom trace listener example in ASP.NET Core 6

Below is the complete source code of our minimal implementation of a custom trace listener for your reference.

using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
namespace TraceListenerDemo
{
public class CustomTraceListener : TraceListener
{
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _iLogger;
private readonly StringBuilder _stringBuilder = new();
public CustomTraceListener(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
_iLogger =
loggerFactory.CreateLogger(nameof(CustomTraceListener));
}
public override void Write(string? message, string? category)
{
_stringBuilder.Append(message + "-" + category);
}
public override void Write(string? message)
{
_stringBuilder.Append(message);
}
public override void WriteLine(string? message)
{
_stringBuilder.AppendLine(message);
_iLogger.LogInformation(_stringBuilder.ToString());
_stringBuilder.Clear();
}
}
}

Register the custom trace listener in the Program.cs file

To use the custom trace listener, you should register it with the Listeners collection using the following code.

var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
Trace.Listeners.Add(new LoggerTraceListener(loggerFactory));

Because our custom trace listener has been added to the listeners collection, it will capture all trace messages generated by the runtime and send the output to our logger. It will also send any trace messages that we send explicitly in the application (like we did in the myFirstListener example earlier).

So, any listener added to the Listeners collection can capture the traces generated by the runtime as well as any trace messages sent explicitly in the application. However, if a trace listener is not added to the collection, it can only send trace messages sent explicitly in the application. It will not capture any trace messages generated by the runtime.

When working with custom trace listeners, you must remember to close or flush the trace listener to ensure that the output buffer is emptied. You can take advantage of the StringBuilderCache class to optimize your code (in the CustomTraceListener class) that uses StringBuilder.

Leave a Reply

Your email address will not be published.