ToolsPack.Logging 3.1.2

dotnet add package ToolsPack.Logging --version 3.1.2                
NuGet\Install-Package ToolsPack.Logging -Version 3.1.2                
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="ToolsPack.Logging" Version="3.1.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ToolsPack.Logging --version 3.1.2                
#r "nuget: ToolsPack.Logging, 3.1.2"                
#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 ToolsPack.Logging as a Cake Addin
#addin nuget:?package=ToolsPack.Logging&version=3.1.2

// Install ToolsPack.Logging as a Cake Tool
#tool nuget:?package=ToolsPack.Logging&version=3.1.2                

ToolsPack.Logging

ElapsedTimeLogger

Micro-benchmark a part of code to investigate on performance. This is a normal ILogger or more precisely a decorator to a ILogger, when you use it to log a message it will "decorate" the message with the duration (elapsed time) since the previous logged message.

class MyCalculator
{
    private static readonly ILogger Log = loggerFactory.CreateLogger<ElapsedTimeLoggerTests>();

    public void Process()
    {
        using (var etw = ElapsedTimeLogger.Create(Log, "blockCodeName"))
        {
            ...
            etw.LogInformation("step 1");
            ...
            etw.LogDebug("step 2");
            ...
            etw.LogInformation("Step 3)");
            ...
        } //"sum up log" is displayed here
    }
}
  • The etw wrap the usual logger Log, we use etw to log message instead of the usual Log
  • the blockCodeName is repeated in the start of each log message, so that we can filter log message by "blockCodeName"
  • Each log message will display the elapsed time (in micro-second) since the last log message.
  • A sum up log will display the total elapsed time (in micro-second) when the etw object is disposed.
22:56:59,866 [DEBUG] Begin blockCodeName
22:56:59,970 [INFO ] blockCodeName - 102350 mcs - step 1
22:57:00,144 [DEBUG] blockCodeName - 173295 mcs - step 2
22:57:00,259 [INFO ] blockCodeName - 114036 mcs - Step 3)
22:57:00,452 [INFO ] End blockCodeName : Total elapsed 585436 mcs

Auto Jump Log Level

var etw = ElapsedTimeLogger.Create(Log, "checkIntraday").InfoEnd().AutoJump(150, 250).AutoJumpLastLog(500, 1000)
  • The log level will auto jump to INFO if the elapsed time exceeds 150 ms
  • The log level will auto jump to WARN if the elapsed time exceeds 250 ms
  • The above sum up log will switch to INFO if the total elapsed time exceeds 500 ms
  • The above sum up log will switch to WARN if the total elapsed time exceeds 1 sec

Customize Start Context and End context

var etw = ElapsedTimeLogger.Create(Log, "foo", "Start_context", "End_context");

will give

22:56:59,866 [DEBUG] Begin Start_context
22:56:59,970 [INFO ] foo - 102350 mcs - step 1
22:57:00,144 [DEBUG] foo - 173295 mcs - step 2
22:57:00,259 [INFO ] foo - 114036 mcs - Step 3)
22:57:00,452 [INFO ] End End_context : Total elapsed 585436 mcs

We often display the parameter of the functions in the "Start context". Example:

public void process(string val, bool useCache)
{
    var context = string.Format("process(val={0}, useCache={1})", val, useCache);
    using (var etw = ElapsedTimeLogger.Create(Log, "process", context))
    {
        ...
        etw.LogInformation("step 1");
        ...
        etw.LogDebug("step 2");
        ...
        etw.LogInformation("Step 3)");
        ...
    } //"sum up log" is displayed here
}

will give

22:56:59,866 [DEBUG] Begin process(val=Lorem ipsum, useCache=true)
22:56:59,970 [INFO ] process - 102350 mcs - step 1
22:57:00,144 [DEBUG] process - 173295 mcs - step 2
22:57:00,259 [INFO ] process - 114036 mcs - Step 3)
22:57:00,452 [INFO ] End process(val=Lorem ipsum, useCache=true) : Total elapsed 585436 mcs

Other benchmark library

MockLogger

Your test is executing some application codes which log certain messages via the Microsoft.Extensions.Logging library. Once the application codes finished, you want to Assert (or to Verify) that some messages are logged as expected.

The MockLogger allows you to "spy" on the log events happening during the application execution. Checkout the MockLoggerTests.cs for full demonstration.

Here how it works:

Step 1: Use a Mock library (such as Moq, NSubstitue..) to instantiate a MockLogger. For eg:

MockLogger _mocklogger = Substitute.For<MockLogger>();

