'TState does not contain RequestId or other value from HostingEnvironment in CustomLogger

I am trying to create a custom LoggerProvider to support our legacy file-logger for an upcomming migration to Microsofts ILogger-Interface.

I have implemented a ILogger that redirects log messages to our legacy logger as well as a LoggerFactory that implements ILoggerProvider and ILoggerFactory.

While doing research for the upcoming migration i found out that other logger providers (in this case Seq and Azure) can log additional Properties from the TState object that is passe to the ILogger.Log methode. Additional Properties may be, but are not limited to:

  • Scope
  • RequestId
  • SourceContext
  • ActionId
  • ...others

The state contains some form of IReadOnlyList<KeyValuePair<string, object>> (specifically a instance of Microsoft.Extensions.Logging.FormattedLogValues). I would like to extract the RequestId that is shown in e.g. Seq from this state to include it in the log-message, how ever there is no entry woth the key "RequestId".

My question: How do i access these additional context properties or why are they not filled in my state-object.

CompatibilityLogger.cs

internal class CompatibilityLogger : ILogger
{

    private readonly LegacyLogger _defaultLogger;

    public CompatibilitySimpleLogger(LegacyLogger logger)
    {
        this._defaultLogger = logger;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var message = formatter(state, exception);

        // Request id oder entsprechende Menge an Tabs einfügen, damit der log vernüftig formatiert ist
        if (TryGetPropertyFromState(state, "RequestId", out string requestId) && !string.IsNullOrWhiteSpace(requestId))
        {
            message = "Request: " + requestId + "\t" + message;
        }
        else
        {
            message = "\t\t\t\t\t\t\t\t\t\t\t\t" + message;
        }


        switch (logLevel)
        {
            //... redirecting to LegacyLogger
        }
    }



    private bool TryGetPropertyFromState<TState>(TState state, string propertyName, out string propertyValue)
    {
        if (state is IReadOnlyList<KeyValuePair<string, object>> kvs)
        {
            if (kvs.FirstOrDefault(x => x.Key == propertyName) is KeyValuePair<string, object> kv)
            {
                propertyValue = kv.Value?.ToString();
                return true;
            }
        }

        propertyValue = null;
        return false;
    }

CompatibilityLoggerFactory.cs

public class CompatibilityLoggerFactory : ILoggerProvider, ILoggerFactory
{
    public CompatibilityLoggerFactory()
    {
        LegacyLoggerManager.InitSettings();
    }

    public void AddProvider(ILoggerProvider provider)
    {
        throw new NotSupportedException($"{nameof(CompatibilityLoggerFactory)} unterstützt das hinzufügen von Logger-Providern nicht. Bei bedarf sollte das einbindende Projekt auf Dependency-Injection umgestellt werden.");
    }

    public ILogger CreateLogger(string categoryName)
    {
        var logger = LegacyLoggerManager.GetLogger(categoryName, true);
        return new CompatibilityLogger(logger);
    }

    public void Dispose()
    {
    }
}

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.AddConsole();
                loggingBuilder.AddSeq();
                loggingBuilder.AddProvider(new CompatibilityLoggerFactory());
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source