.NET6下动态添加FileTarget后NLog RichTextBoxTarget触发空引用异常
环境与配置
团队正在将Windows Form应用从.NET Framework 4.6.2迁移至.NET 6,使用NLog(集成Microsoft.Extensions.Logging)实现日志写入文件及主窗体RichTextBox,依赖包版本:
- NLog.Extensions.Logging 5.3.2
- NLog.Windows.Forms 5.2.0
NLog配置文件:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="false" internalLogFile="${basedir}/LogFiles/InternalLog.txt" internalLogLevel="Info"> <extensions> <add assembly="NLog.Windows.Forms"/> </extensions> <targets> <default-wrapper xsi:type="AsyncWrapper" timeToSleepBetweenBatches="500" /> <target name="file" xsi:type="File" layout="${longdate}|${level}|${message}|${exception}" fileName="${basedir}/LogFiles/file.txt" archiveAboveSize="1048576" archiveEvery="Day" maxArchiveFiles="21" archiveFileName="${basedir}/LogFiles/Errorlog/file.{####}.txt" archiveNumbering="Sequence" encoding="utf-8"/> <target name="trace" xsi:type="Trace" layout="${longdate}|${level}|${message}|${exception}" /> <target name="richTextBox" xsi:type="RichTextBox" layout="${message}${exception}" controlName="rtbStatus" formName="BaseForm" allowAccessoryFormCreation="false" messageRetention="All" maxLines="10000"/> </targets> <rules> <logger name="ErrorLog" minlevel="Info" writeTo="file,trace,richTextBox"/> </rules> </nlog>
动态创建FileTarget代码
为用户配置的通信应用动态生成调试模拟文件,代码实现:
public NLogSimFileLogger(string category, string name) { try { this._TargetName = category + "-" + name; FileTarget target = new FileTarget(); target.Layout = "${message}"; target.ReplaceFileContentsOnEachWrite = false; target.ArchiveAboveSize = 1048576; target.ArchiveEvery = FileArchivePeriod.Day; target.MaxArchiveFiles = 21; target.ArchiveFileName = "${basedir}/LogFiles/Sims/" + category + "/" + name + "{####}.txt"; target.ArchiveNumbering = ArchiveNumberingMode.Sequence; target.FileName = "${basedir}/LogFiles/Sims/" + category + "/" + name + ".sim"; target.KeepFileOpen = true; target.ConcurrentWrites = true; target.Name = this._TargetName; LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Fatal, target, this._TargetName); LogManager.ReconfigExistingLoggers(); this._Logger = LoggerFactory.Create(configure => configure.AddNLog(LogManager.Configuration)).CreateLogger(this._TargetName); } catch(Exception ex) { // 异常处理逻辑 } }
异常信息
创建动态FileTarget时,RichTextBoxTarget的SendTheMessageToRichTextBox方法触发NullReferenceException,异常栈:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object. at NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(String logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent) --- End of inner exception stack trace --- at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Delegate.DynamicInvokeImpl(Object[] args) at System.Delegate.DynamicInvoke(Object[] args) at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbacks() at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.TextBoxBase.WndProc(Message& m) at System.Windows.Forms.RichTextBox.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam) at Interop.User32.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at MyLibrary.SingleInstance..ctor(Form mainForm, SingleInstanceTypes type) in C:\SandBox\Libraries\UI\Lear.UI\SingleInstance.cs:line 104 at MyApp.Program.Main(String[] args) in C:\Sandbox\LPS3Services\Projects\MessageRouter\LPS.MessageRouter.Monitor\Program.cs:line 38
NLog内部日志
2023-07-28 11:56:40.0448 Info Message Template Auto Format enabled 2023-07-28 11:56:40.0564 Info Loading assembly name: NLog.Windows.Forms 2023-07-28 11:56:40.0767 Info NLog.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c. File version: 5.2.0.532. Product version: 5.2.0+2d57aed1aadb6fc0d0ffaa67674ba6ad00baf4cd. GlobalAssemblyCache: False 2023-07-28 11:56:40.1461 Info Registered target NLog.Targets.Wrappers.AsyncTargetWrapper(Name=file) 2023-07-28 11:56:40.1461 Info Registered target NLog.Targets.Wrappers.AsyncTargetWrapper(Name=trace) 2023-07-28 11:56:40.1461 Error Forbidden usage of RetentionStrategy (All) when MaxLines is not set 2023-07-28 11:56:40.1461 Info Registered target NLog.Targets.Wrappers.AsyncTargetWrapper(Name=richTextBox) 2023-07-28 11:56:40.1849 Info Validating config: TargetNames=file, trace, richTextBox, ConfigItems=30, FilePath=.\MyApp\bin\Debug\net6.0-windows\NLog.config 2023-07-28 11:56:40.2046 Info Form BaseForm not found, waiting for ReInitializeAllTextboxes. 2023-07-28 11:56:40.2113 Info Configuration initialized. 2023-07-28 11:56:40.2113 Info NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c. File version: 5.2.2.1914. Product version: 5.2.2+51cc02a2dd780a9a01eb6747de52071d78e2f506. GlobalAssemblyCache: False 2023-07-28 11:56:40.5062 Info Executing ReInitializeAllTextboxes for Form MyApp.MainForm, Text: MainForm 2023-07-28 11:56:40.5062 Info Attaching target richTextBox_wrapped to textbox BaseForm.rtbStatus 2023-07-28 11:56:42.6583 Info Registered target NLog.Targets.FileTarget(Name=OPC-TagLog) 2023-07-28 11:56:42.6583 Info Validating config: TargetNames=file, trace, richTextBox, OPC-TagLog, ConfigItems=38, FilePath=.\MyApp\bin\Debug\net6.0-windows\NLog.config 2023-07-28 11:56:42.6583 Info Closing old configuration. 2023-07-28 11:56:42.7235 Info Validating config: TargetNames=file, trace, richTextBox, OPC-TagLog, ConfigItems=38, FilePath=.\MyApp\bin\Debug\net6.0-windows\NLog.config 2023-07-28 11:56:42.7235 Info Attaching target richTextBox_wrapped to textbox BaseForm.rtbStatus 2023-07-28 11:56:42.8612 Info Registered target NLog.Targets.FileTarget(Name=AS400-AS400 Resolution Connection Data Source0) 2023-07-28 11:56:42.8612 Info Validating config: Targets=5, ConfigItems=46, FilePath=.\MyApp\bin\Debug\net6.0-windows\NLog.config 2023-07-28 11:56:42.8612 Info Closing old configuration. 2023-07-28 11:56:42.8889 Info Validating config: Targets=5, ConfigItems=46, FilePath=.\MyApp\bin\Debug\net6.0-windows\NLog.config 2023-07-28 11:56:42.8889 Info Attaching target richTextBox_wrapped to textbox BaseForm.rtbStatus
已尝试的解决方案
- 用
LogManager.SuspendLogging包裹FileTarget创建逻辑 - 移除
LogManager.ReconfigExistingLoggers()调用
问题仅在.NET6中出现,.NET Framework 4.6.2版本无此异常,推测是RichTextBoxTarget重新初始化时,其他线程尝试写入日志导致空引用。
推荐解决方案
1. 安全更新NLog配置
避免直接修改全局配置实例,使用配置副本更新,减少对已有Target的影响:
public NLogSimFileLogger(string category, string name) { try { this._TargetName = category + "-" + name; FileTarget target = new FileTarget { Layout = "${message}", ReplaceFileContentsOnEachWrite = false, ArchiveAboveSize = 1048576, ArchiveEvery = FileArchivePeriod.Day, MaxArchiveFiles = 21, ArchiveFileName = "${basedir}/LogFiles/Sims/" + category + "/" + name + "{####}.txt", ArchiveNumbering = ArchiveNumberingMode.Sequence, FileName = "${basedir}/LogFiles/Sims/" + category + "/" + name + ".sim", KeepFileOpen = true, ConcurrentWrites = true, Name = this._TargetName }; // 创建配置副本,避免修改原配置时的线程冲突 var configCopy = LogManager.Configuration.Copy(); configCopy.AddRule(LogLevel.Debug, LogLevel.Fatal, target, this._TargetName); // 替换全局配置,自动触发重新配置 LogManager.Configuration = configCopy; // 复用已有的ILoggerFactory,避免重复初始化 this._Logger = LoggerFactory.Create(configure => configure.AddNLog()).CreateLogger(this._TargetName); } catch(Exception ex) { // 异常处理逻辑 } }
2. 强制重新绑定RichTextBoxTarget
配置更新后,手动触发RichTextBoxTarget的控件重新绑定,确保引用有效:
LogManager.Configuration = configCopy; // 强制重新初始化所有文本框目标 RichTextBoxTarget.ReInitializeAllTextboxes();
3. 升级NLog.Windows.Forms版本
检查NLog.Windows.Forms的最新版本,官方可能已修复.NET6下的线程安全问题,升级至最新稳定版。
4. 修正配置警告
内部日志中的Forbidden usage of RetentionStrategy (All) when MaxLines is not set警告,确认配置中maxLines属性正确生效,可尝试显式设置messageRetention="Limit"并配合maxLines,避免配置加载异常:
<target name="richTextBox" xsi:type="RichTextBox" layout="${message}${exception}" controlName="rtbStatus" formName="BaseForm" allowAccessoryFormCreation="false" messageRetention="Limit" maxLines="10000"/>
5. 确保日志写入的线程安全
对于RichTextBoxTarget,确保所有日志写入操作在UI线程上下文执行,或者配置AsyncWrapper时设置overflowAction="Discard",避免重新初始化时队列积压未处理消息导致空引用。
内容的提问来源于stack exchange,提问作者foleymas

