Logging

Quick access

File logger provider

Purposes

  • Separate log folder for services
  • Limit log file size
  • Limit the number of log files
  • Separate log file for job

Usage

  1. Configure log builder

Call AddFileLogger() on the logging builder to add a customized file logger provider.

using Juice.Extensions.Logging;
...

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddFileLogger(builder.Configuration.GetSection("Logging:File"));

Logging:File configuration section may be present:

  • Directory: where to store logging files
  • RetainPolicyFileCount: default value is 50
  • MaxFileSize: default value is 5 * 1024 * 1024;
  • ForkJobLog: default value is true
  • BufferTime: the log entries will be written to the file after the interval (5 seconds by default)
  • GeneralName: the default name of sub-directory if there is no ServiceDescription provided. It’s optional and the default value is General
// appsettings.json
{
  "Logging": {    
    "File": {
      "Directory": "C:\\Workspace\\Services\\logs",
      "BufferTime": "00:00:03",
      //"ForkJobLog":  false
    }
  }
}
  1. Separate logs by scopes

To separate logs folder, we will init new log scope with specified properties:

  • ServiceId: Guid value
  • ServiceDescription: string value

You may want to store _logScope to dispose it later.

// {Logging:File:Directory}/{ServiceDescription}/{yyyy_MM_dd-HHmm}.log
_logScope = _logger.BeginScope(new List<KeyValuePair<string, object>>
{
    new KeyValuePair<string, object>("ServiceId", Id),
    new KeyValuePair<string, object>("ServiceDescription", Description)
});

To separate log file for job, we will init new log scope with specified properties:

  • TraceId: string value
  • Operation: string value
// {Logging:File:Directory}/{ServiceDescription}/{TraceId} - {Operation}.log
using (_logger.BeginScope(new List<KeyValuePair<string, object>>
{
    new KeyValuePair<string, object>("TraceId", TraceId),
    new KeyValuePair<string, object>("Operation", Operation)
})){
    // job processing
    _logger.LogInformation("Begin invoke");
    var state = await InvokeAsync();
    // {Logging:File:Directory}/{ServiceDescription}/{TraceId} - {Operation}_{OperationState}.log
    // {Logging:File:Directory}/{ServiceDescription}/{TraceId} - {Operation}_{OperationState} ({increased number}).log
    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
    {
        new KeyValuePair<string, object>("OperationState", state)
    })){
        // job is completed with state
        _logger.LogInformation("Invoke result: {0}", state);
    }
}
  1. Write log scopes to file

Any log scopes specified by the string will be written to the log file

using (logger.BeginScope("Scope 1")){
  using(logger.BeginScope("Scope 1.1")){
    logger.LogInformation("Log inside scope");
  }
}
logger.LogInformation("Log outside scope");

//---- Begin: Scope 1
//-------- Begin: Scope 1.1
//
//{Timestamp} {LogLevel}: Log inside scope
//
//--------   End: Scope 1.1
//----   End: Scope 1
//{Timestamp} {LogLevel}: Log outside scope

The library can be accessed via Nuget:

SignalR logger provider

Purposes

  • Send logs to signalR hub
  • Log channel by ServiceId
  • Support log State, Contextual and Scopes

Usage

  1. Configure log builder

Call AddSignalRLogger() on the logging builder to add a customized file logger provider.

using Juice.Extensions.Logging;
...

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddSignalRLogger(builder.Configuration.GetSection("Logging:SignalR"));

Logging:SignalR configuration section may be present:

  • Directory: where to store logging files for the SignalRLogger itself and the HubConnection
  • RetainPolicyFileCount: default value is 50
  • MaxFileSize: default value is 5 * 1024 * 1024;
  • BufferTime: the log entries will be written to the file after the interval (5 seconds by default)
  • GeneralName: the sub-directory name to store logging files. It’s optional and the default value is SignalRLogger and SignalRLoggerHubConnection
  • HubUrl: the signalR hub’s url
  • JoinGroupMethod: optional method name to join group with ServiceId, default value is JoinGroup
  • LogMethod: optional method name to send the log data, default value is LoggingAsync
  • StateMethod: optional method name to send the state, default value is StateAsync
  • ScopesSupported: hub is supported scopes logging or not
  • ExcludedScopes: excluded scopes to send to the client. We will exclude ServiceId, ServiceDescription, TraceId, OperationState, Contextual by default, you can override this default by implementing IScopesFilter yourself
  • Disabled: soft disable this logging channel
// appsettings.json
{
  "Logging": {    
    "File": {
      "Directory": "C:\\Workspace\\Services\\logs",
      "HubUrl": "https://localhost:57754/loghub"
    }
  }
}
  1. Using logs scopes

Basically, the supported scopes are like the File logger provider above, but SignalR logger provider is support more stronger named scopes:

  • Contextual: make it possible for your logging client to specify log colors by contextual. Ex: success, warning, danger…
  • OperationState: if specified, we will send to the StateAsync method on the hub

