RJCP.Diagnostics.Trace 0.2.1

dotnet add package RJCP.Diagnostics.Trace --version 0.2.1
NuGet\Install-Package RJCP.Diagnostics.Trace -Version 0.2.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RJCP.Diagnostics.Trace" Version="0.2.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add RJCP.Diagnostics.Trace --version 0.2.1
#r "nuget: RJCP.Diagnostics.Trace, 0.2.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install RJCP.Diagnostics.Trace as a Cake Addin
#addin nuget:?package=RJCP.Diagnostics.Trace&version=0.2.1

// Install RJCP.Diagnostics.Trace as a Cake Tool
#tool nuget:?package=RJCP.Diagnostics.Trace&version=0.2.1

RJCP.Diagnostics.Trace

This assembly is to assist with logging be extending common operations on top of System.Diagnostic.

Projects using this library allow for easy compatibility between .NET Framework and .NET Core when porting, and allow easy reconfiguration of the trace sources.

1. Motivation

1.1. Intended Use Cases

The software I develop is primarily console applications, windows forms applications, and reusable libraries. This library is intended primarily for these scenarios.

1.2. .NET Framework works with .NET Core

This library is intended for usage in your own .NET Framework and .NET Standard libraries.

With .NET 2.0 to .NET 4.8, the .NET Framework offered the TraceListener and TraceSource which read a configuration file and provided a static singleton to obtain logging. With the advent of .NET Core, this was replaced with the ILoggerFactory and ILogger using dependency injection, making it for library designers and console applications more complicated. Further, compatibility was lost with .NET Core that no longer would properly instantiate a TraceListener from the application config.

This library provides a LogSource class, that when your library uses, will receive a TraceListener. The TraceListener is the lowest common denominator for both .NET Framework and .NET Core.

Users of .NET Standard can then use the LogSource.SetLoggerFactory or LogSource.SetLogger with a category name that the library can use to get the user desired logging, resulting in common code for both frameworks, reducing fragmentation.

2. Examples of Usage

2.1. .NET Framework and .NET Core Code

Your code should get the TraceListener from the LogSource.

var logger = new LogSource("MyCategory");
logger.TraceEvent(TraceEventType.Information, "Log Message");

2.2. .NET Framework Client

There is nothing in particular that needs to be done, other than to define the trace listener in the app.config file.

<configuration>
  <system.diagnostics>
    <sources>
      <source name="MyCategory" switchValue="Information">
        <listeners>
          <clear/>
          <add name="consoleListener"/>
        </listeners>
      </source>
    </sources>

    <sharedListeners>
      <add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener" />
    </sharedListeners>
  </system.diagnostics>
</configuration>

2.3. .NET Core Client

Because .NET Core doesn't read the configuration file, the TraceListener will only ever instantiate the DefaultTraceListener and logging won't work. We need to create an ILoggerFactory. The easiest way to do this in a console application:

internal static ILoggerFactory GetConsoleFactory()
{
    return LoggerFactory.Create(builder => {
        builder
            .AddFilter("MyCategory", LogLevel.Debug)
            .AddConsole();
    });
}

And then assign it:

LogSource.SetLoggerFactory(GetConsoleFactory());
2.3.1. Reading from a Configuration File

The previous section can be modified slightly to read from a configuration file:

internal static ILoggerFactory GetConsoleFactory()
{
    IConfigurationRoot config = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", true, false)
        .Build();

    return LoggerFactory.Create(builder => {
        builder
            .AddConfiguration(config.GetSection("Logging"))
            .AddConsole();
    });
}

And then assign it:

LogSource.SetLoggerFactory(GetConsoleFactory());
2.3.2. Unit Testing with NUnit 3.x

Your unit tests may want to provide its own logging levels independent of the object being tested. If your .NET core initializes and sets the factory similar to the previous section, all code will use that factory for logging.

In .NET Core NUnit projects, you could define a file called TestSetupFixture.cs that is similar to:

namespace App {
  using Microsoft.Extensions.Logging;
  using NUnit.Framework;
  using RJCP.CodeQuality.NUnitExtensions.Trace;
  using RJCP.Diagnostics.Trace;

  [SetUpFixture]
  public class TestSetupFixture {
    [OneTimeSetUp]
    public void GlobalSetup() {
      GlobalLogger.Initialize();
    }
  }

  internal static class GlobalLogger {
    static GlobalLogger() {
      ILoggerFactory factory = LoggerFactory.Create(builder => {
        builder
          .AddFilter("Microsoft", LogLevel.Warning)
          .AddFilter("System", LogLevel.Warning)
          .AddFilter("RJCP.Diagnostics.Log", LogLevel.Debug)
          .AddNUnitLogger();
        });
        LogSource.SetLoggerFactory(factory);
    }

    // Just calling this method will result in the static constructor being executed.
    public static void Initialize() {
      /* Can be empty, reference will initialize static constructor */
    }
  }
}

So long as GlobalLogger.Initialize() is called before the code being tested can initialize the LogSource.SetLoggerFactory, it will work.

3. Known Issues

3.1. Using ILogger with NUnit Tests

Using the .NET Core ConsoleLogger will not work in NUnit Test cases. Use my RJCP.DLL.CodeQuality library to get a NUnitLogger.

The ConsoleLogger doesn't work in NUnit, as it creates a thread in the background that does the logging. This thread maintains a handle to the console, and is incompatible with the way that NUnit also tries to capture th console and redirect itself. That's why the workaround for console logging was to completely instantiate the factory on every test case, which is inefficient, but would reset the background thread allowing NUnit to capture the console again at the start of each test. But it still didn't solve the race conditions that might sometimes occur due to console logging actually being done in another thread, and so some console output might actually be captured for a different test case.

See GitHub Issue #3919.

4. Release History

4.1. Version 0.2.1

Quality:

  • Add reference to README.md in NuGet package (DOTNET-810)
  • Trace: Upgrade to .NET Core 6.0 (DOTNET-936, DOTNET-942, DOTNET-945, DOTNET-959)
  • Update to .NET 8.0 (DOTNET-982, DOTNET-983, DOTNET-989, DOTNET-990)

4.2. Version 0.2.0

  • Initial Version
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Framework net40 is compatible.  net403 was computed.  net45 was computed.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on RJCP.Diagnostics.Trace:

Package Downloads
RJCP.IO.Device

Get Windows Device Information.

RJCP.SerialPortStream

An independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability.

RJCP.Diagnostics.Log

Parse external log streams.

RJCP.Diagnostics.Log.Dlt

An AutoSAR DLT v1 Decoder and Encoder.

RJCP.Diagnostics.CrashReporter

Library tools to allow developers to capture debug information.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.2.1 787 3/9/2024
0.2.0 1,382 6/9/2023