SteeltoeOSS / Steeltoe

.NET Components for Externalized Configuration, Database Connectors, Service Discovery, Logging and Distributed Tracing, Application Management, Security, and more.
https://steeltoe.io
Apache License 2.0
1k stars 164 forks source link

Dynamic logging with NLog #858

Open thompson-tomo opened 2 years ago

thompson-tomo commented 2 years ago

Is your feature request related to a problem? Please describe.

I have an on-premise micro service architecture with each application running dotnet core & nlog (company recommendation) and am needing a solution to centrally manage log levels. Currently my solution involves rebooting the services

Describe the solution you'd like

I would like NLog to have the same level of support as SeriLog

Describe alternatives you've considered

Switching to Serilog but I'd against corporate policies

Additional context

We should have a way of obtaining log levels from a microservice & adjusting them when using NLog

snakefoot commented 2 years ago

Maybe something like this:

    [ProviderAlias("DynamicNLog")]
    public class DynamicNLogLoggerProvider : DynamicLoggerProviderBase
    {
        protected readonly IOptionsMonitor<LoggerFilterOptions> _filterOptions;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicNLogLoggerProvider"/> class.
        /// </summary>
        /// <param name="filterOptions">Logger filters</param>
        /// <param name="messageProcessors">message processors to apply to message</param>
        public DynamicNLogLoggerProvider(IOptionsMonitor<LoggerFilterOptions> filterOptions, IEnumerable<IDynamicMessageProcessor> messageProcessors = null)
        : base(() => new NLog.Extensions.Logging.NLogLoggingProvider(new NLog.Extensions.Logging() { RemoveLoggerFactoryFilter = false }), GetInitialLevelsFromOptions(filterOptions), messageProcessors)
        {
        }

        private static InitialLevels GetInitialLevelsFromOptions(IOptionsMonitor<LoggerFilterOptions> filterOptions)
        {
            var runningLevelFilters = new Dictionary<string, Func<string, LogLevel, bool>>();
            var originalLevels = new Dictionary<string, LogLevel>();
            Filter filter = null;
            foreach (var rule in filterOptions.CurrentValue.Rules.Where(p => string.IsNullOrEmpty(p.ProviderName) || p.ProviderName == "Console" || p.ProviderName == "Logging"))
            {
                originalLevels[rule.CategoryName ?? "Default"] = rule.LogLevel ?? LogLevel.None;
                if (rule.CategoryName == "Default" || string.IsNullOrEmpty(rule.CategoryName))
                {
                    filter = (category, level) => level >= (rule.LogLevel ?? LogLevel.None);
                }
                else
                {
                    runningLevelFilters[rule.CategoryName] = (category, level) => level >= (rule.LogLevel ?? LogLevel.None);
                }
            }

            return new InitialLevels
            {
                DefaultLevelFilter = filter,
                OriginalLevels = originalLevels,
                RunningLevelFilters = runningLevelFilters
            };
        }
    }

RemoveLoggerFactoryFilter = false will tell NLog to abide to default Microsoft Extension Logging Filters like the console-provider.

       /// <summary>
        /// Adds Dynamic NLog Logger Provider
        /// </summary>
        /// <param name="builder">Your ILoggingBuilder</param>
        public static ILoggingBuilder AddDynamicNLog(this ILoggingBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            // only run if an IDynamicLoggerProvider hasn't already been added
            if (!builder.Services.Any(sd => sd.ServiceType == typeof(IDynamicLoggerProvider)))
            {
               builder.AddFilter<DynamicNLogLoggerProvider>(null, LogLevel.Trace);
               builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DynamicNLogLoggerProvider>());              
               builder.Services.AddSingleton((p) => p.GetServices<ILoggerProvider>().OfType<IDynamicLoggerProvider>().SingleOrDefault());
            }

            return builder;
        }
thompson-tomo commented 2 years ago

That's a good starting point but would want to also include other functionality offered with serilog ie setting levels (src/Management/src/EndpointBase/Loggers/LoggersEndpoint.cs)

TimHess commented 2 years ago

This would be a good candidate for community contribution. We are happy to help the effort along, but this is not likely a current priority for the core Steeltoe team