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

WCF服务因未处理OutOfMemoryException异常终止问题排查

排查WCF服务中NLog引发的System.OutOfMemoryException问题

我先梳理下你的场景:你开发了一个WCF服务,核心功能是接收客户消息、解析为指定格式后,通过TCP/HTTP/FTP等协议转发给其他客户端。这个Windows服务采用TPL为每个客户端创建长期运行的线程,日志方案使用NLog,配置了文件日志和事件查看器日志,具体配置如下:

<target xsi:type="File" name="flatfile" layout="${longdate} ${uppercase:${level}} ${message} ${exception:format=tostring,StackTrace}" archiveAboveSize="2000000" archiveFileName="${basedir}/logs/archive/${shortdate}-{#####}.engine.log" archiveNumbering="Sequence" archiveEvery="None" maxArchiveFiles="100" fileName="${basedir}/logs/engine.current.log" keepFileOpen="true" concurrentWrites="true" />
<target xsi:type="EventLog" name="eventlog" layout="${longdate} ${uppercase:${level}} ${message} ${exception:format=tostring} ${StackTrace}" log="Application" source="Nuance Interface Engine Service" eventId="${event-properties:EventID}" />

尽管已经配置了concurrentWrites="true",服务运行20多小时后,事件查看器中出现未处理的System.OutOfMemoryException,异常栈如下:

Application: MyWcfService.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.OutOfMemoryException
at System.Text.StringBuilder..ctor(System.String, Int32, Int32, Int32)
at NLog.Layouts.SimpleLayout.GetFormattedMessage(NLog.LogEventInfo)
at NLog.Targets.FileTarget.GetFormattedMessage(NLog.LogEventInfo)
at NLog.Targets.FileTarget.GetBytesToWrite(NLog.LogEventInfo)
at NLog.Targets.FileTarget.Write(NLog.Common.AsyncLogEventInfo[])
at NLog.Targets.Target.WriteAsyncLogEvents(NLog.Common.AsyncLogEventInfo[])
at NLog.Targets.Wrappers.AsyncTargetWrapper.ProcessPendingEvents(System.Object)
at System.Threading.TimerQueueTimer.CallCallbackInContext(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.TimerQueueTimer.CallCallback()
at System.Threading.TimerQueueTimer.Fire()
at System.Threading.TimerQueue.FireQueuedTimerCompletion(System.Object)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

紧接着1秒后,进程崩溃的错误日志:

Faulting application name: Hl7ic.Engine.View.exe, version: 18.0.1.160, time stamp: 0x5af5cd1f
Faulting module name: KERNELBASE.dll, version: 6.3.9600.18938, time stamp: 0x5a7dd8a7
Exception code: 0xe0434352
Fault offset: 0x00015ef8
Faulting process id: 0x1074
Faulting application start time: 0x01d3ea7338d9851c
Faulting application path: C:\Program Files (x86)\MyServices\MyWcfService.exe
Faulting module path: C:\windows\SYSTEM32\KERNELBASE.dll
Report Id: 59b36929-5688-11e8-80ca-005056a80aaa
Faulting package full name:
Faulting package-relative application ID:

你最初怀疑是多线程日志访问冲突,添加了concurrentWrites="true"但问题依旧,下面是我整理的排查方向和解决方案:


问题分析与排查方向

从异常栈来看,内存溢出发生在NLog格式化日志消息的环节——具体是SimpleLayout.GetFormattedMessage调用StringBuilder构造的时候,这说明根本不是文件写入的并发冲突问题,而是日志消息本身或者NLog内部的内存累积导致的。接下来给你几个重点排查方向:

1. 日志消息包含超大内容

如果你的服务在处理消息时,把整个原始消息(比如大体积的HL7报文、二进制数据)直接写入日志,会导致每次日志条目都占用大量内存,长期运行后累积引发OOM。

  • 检查${message}字段对应的日志内容,是否存在几MB甚至几十MB的超大字符串。
  • 建议对大消息做截断处理,比如只记录消息ID、关键字段,或者限制日志消息的最大长度:
    <!-- 在layout中对message做截断,保留前1000个字符 -->
    layout="${longdate} ${uppercase:${level}} ${message:maxLength=1000} ${exception:format=tostring,StackTrace}"
    

2. NLog AsyncTargetWrapper的队列溢出

默认情况下,NLog的AsyncTargetWrapper会使用无界队列来缓存待处理的日志事件。如果你的服务产生日志的速度远大于写入磁盘的速度,队列会持续膨胀,最终耗尽内存。

  • 检查NLog配置中是否显式设置了AsyncTargetWrapper的队列限制。如果没有,建议添加queueLimit和overflowAction参数:
    <targets>
      <target xsi:type="AsyncWrapper" name="asyncFlatFile" queueLimit="10000" overflowAction="Discard">
        <target xsi:type="File" name="flatfile" ... /> <!-- 原文件target配置 -->
      </target>
      <!-- 事件日志也建议添加异步包装 -->
      <target xsi:type="AsyncWrapper" name="asyncEventLog" queueLimit="5000" overflowAction="Discard">
        <target xsi:type="EventLog" name="eventlog" ... /> <!-- 原事件日志target配置 -->
      </target>
    </targets>
    
    设置队列上限后,当队列满时会丢弃旧日志,避免内存无限增长。

3. 长期运行线程的内存泄漏

你的服务使用TPL创建长期运行的线程,需要检查这些线程是否持有大对象引用(比如未释放的消息对象、日志上下文),导致GC无法回收。

  • 使用内存分析工具(比如Visual Studio内存诊断、dotMemory)捕获服务运行20小时后的内存快照,查看哪些对象占用了大量内存:
    • 检查是否有大量LogEventInfo对象堆积,说明NLog的异步队列没有及时处理。
    • 检查是否有未释放的消息对象、网络连接资源等。

4. NLog版本问题

旧版本的NLog可能存在内存泄漏的bug,比如在处理异步日志、大日志消息时的内存管理问题。

  • 检查当前使用的NLog版本,建议升级到最新的稳定版本(比如4.7.x及以上),新版本修复了很多内存相关的问题。

5. 事件日志的性能瓶颈

事件日志的写入速度远慢于文件日志,如果大量日志写入事件日志,可能导致NLog的异步队列阻塞,进而累积内存。

  • 评估事件日志的必要性,是否需要记录所有级别的日志?可以降低事件日志的日志级别(比如只记录Error和Fatal),减少写入量:
    <rules>
      <logger name="*" minlevel="Error" writeTo="eventlog" />
      <logger name="*" minlevel="Info" writeTo="flatfile" />
    </rules>
    

验证步骤

  1. 先对日志消息做截断处理,观察服务是否还会出现OOM。
  2. 添加AsyncTargetWrapper的队列限制,控制内存占用。
  3. 升级NLog到最新版本,排除已知bug。
  4. 用内存分析工具监控内存使用,定位泄漏点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:22:42