Trying to get appSettings.json settings in an ILogger provider

David Thielen 3,211 Reputation points
2023-10-13T01:52:49.4066667+00:00

Hi all;

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>();
}

Any ideas?

thanks - dave

Developer technologies | ASP.NET | ASP.NET Core
Developer technologies | .NET | Blazor
0 comments No comments
{count} votes

Accepted answer
  1. Anonymous
    2023-10-13T06:47:07.5633333+00:00

    Hello @David Thielen ,

    I made some modifications to your code and it works fine with me, please let me know if I understand wrong.

    Here is my sample code.

    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();
    
            }
        }
    }
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Jason


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.