Click here to Skip to main content
14,490,077 members

Use Trace and TraceSource in .NET Core Logging

Rate this:
5.00 (4 votes)
Please Sign up or sign in to vote.
5.00 (4 votes)
27 Jan 2020CPOL
How to use Trace and TraceSource in .NET Core Logging

Introduction

This article introduces how to use Trace and TraceSource in component design on .NET Core. And this might be useful if:

  1. You have components being built for both .NET Framework and .NET Core / .NET Standard, and you prefer to keep Trace and TraceSource.
  2. You have 3rd party components which use Trace and TraceSource.
  3. You are migrating a complex .NET Framework application to .NET Core, and you don't want to change the tracing and logging design for now.
  4. You would keep tracing and logging separated and simple.

And the targeted readers are those programmers who have rich experiences in .NET Framework programming, and the knowledge discussed here is up to date with .NET Core 3.0 and .NET Framework 2.0 while .NET Framework 4.8 is the last major release of .NET Framework.

Background

In .NET Core, the default tracing and logging has be escalated to ILogger<T>, and the respective logger object is expected to be instantiated through Dependency Injection of .NET Core. ILogger<T> may be comparable with System.Diagnostics.TraceSource, and the attached ILoggerProvider objects may be comparable with System.Diagnostics.TraceListener.

During the programming on .NET Core for a complex business application, I found there are very few articles/posts about using Trace and TraceSource in .NET Core, and almost all articles and examples about ILogger<T> that I could find through Google describe how to use ILogger which are immediately injected in Program, Startup and Controller, while I have been looking for examples or guidelines for using ILogger in components/assemblies far away from the Program project.

Using the Code

The code examples contain multiple projects, each of which represent a simple scenario and a technical solution.

Basic

Code Example: ConsoleApp0

Presumably, you have read Logging in .NET Core and ASP.NET Core. Logging is not included in .NET Core runtime and startup logic, nor in the scaffolding codes of a console app. To use logging, package Microsoft.Extensions.Logging is needed.

However, this package alone is not enough for logging to console. Instead, use Microsoft.Extensions.Logging.Console:

Image 1

And this package includes Microsoft.Extensions.Logging.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

namespace ConsoleApp0
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World! from console");

            ILogger logger;
            IFooService fooService;

            using (var serviceProvider = new ServiceCollection()
                .AddSingleton<IFooService, FooService>()
                .AddLogging(cfg =>
                {
                    cfg.AddConsole();
                })
                .BuildServiceProvider())
            {

                logger = serviceProvider.GetService<ILogger<Program>>();

                fooService = serviceProvider.GetService<IFooService>();
            }

            logger.LogInformation("logger information");
            logger.LogWarning("logger warning");

            fooService.DoWork();
        }
    }

    public interface IFooService
    {
        void DoWork();
    }

    public class FooService : IFooService
    {
        private readonly ILogger logger;

        public FooService(ILogger<FooService> logger)
        {
            this.logger = logger;
        }

        public void DoWork()
        {
            logger.LogInformation("Doing work.");
            logger.LogWarning("Something warning");
            logger.LogCritical("Something critical");
        }
    }
}

Here's the execution:

Image 2

And you can do further configuration in codes through the overloading of AddConsole().

.AddLogging(cfg => { cfg.AddConsole(cfg=> cfg.DisableColors=true); })
.BuildServiceProvider())

Configuration in File

Code Example: ConsoleApp1

You may prefer to configure components and services through an external config file, and it is common to use appsettings.json. Thus, package Microsoft.Extensions.Configuration.Json is needed.

var configuration = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json", false, true)
                    .Build();

ILogger logger;
IFooService fooService;

using (var serviceProvider = new ServiceCollection()// thanks to 
                // https://thecodebuzz.com/logging-in-net-core-console-application/
       .AddSingleton<IFooService, FooService>()
       .AddLogging(cfg => 
       {
           cfg.AddConfiguration(configuration.GetSection("Logging"));
           cfg.AddConsole(); 
       })
       .BuildServiceProvider())
       {
           logger = serviceProvider.GetService<ILogger<Program>>();
           //logger = serviceProvider.GetService<ILoggerFactory>().
                            CreateLogger<Program>(); // Factory first. This works too.

           fooService = serviceProvider.GetService<IFooService>();
       }

