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

自定义ILoggerProvider中日期格式本地化问题求助

自定义ILoggerProvider日期格式化问题

我实现了一个自定义ILoggerProvider,当前遇到的问题是:传入ILogger的Date、DateTime、DateTimeOffset参数,会被内部的Microsoft.Logging.Extensions.LogValuesFormatter类使用InvariantCulture转换为字符串,而非应用程序的CurrentCulture。我们位于英国,使用dd/MM/yyyy格式,而非不变区域性的MM/dd/yyyy格式,甚至yyyy-MM-dd都比美式格式更适用。

该问题出现在示例代码的Log<TState>方法中,其中TState是内部的Microsoft.Logging.Extensions.LogValuesFormatter类,无法直接解析,只能传入提供的formatter。请问是否可以替换LogValuesFormatter类?支持富参数日志的提供程序能实现该功能,我希望自定义日志也能做到。

曾参考相关方案,但均存在严重弊端:

  • 使用反射(性能低且在.NET7+中失效)
  • 将state转换为IReadOnlyList<KeyValuePair<string, object>>(因类为内部类而无效)
  • 序列化/反序列化state(效率极低且效果有限)
  • 实现自定义LogInformation扩展方法(需重写大量日志条目,且破坏ILogger的通用性)
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Text;

namespace XXXXX.Base;

/// <summary>
/// Custom ILoggerProvider, writes logs to text files with the option to cycle the filename daily.
/// Daily File change is checked and handled by a call to CustomFileLoggerProvider.CheckTimeToChangeFile()
/// which for efficiency should be done via an occasional background worker or a Scoped Page Request (middleware)
/// </summary>
[ProviderAlias("CustomFile")]
public sealed class CustomFileLoggerProvider : ILoggerProvider
{
    private readonly ConcurrentDictionary<string, CustomFileLogger> _loggers = new(StringComparer.OrdinalIgnoreCase);
    private CustomFileLoggerConfiguration _currentConfig;
    private readonly IDisposable? _onChangeToken;
    private readonly object _lock = new();
    private StreamWriter? _logFileWriter;
    private bool _stopLogging = false;

    /// <summary>
    /// If messages come in prior to initialisation via the first request (middleware) then stores values here
    /// </summary>
    private readonly List<string> _initialCache = new(32);

    public CustomFileLoggerProvider(IOptionsMonitor<CustomFileLoggerConfiguration> config)
    {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(config => _currentConfig = config);
    }

    public void Flush()
    {
        _logFileWriter?.Flush();
    }

    public void Write(string text)
    {
        if (_stopLogging)
            return;

        lock (_lock)
        {
            if (_logFileWriter is null)
            {
                OpenLogFile();
                if (_logFileWriter is null)
                {
                    if (_initialCache.Count < 100)
                        _initialCache.Add(text);
                    else
                        _stopLogging = true;
                }
            }
            if (_logFileWriter is not null)
            {
                _logFileWriter.WriteLine(text);
                _logFileWriter.Flush();
            }
        }
    }



    public ILogger CreateLogger(string categoryName) =>
        _loggers.GetOrAdd(categoryName, name => new CustomFileLogger(categoryName, Write));

    /// <summary>
    /// Opens a new log file.  This should be called while under lock.
    /// </summary>
    private bool OpenLogFile()
    {
        var filename = _currentConfig.LogFile;
        if (filename is null)
            return false;

        try
        {
            _logFileWriter = new StreamWriter(filename, append: true, encoding: Encoding.UTF8);
            _logFileWriter.WriteLine();
            _logFileWriter.WriteLine();
            _logFileWriter.WriteLine($"======== {DateTime.UtcNow:dd/MM/yyyy HH:mm:ss} ========");
            _logFileWriter.Flush();
            _stopLogging = false;
            return true;
        }
        catch (IOException ex)
        {
        ...
        }
    }

    /// <summary>
    /// Closes the current log file.  This should be called while under lock.
    /// </summary>
    private void CloseLogFile(string reason)
    {
        if (_logFileWriter is not null)
        {
            if (!string.IsNullOrEmpty(reason))
                _logFileWriter.WriteLine(reason);
            _logFileWriter.WriteLine("======== END ========");
            _logFileWriter.Flush();
            _logFileWriter.Dispose();
            _logFileWriter = null;
        }
    }

    public void Dispose()
    {
        lock (_lock)
        {
            CloseLogFile("Closing Log File due to Provider being disposed (e.g. application shutdown)");
            _loggers.Clear();
            _onChangeToken?.Dispose();
        }
    }
}

/// <summary>
/// Custom ILogger, writes logs to text files
/// </summary>
public sealed class CustomFileLogger(string categoryName, Action<string> writeFunc) : ILogger
{
    private readonly string _categoryName = categoryName;
    private readonly Action<string> _writeFunc = writeFunc;

    public IDisposable BeginScope<TState>(TState state) where TState : notnull => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        // Ensure that only information level and higher logs are recorded
        logLevel >= LogLevel.Information;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        // Ensure that only information level and higher logs are recorded
        if (!IsEnabled(logLevel))
            return;

        // Get the formatted log message
        var message = formatter(state, exception);

        //var now = DateTimeOffset.Now;

        //Write log messages to text file
        _writeFunc($"{LL(logLevel)} [{_categoryName}] {message}");
    }

    private static string LL(LogLevel l) => l switch
    {
        LogLevel.Information => "INF",
        LogLevel.Trace => "TRC",
        LogLevel.Debug => "DBG",
        LogLevel.Warning => "WRN",
        LogLevel.Error => "ERR",
        LogLevel.Critical => "CRITICAL",
        LogLevel.None => "",
        _ => throw new ArgumentOutOfRangeException(nameof(l))
    };
}

/// <summary>
/// By convention, extension methods on ILoggingBuilder are used to register the custom provider
/// </summary>
public static class CustomFileLoggerExtensions
{
    public static ILoggingBuilder AddCustomFileLogger(this ILoggingBuilder builder)
    {
        builder.AddConfiguration();
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, CustomFileLoggerProvider>());
        LoggerProviderOptions.RegisterProviderOptions<CustomFileLoggerConfiguration, CustomFileLoggerProvider>(builder.Services);
        return builder;
    }

    public static ILoggingBuilder AddCustomFileLogger(this ILoggingBuilder builder, Action<CustomFileLoggerConfiguration> configure)
    {
        builder.AddCustomFileLogger();
        builder.Services.Configure(configure);
        return builder;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 07:39:53