基于异步工厂方法配置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
相关产品推荐
相关产品推荐