Step 2: Ask your application codes to use the MockLogger by providing (or by injecting) it to your Application codes, for eg:

var diContainer = new ServiceCollection();
diContainer.AddLogging(builder =>
{
    builder.AddMockLogger(_mocklogger);
});

step 3: Then use your Mock library to spy (NSubstitue.Received(), Moq.Verify..) on the IsLogged hooks of the MockLogger. For eg:

_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Error),
    Arg.Any<EventId>(),
    Arg.Is<Exception?>(ex => ex.Message == "some exception"),
    Arg.Is<string>(s => s.Contains("some error on Greeting")));

Basic usages

//Act
_mocklogger.LogInformation("haha");
_mocklogger.LogError(new Exception("some exception"), "some error on {method}", "Greeting");

//Assert
_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Information),
    Arg.Any<EventId>(),
    Arg.Any<Exception?>(),
    Arg.Is("haha"));

_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Error),
    Arg.Any<EventId>(),
    Arg.Is<Exception?>(ex => ex.Message == "some exception"),
    Arg.Is<string>(s => s.Contains("some error on Greeting")));

Same Basic usages for High performance logging

//Logging definition

[LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Send request {message}")]
public static partial void LogSendRequestInfo(this ILogger logger, string message);

[LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "Received something wrong")]
public static partial void LogGetResponseError(this ILogger logger, Exception ex);

//High performance logging

_mocklogger.LogSendRequestInfo("toto");
_mocklogger.LogGetResponseError(new Exception("Failed to do thing"));

//Assert the log content

_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Information),
    Arg.Any<EventId>(),
    Arg.Any<Exception?>(),
    Arg.Is("Send request toto"));

_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Error),
    Arg.Any<EventId>(),
    Arg.Is<Exception?>(ex => ex.Message == "Failed to do thing"),
    Arg.Is<string>(s => s.Contains("Received something wrong")));

Log structuring

In log structuring, a log item is usually a list of objects (key-value) rather than just a string.

Your application codes might also generate many log items with the same text message but with different objects in the log structure. In this case, Asserting (verifying) the log text message is not very interesting, you usually want to spy on the log structure instead.

//Logging definition

[LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "The person {person} odered {order}")]
public static partial void LogStructuringSampleInfo(this ILogger logger, SomePersonObject person, SomeOrderObject order);

//High performance logging, it will generate a structured log which contains 2 objects

_mocklogger.LogStructuringSampleInfo(new SomePersonObject("Hiep", 18), new SomeOrderObject("#1", 30.5));

//Assert the log content

using LogState = IReadOnlyList<KeyValuePair<string, object?>>;

_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Information),
    Arg.Any<EventId>(),
    Arg.Any<Exception?>(),
    Arg.Is<string>(msg => Regex.IsMatch(msg, "The person .* odered .*")),
    Arg.Is<LogState>(state => AssertLogStructuring(state)));

//verify that the "person" and "order" objects in the log item got the expected values
private static bool AssertLogStructuring(LogState state)
{
    var person = (from s in state
                    where s.Key == "person"
                    select s.Value as SomePersonObject).First();

    var order = (from s in state
                    where s.Key == "order"
                    select s.Value as SomeOrderObject).First();

    return person.Name == "Hiep" && person.Age == 18 && order.Id == "#1" && order.Amount == 30.5;
}

Use MockLogger to spy on other loggers used by your application codes

You have to add the MockLogger to the logging pipeline of your application, so that it can "monitor" all log events generated by your application codes:

//let's say you have a service provider similar to the one in your ASP.NET app
var diContainer = new ServiceCollection();
diContainer.AddLogging(builder =>
{
    //add the MockLogger to the logging pipeline
    builder.AddMockLogger(_mocklogger);
});
var services = diContainer.BuildServiceProvider();

//let's say your application is using this logger (which is not our MockLogger)
ILogger<MyApp> someAppLogger = services.GetRequiredService<ILogger<MyApp>>();

//your application logged something with this logger
someAppLogger.LogInformation("haha");

//Assert that application logged the expected message whatever the logger is used
_mocklogger.Received().IsLogged(
    Arg.Is(LogLevel.Information),
    Arg.Any<EventId>(),
    Arg.Any<Exception?>(),
    Arg.Is("haha"));
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  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 is compatible.  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 Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.1.2 717 3/7/2024
3.1.1 118 2/9/2024
3.1.0 518 2/8/2024
3.0.3 130 2/8/2024
3.0.2 105 2/8/2024
3.0.1 444 11/27/2022
3.0.0 1,335 7/5/2022
1.0.1 866 6/1/2020
1.0.0 423 5/14/2020