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

基于异步工厂方法配置NLog:私有构造下Logger实例获取问题

问题描述

版本信息

  • NLog 5.0.4
  • .NET 6.0 应用程序

原本通过XML配置文件配置NLog运行正常,但配置文件包含明文密钥,希望改为代码化配置,在C#中安全获取密钥以避免明文存储。已实现代码化配置,但密钥需通过异步方法获取,无法在Logger构造函数中执行,因此将配置代码和异步调用移至工厂方法。

核心问题:因配置依赖异步调用,无法通过私有构造的工厂方法在各类中获取Logger实例。

当前NLog配置代码

public class MyLogger : Logger 
{
    // 私有构造函数
    private MyLogger(LoggingConfiguration config)
    {
      // 应用配置           
      LogManager.Configuration = config;
    }

    // 工厂方法
    public async static Task<MyLogger> BuildMyLogger()
    {
      LoggingConfiguration config = new LoggingConfiguration();

      var secret = await CallToGetSecretString();

        DatabaseTarget databaseTarget = new DatabaseTarget("database")
        {
          ConnectionString = secret,
          CommandType = System.Data.CommandType.StoredProcedure,
          CommandText = "[StoredProcedure]",
          Parameters = {
          new DatabaseParameterInfo("@logged", "${date}")
          // 其他参数已省略...
          }
        };
          
        config.AddRuleForAllLevels(databaseTarget);

        return new MyLogger(config);
    }

    // 其他代码...
}

此前的配置方式

public class MyLogger : Logger
{
  public MyLogger()
  {
    LogManager.Configuration = new XmlLoggingConfiguration("NLog/MyNlogConfig.config");
  }
}

原调用代码在新配置下报错

原调用代码:

private readonly static MyLogger logger = (MyLogger)LogManager.GetCurrentClassLogger(typeof(MyLogger));

错误信息:

System.TypeInitializationException: 'The type initializer for 'App.Logic.MyClass' threw an exception.'
Inner Exception
InvalidCastException: Unable to cast object of type 'NLog.Logger' to type 'App.Nlog.MyLogger'.

尝试直接调用工厂方法但失败

代码:

private readonly static MyLogger logger = await MyLogger.BuildMyLogger();

错误:

CS1992  The 'await' operator can only be used when contained within a method or lambda expression marked with the 'async' modifier

解决方案

你遇到的类型转换错误根源是:NLog的LogManager.GetCurrentClassLogger不会返回自定义的MyLogger实例——NLog内部会创建自己的Logger子类实例,无法强制转换为你定义的类型。加上静态字段初始化不能使用await,这两个问题导致了当前困境。

1. 放弃继承NLog.Logger,改用日志管理类

NLog的Logger设计为轻量级、由LogManager统一管理的实例,不建议自定义继承。我们可以创建静态辅助类处理异步配置初始化:

public static class NLogSetup
{
    // 确保配置只初始化一次
    private static readonly Lazy<Task<LoggingConfiguration>> _lazyConfig = new(LoadConfigAsync);

    private static async Task<LoggingConfiguration> LoadConfigAsync()
    {
        var config = new LoggingConfiguration();
        
        // 异步获取密钥
        var connectionString = await CallToGetSecretString();

        // 配置数据库目标
        var dbTarget = new DatabaseTarget("database")
        {
            ConnectionString = connectionString,
            CommandType = System.Data.CommandType.StoredProcedure,
            CommandText = "[StoredProcedure]",
            Parameters =
            {
                new DatabaseParameterInfo("@logged", "${date}")
                // 添加其他参数...
            }
        };

        config.AddRuleForAllLevels(dbTarget);
        return config;
    }

    // 全局初始化方法,在应用启动时调用
    public static async Task InitializeAsync()
    {
        var config = await _lazyConfig.Value;
        LogManager.Configuration = config;
    }
}

2. 在应用启动时完成异步初始化

在.NET 6的Program.cs中,提前执行异步初始化,后续所有Logger都能使用正确配置:

var builder = WebApplication.CreateBuilder(args);

// 先初始化NLog配置
await NLogSetup.InitializeAsync();

// 注册NLog到日志系统(如果需要)
builder.Logging.ClearProviders();
builder.Logging.AddNLog();

// 其他启动逻辑...

var app = builder.Build();
// ...

3. 正常使用Logger

初始化完成后,可在任意类中同步获取Logger,和原来用法一致:

public class MyClass
{
    private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

    public void DoWork()
    {
        _logger.Info("执行工作任务");
        // ...
    }
}

4. 处理无法提前初始化的场景

如果无法在启动时完成初始化,可在类中使用延迟异步加载Logger:

public class MyClass
{
    private static readonly Lazy<Task<Logger>> _lazyLogger = new(async () =>
    {
        await NLogSetup.InitializeAsync();
        return LogManager.GetCurrentClassLogger();
    });

    public async Task DoWorkAsync()
    {
        var logger = await _lazyLogger.Value;
        logger.Info("异步执行工作任务");
        // ...
    }
}

核心要点

  • 不要继承NLog.Logger:NLog的Logger实例由LogManager管控,自定义子类会导致类型不兼容问题。
  • 异步配置提前做:在应用启动阶段完成异步密钥获取和NLog配置,避免业务代码中处理异步日志初始化。
  • 用Lazy<Task<T>>保证初始化唯一性:确保配置只加载一次,避免重复调用密钥接口。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 19:25:20