And appsettings.json is:

{
    "Logging": {
        "Console": {
            "disableColors": false
        },

        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Information",
            "ConsoleApp1.FooService": "Warning",
            "ConsoleApp1.Program": "Debug"
        }
    }
}

Hints

After reading the Logging section in appsettings.json, through DI .NET Core runtime configures the console logger provider and the loggers such as "Microsoft" and "ConsoleApp1.FooService", etc.

Trace and TraceSource

System.Diagnostics.Trace and System.Diagnostics.TraceSource had been designed for separating tracing and logging, and the logging is realized through attached trace listeners.

Built-in Trace Listeners

Code Example: ConsoleAppTraceListener

Many of the TraceListener derived classes on .NET Framework are available on .NET Core, however, things like IisTraceListener are unavailable on .NET Core.

On .NET Framework, the application could initialize Trace and TraceSource and instantiate trace listeners through app.config which are loaded before the first line of the application codes is executed.

On .NET Core, you may still use various trace listeners like ConsoleTraceListener, however, as .NET Core won't load a config file by default and the built-in configuration does not care about trace listeners.

using (var listener = new TextWriterTraceListener("c:\\temp\\mylog.txt"))
using (var consoleListener = new ConsoleTraceListener())
{
    Trace.Listeners.Add(listener);
    Trace.Listeners.Add(consoleListener);

So you have to instantiate trace listeners and initialize Trace and TraceSources object in the application startup codes. Not too bad, however things move on and there are more and more 3rd party components that may interface with ILogger<T> for tracing and logging. Considering various factors and tradeoffs, it may be better to build a bridge between TraceSource and ILogger<T>, so legacy components that use Trace and TraceSource could send trace messages to ILogger<T>.

LoggerTraceListener

Code Example: ConsoleAppTrace

Since Trace and TraceSource has only Listeners to interface with logging, thus here's LoggerTraceListener to listen to tracing and write to ILogger<T> which eventually sends the tracing to logger providers.

public class LoggerTraceListener : TraceListener
{
    private readonly ILogger logger;

    public LoggerTraceListener(ILogger logger)
    {
        this.logger = logger;
    }

    public override void Write(string message)
    {
        logger.LogInformation(message);
    }

    public override void WriteLine(string message)
    {
        logger.LogInformation(message);
    }

    public override void WriteLine(string message, string category)
    {
        logger.LogInformation(category + ": " + message);
    }

    public override void TraceEvent
           (TraceEventCache eventCache, string source, TraceEventType eventType, int id)
    {
        switch (eventType)
        {
            case TraceEventType.Critical:
                logger.LogCritical(id, source);
                break;
            case TraceEventType.Error:
                logger.LogError(id, source);
                break;
            case TraceEventType.Warning:
                logger.LogWarning(id, source);
                break;
            case TraceEventType.Information:
                logger.LogInformation(id, source);
                break;
            case TraceEventType.Verbose:
                logger.LogTrace(id, source);
                break;
            case TraceEventType.Start:
                logger.LogInformation(id, "Start: " + source);
                break;
            case TraceEventType.Stop:
                logger.LogInformation(id, "Stop: " + source);
                break;
            case TraceEventType.Suspend:
                logger.LogInformation(id, "Suspend: " + source);
                break;
            case TraceEventType.Resume:
                logger.LogInformation(id, "Resume: " + source);
                break;
            case TraceEventType.Transfer:
                logger.LogInformation(id, "Transfer: " + source);
                break;
            default:
                throw new InvalidOperationException("Impossible");
        }
    }

Application startup:

ILogger logger;

using (var serviceProvider = new ServiceCollection()
    .AddLogging(cfg =>
    {
        cfg.AddConfiguration(configuration.GetSection("Logging"));
        cfg.AddConsole();
    })
    .BuildServiceProvider())
{
    logger = serviceProvider.GetService<ILogger<Program>>();
}

logger.LogInformation("logger information");
logger.LogWarning("logger warning");

using (var listener = new LoggerTraceListener(logger))
{
    System.Diagnostics.Trace.Listeners.Add(listener);
    TraceSources.Instance.InitLoggerTraceListener(listener);

    TraceLover.DoSomething();
    TraceSourceLover.DoSomething();
}

Now, what logging mediums that Trace and TraceSource are determined by what logger providers are attached to ILogger.

Trace, TraceSource and Logger Providers in Harmony

Code Example: ConsoleappSeriLog

Microsoft had developed little concrete logger providers in .NET Core, and this is probably by business vision thus by design. There are quite a few 3rd party logger providers around:

  • NLog
  • Log4net
  • Serilog

And there's a very good article comparing these three:

I would agree that Serilog is the best overall.

var configuration = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json", false, true)
                    .Build();

Serilog.Log.Logger = new Serilog.LoggerConfiguration()
                      .Enrich.FromLogContext()
                      //.WriteTo.Console() I prefer plugging through the config file
                      .ReadFrom.Configuration(configuration)
                      .CreateLogger();

Microsoft.Extensions.Logging.ILogger logger;
IFooService fooService;

var services = new ServiceCollection();
services.AddLogging(configure => configure.AddSerilog());

using (var serviceProvider = services
    .AddSingleton<IFooService, FooService>()
    .BuildServiceProvider())
{
    logger = serviceProvider.GetService<ILogger<Program>>();
    fooService = serviceProvider.GetService<IFooService>();
}

try
{
    Log.Information("Starting up");
    logger.LogInformation("logger information");
    logger.LogWarning("logger warning");

    fooService.DoWork();

    using (var listener = new LoggerTraceListener(logger))
    {
        System.Diagnostics.Trace.Listeners.Add(listener);
        TraceSources.Instance.InitLoggerTraceListener(listener);

        TraceLover.DoSomething();
        TraceSourceLover.DoSomething();
    }
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application start-up failed");
}
finally
{
    Log.CloseAndFlush();
}

And the appsettings.json is:

{
    "TraceSource": {
        "WebApi": {
            "SourceLevels": "Information"
        },

        "HouseKeeping": { "SourceLevels": "Warning" },

        "DbAudit": {
            "SourceLevels": "Warning"
        }
    },

    "Serilog": {
        "MinimumLevel": {
            "Default": "Debug",
            "Override": {
                "Microsoft": "Information",
                "System": "Warning",
                "ConsoleApp1.FooService": "Warning",
                "ConsoleApp1.Program": "Information"
            }
        },

        "WriteTo": [
            {
                "Name": "Console"
            },

            {
                "Name": "File",
                "Args": {
                    "path": "%PROGRAMDATA%/my/logs/CloudPosApi_Test.log",
                    "outputTemplate": "{Timestamp:MM-dd HH:mm:ss.fff zzz} 
                           [{Level}] {ThreadId} {Message}{NewLine}{Exception}",
                    "rollingInterval": "Day"
                }
            }
        ]
    }
}

Points of Interest

On .NET Framework, the runtime will load app.config and apply settings to some built-in components before the first line of the application codes is executed. And some other components like SMTPClient and System.Diagnostics components will read app.config by default.

On .NET Core, it is the application programmer's responsibility to configure either in codes, or through loading a config file.

Even though I have been writing components for .NET Core for a few years, I had just started developing complex .NET Core business applications for a few months and I am far from being proficient .NET Core programming. Please point out some mistakes that I might have made in this article. And in the code example, there's an example about TraceSourceLoggerProvider, not yet working. Amazingly, there is apparently zero example of TraceSourceLoggerProvider among 346 Google search results (actually less than 60) as of 2020-01-08. I have a question, do you know how to use TraceSourceLoggerProvider?

History

  • 27th January, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Zijian
Software Developer
Australia Australia
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.

Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.

Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.

Comments and Discussions

 
-- There are no messages in this forum --
Article
Posted 27 Jan 2020

Stats

4.1K views
9 bookmarked