Trying to get appSettings.json settings in an ILogger provider

291 Views Asked by At

The ILogger provider I created runs fine. But I have two problems:

  1. The appSettings.json are not passed in to the provider constructor.
  2. The logging system ignores the appSettings.json set specifically for my provider.

appSettings.json:

"Logging": {
    "LogLevel": {
        "Default": "Warning",
        "LouisHowe": "Information",
        "ThirdPartyServices": "Information",
        "CommonUtilities": "Information",
        "Microsoft": "Warning"
    },

    "File": {
        "Path": "C:/temp/log-{yyyy-MM-dd}.txt",
        "Interval": "Day",
        "LogLevel": {
            "Default": "Information",
            "LouisHowe": "Trace",
            "Microsoft": "Warning"
        }
    },

The options classes:

public class FileLoggerOptions
{
    public string? Path { get; set; } = "";
    public string? Interval { get; set; } = "";
}

internal class FileLoggerOptionsSetup : ConfigureFromConfigurationOptions<FileLoggerOptions>
{
    public FileLoggerOptionsSetup(ILoggerProviderConfiguration<FileLoggerProvider> providerConfiguration)
        : base(providerConfiguration.Configuration)
    {
    }
}

The provider constructors:

public FileLoggerProvider(IOptionsMonitor<FileLoggerOptions> options)
    : this(options.CurrentValue)
{
    _settingsChangeToken = options.OnChange(opt => { _options = opt; });
}

public FileLoggerProvider(FileLoggerOptions options)
{
    options.Path = "C:/temp/log-{yyyy-MM-dd}.txt";
    _options = options;
}

And how it is added in Program.cs:

public static ILoggingBuilder AddFileLogger(this ILoggingBuilder builder)
{
    builder.AddConfiguration();

    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, 
        FileLoggerProvider>());
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<FileLoggerOptions>, 
        FileLoggerOptionsSetup>());
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<FileLoggerOptions>, 
        LoggerProviderOptionsChangeTokenSource<FileLoggerOptions, FileLoggerProvider>>());
    return builder;
}

I can do the following to get the configuration. But I want to understand why the above doesn't work. And the following does not get it to use the LogLevels set under File.

public FileLoggerProvider(IConfiguration config)
{
    _options = config.GetSection("Logging:File").Get<FileLoggerOptions>();
}
2

There are 2 best solutions below

0
On BEST ANSWER

Found the solution. So simple, once you know what it is. Need the following attribute:

[ProviderAlias("File")]
public class FileLoggerProvider : ILoggerProvider, IAsyncDisposable

And that makes sense. It needs to know what I named it in appSettings.json. With that added, it all works as expected.

1
On

Please follow my steps to implement it.

Here is the test result.

enter image description here

My project structure.

enter image description here

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "LouisHowe": "Information",
      "ThirdPartyServices": "Information",
      "CommonUtilities": "Information",
      "Microsoft": "Warning"
    },

    "File": {
      "Path": "C:/temp/log-{0:yyyy-MM-dd}.txt",
      "Interval": "Day",
      "LogLevel": {
        "Default": "Information",
        "LouisHowe": "Trace",
        "Microsoft": "Warning"
      }
    }
  },
  "AllowedHosts": "*"
}

FileLoggerExtensions.cs

namespace ilogger_sink
{

    // Extension method to add custom file logger
    public static class FileLoggerExtensions
    {
        public static ILoggingBuilder AddFileLogger(this ILoggingBuilder builder, IConfiguration configuration)
        {
            builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
            builder.Services.Configure<FileLoggerOptions>(configuration.GetSection("Logging:File"));
            return builder;
        }
    }
}

FileLoggerProvider.cs

using Microsoft.Extensions.Options;

namespace ilogger_sink
{
    public class FileLoggerProvider : ILoggerProvider
    {
        private readonly FileLoggerOptions? _options;

        public FileLoggerProvider(IOptions<FileLoggerOptions> options)
        {
            _options = options.Value;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new FileLogger(_options.Path);
        }

        public void Dispose() { }
    }

}

FileLoggerOptions.cs

namespace ilogger_sink
{
    // File logger options
    public class FileLoggerOptions
    {
        public string? Path { get; set; }
    }
}

FileLogger.cs

namespace ilogger_sink
{
    public class FileLogger : ILogger
    {
        private readonly string? _logDirectory;
        private readonly string? _logFileFormat;

        public FileLogger(string logPathFormat)
        {
            _logDirectory = Path.GetDirectoryName(logPathFormat);
            _logFileFormat = Path.GetFileName(logPathFormat);
        }

        public IDisposable? BeginScope<TState>(TState state) => null;

        public bool IsEnabled(LogLevel logLevel) => true;

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
                return;

            string message = formatter(state, exception);

            // Using string format to insert the current date into the filename
            string filename = String.Format(_logFileFormat, DateTime.UtcNow);
            string logFilePath = Path.Combine(_logDirectory, filename);

            try
            {
                System.IO.File.AppendAllText(logFilePath, message + Environment.NewLine);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Failed to log message: {ex}");
            }
        }
    }

}

Program.cs

...
namespace ilogger_sink
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Logging.ClearProviders();
            builder.Logging.AddConsole();

            builder.Logging.AddFileLogger(builder.Configuration);

            // Add services to the container.
            builder.Services.AddRazorPages();
            ...

            var app = builder.Build();

            ...
            app.Run();

        }
    }
}