You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#自定义ILogger如何复用AppSettings公共路径配置

问题描述

我检索了相关资料未找到目标解决方案,此处仅提供核心代码片段用于说明问题,全量代码依赖整套业务系统无法独立运行。
我使用C#内置日志框架实现向Windows事件查看器、控制台输出日志;因不想引入第三方日志类库,自行编写了简易文件日志组件,可正常将同源日志写入文件。
我的appsettings.json中配置了工作目录等业务参数,同时也单独配置了日志组件的存储路径,我希望复用公共的工作路径配置,无需两处重复配置相同路径。

现有配置与代码

appsettings.json

{
  "AppSettings": {
    "WorkingFolderPath": "C:\\mypath\\",
    "HeartbeatIntervalMinutes": 0, // 调试模式下该值单位为秒
    "FileRetryDelayMinutes": 1,
    "PingTimeoutMilliseconds": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Warning"
    },
    "Debug": {
      "LogLevel": {
        "Default": "Debug"
      }
    },
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Debug"
      }
    },
    "EventLog": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "FileLog": {
      "Options": {
        "FolderPath": "C:\\mypath\\",
        "RetentionDays": 5
      },
      "LogLevel": {
        "Default": "Debug"
      }
    }
  }
}

如配置文件所示,目标是直接使用AppSettings节点下的WorkingFolderPath作为FileLog日志组件的存储路径,无需在FileLog的Options节点下重复配置FolderPath。

Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureLogging((context, logging) =>
            {
                logging.ClearProviders();
                logging.AddConsole();
                logging.AddDebug();
                logging.AddEventLog(new EventLogSettings()
                {
                    SourceName = "FreshIQAppMessagingService"
                });
                logging.AddFileLogger(options =>
                {
                    context.Configuration.GetSection("Logging").GetSection("FileLog").GetSection("Options").Bind(options);
                });
            })
            .ConfigureAppConfiguration((hostContext, config) =>
            {
                config
                    .SetBasePath(ApplicationPath)
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);

                config.AddEnvironmentVariables();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.Configure<Configuration>(hostContext.Configuration.GetSection("AppSettings"));
                services.AddHostedService<Service1>();
                services.AddHostedService<Service2>();
                services.AddHostedService<Service3>();
                services.AddHostedService<Service4>();
            });

当前代码在配置日志时仅绑定了FileLog节点下的Options配置,未找到合适的修改方案,不知道如何调整相关类的实现,才能将appsettings中AppSettings节点下的WorkingFolderPath传入自定义FileLogger,替换原有Options中的路径配置,实现配置复用。
当前绑定配置的代码片段如下:

logging.AddFileLogger(options =>
                    {
                        context.Configuration.GetSection("Logging").GetSection("FileLog").GetSection("Options").Bind(options);
                    });

自定义日志组件实现

FileLoggerExtensions.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

namespace FreshIQAppMessaging.Logging
{
    public static class FileLoggerExtensions
    {
        public static ILoggingBuilder AddFileLogger(this ILoggingBuilder loggingBuilder, Action<FileLoggerOptions> configure)
        {
            loggingBuilder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
            loggingBuilder.Services.Configure(configure);

            return loggingBuilder;
        }
    }
}

FileLoggerProvider.cs

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.IO;

namespace FreshIQAppMessaging.Logging
{
    [ProviderAlias("FileLog")]
    public class FileLoggerProvider : ILoggerProvider
    {
        public readonly FileLoggerOptions Options;
        public FileLoggerProvider(IOptions<FileLoggerOptions> options)
        {
            Options = options.Value;

            if (!Directory.Exists(Options.FolderPath))
            {
                Directory.CreateDirectory(Options.FolderPath);
            }
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new FileLogger(this);
        }

        public void Dispose() { }
    }
}

FileLogger.cs

using Microsoft.Extensions.Logging;
using System;
using System.Globalization;
using System.IO;

namespace FreshIQAppMessaging.Logging
{
    public class FileLogger : ILogger
    {
        protected readonly FileLoggerProvider _fileLoggerProvider;

        public FileLogger(FileLoggerProvider fileLoggerProvider)
        {
            _fileLoggerProvider = fileLoggerProvider;
        }

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

        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel != LogLevel.None;
        }

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

            // 清理过期日志文件
            var logFiles = Directory.GetFiles(_fileLoggerProvider.Options.FolderPath, "*-MyApp.log");
            foreach (var logFilePath in logFiles)
            {
                var logFileName = new FileInfo(logFilePath).Name;
                if (DateTime.TryParseExact(logFileName.Substring(0, 8), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var logFileDate))
                {
                    if (logFileDate.AddDays(_fileLoggerProvider.Options.RetentionDays) < DateTime.Now)
                    {
                        File.Delete(logFilePath);
                    }
                }
            }

            var fullFilePath = $"{_fileLoggerProvider.Options.FolderPath}\\{DateTime.Now.ToString("yyyyMMdd")}-MyApp.log";
            var logRecord = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} [{logLevel.ToString()}] {formatter(state, exception)} {(exception != null ? exception.StackTrace : "")}";

            using (var streamWriter = new StreamWriter(fullFilePath, true))
            {
                streamWriter.WriteLine(logRecord);
            }
        }
    }
}

FileLoggerOptions.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace FreshIQAppMessaging.Logging
{
    public  class FileLoggerOptions
    {
        public virtual string? FolderPath { get; set; }
        public virtual int RetentionDays { get; set; } = 5;
    }
}

解决方案

只需要修改Program.cs中AddFileLogger的配置绑定逻辑,绑定完FileLog的Options配置后,手动读取AppSettings:WorkingFolderPath赋值给FolderPath即可,显式读取的工作路径优先级高于FileLog节点下配置的路径,修改后的代码如下:

logging.AddFileLogger(options =>
{
    // 先绑定FileLog节点下的基础配置
    context.Configuration.GetSection("Logging").GetSection("FileLog").GetSection("Options").Bind(options);
    // 读取公共工作路径配置,覆盖FolderPath
    var workingPath = context.Configuration.GetValue<string>("AppSettings:WorkingFolderPath");
    if (!string.IsNullOrWhiteSpace(workingPath))
    {
        options.FolderPath = workingPath;
    }
});

修改完成后即可删除appsettings.json中Logging:FileLog:Options:FolderPath的重复配置,日志组件会自动读取公共工作路径作为日志存储目录,实现配置复用。

注意:.NET Host的默认执行顺序是先加载配置,再执行日志配置、服务配置,所以context.Configuration中已经包含了appsettings.json的所有配置内容,直接读取不会出现空值问题。如果后续调整执行顺序出现配置读取为空的情况,将ConfigureAppConfiguration移动到ConfigureLogging之前即可。


内容的提问来源于stack exchange,提问作者Chris Ward

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 00:09:21