Any log scopes specified by the string will be sent to the log hub if it is supported.

  using (_logger.BeginScope(new List<KeyValuePair<string, object>>
  {
      new KeyValuePair<string, object>("TraceId", jobId),
      new KeyValuePair<string, object>("Operation", "Invoke recurring tasks")
  }))
  {
      // will send scopes []
      _logger.LogInformation("Starting...");
      for (var i = 0; i < 10; i++)
      {
          if (_stopRequest!.IsCancellationRequested) { break; }
          using (_logger.BeginScope($"Task {i}"))
          {
            // will send scopes ["Task {i}"]
            for (var j = 0; j < 10; j++)
            {
                _logger.LogInformation("Processing {subtask}", j);
                  using (_logger.BeginScope(new List<KeyValuePair<string, object>>
                {
                    new KeyValuePair<string, object>("Contextual", "success")
                }))
                {
                    _logger.LogError("Subtask {task} completed", j);
                }
            }
            
            using (_logger.BeginScope("Succeeded"))
            {
                // will send scopes ["Task {i}", "Succeeded"]
                _logger.LogInformation("Task {task} succeeded", i);
            }
          }
      }
      using (_logger.BeginScope(new List<KeyValuePair<string, object>>
      {
          new KeyValuePair<string, object>("OperationState", "Succeeded")
      }))
      {   // will send to the state method, not logging method
          _logger.LogInformation("End");
      }
  }
  1. Hub implementation

The code block below is an example of signalR hub to handle logging

    public class LogHub : Hub<ILogClient>
    {
        public async Task LoggingAsync(Guid serviceId, string? traceId, string category, string message, LogLevel level, string? contextual, object[] scopes)
        {
            await Clients.Others.LoggingAsync(serviceId, traceId, category, message, level, contextual, scopes);
        }
        public async Task StateAsync(Guid serviceId, string? traceId, string state, string message)
        {
            await Clients.Others.StateAsync(serviceId, traceId, state, message);
        }
        public async Task BeginScopeAsync(Guid serviceId, string? traceId, string category, object scope)
        {
            await Clients.Others.BeginScopeAsync(serviceId, traceId, category, scope);
        }
        public async Task EndScopeAsync(Guid serviceId, string? traceId, string category, object scope)
        {
            await Clients.Others.EndScopeAsync(serviceId, traceId, category, scope);
        }
    }
  1. Implement IScopesFilter

To custom the log scopes filter to send to SingalR client, you can implement the IScopesFilter and register it with DI

// CustomScopesFilter.cs
using Microsoft.Extensions.Options;
internal class CustomScopesFilter : IScopesFilter{
  public bool IsIncluded(string scope)
  {
      // your filter
  }
}
// Program.cs
using Juice.Extensions.Logging;
builder.Logging.AddSignalRLogger<CustomScopesFilter>(builder.Configuration.GetSection("Logging:SignalR"));

NOTE

  • If you are not supported for scopes in the logging method, please set the ScopesSupported to False

The library can be accessed via Nuget:

DB logger provider

Purposes

  • Store log records to the DB, included ServiceId, TraceId and Operation
  • Store log metrics by Service, Operation and Category to the DB

Usage

Call AddDbLogger() and/or AddMetricsLogger() on the logging builder to add a customized file logger provider.

using Juice.Extensions.Logging;
...

var builder = WebApplication.CreateBuilder(args);

// require RootConfiguration to access the ConnectionStrings
builder.Logging.AddDbLogger(builder.Configuration.GetSection("Logging:Db"), builder.Configuration);
builder.Logging.AddMetricsLogger(builder.Configuration.GetSection("Logging:Metrics"), builder.Configuration);

Logging:Db configuration section may be present:

  • Directory: where to store logging files for the DbLogger itself
  • RetainPolicyFileCount: default value is 50
  • MaxFileSize: default value is 5 * 1024 * 1024;
  • BufferTime: the log entries will be written to the DB after the interval (5 seconds by default)
  • GeneralName: the sub-directory name to store logging files. It’s optional and the default value is DBLogger
  • DatabaseProvider: the database provider name
  • Schema: the DB schema to create the log table
  • ConnectionName: the DB connection name
  • Disabled: soft disable this logging channel

Logging:Metrics configuration section is as same as the Logging:Db but added:

  • SampleRate: the sample rate to collect the log metrics, if not set, the collection time is 5 seconds.
// appsettings.json
{
  "Logging": {    
    "Db": {
      "Directory": "C:\\Workspace\\Services\\logs",
      "DatabaseProvider": "PostgreSQL",
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Metrics": {
      "Directory": "C:\\Workspace\\Services\\logs",
      "DatabaseProvider": "PostgreSQL",
      "SampleRate": "00:00:30",
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}

The library can be accessed via Nuget: