从零搭建工业控制系统(二十八):日志系统——出了问题靠它救命
2026/9/14 21:19:56 网站建设 项目流程

日志系统:出了问题靠它救命

这是「从零搭建工业控制系统」系列第28篇。前面所有功能模块都依赖日志,这篇终于讲日志系统本身——log4net分级别输出、自定义UI Appender、性能优化、调试技巧。


没有日志的深夜

有一次凌晨两点现场报故障,说程序闪退了。我问有没有日志,对方说"有啊,但是Logs文件夹是空的"。

远程连上去一看,log4net配置文件没被复制到输出目录。程序里到处调_logger.Info(),但所有日志都写到了一个不存在的路径。

从那以后我做了三件事:配置文件设为AlwaysCopy、启动时检查Logs目录存在、加了一个UI日志面板作为兜底——就算文件日志挂了,界面上至少还能看到。


log4net配置:分级别分文件

项目用log4net按级别分文件输出:ERROR、WARN、INFO、DEBUG各一个文件。

<log4net><!-- 错误日志 --><appendername="errorFileAppender"type="log4net.Appender.RollingFileAppender"><filtertype="log4net.Filter.LevelMatchFilter"><levelToMatchvalue="ERROR"/></filter><filtertype="log4net.Filter.DenyAllFilter"/><filevalue="Logs\err.log"/><rollingStylevalue="Composite"/><datePatternvalue="yyyyMMdd"/><maxSizeRollBackupsvalue="30"/><maximumFileSizevalue="50MB"/><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger{1} - %message%newline"/></layout></appender></log4net>

日志格式:2024-12-20 14:30:25.123 [1] ERROR ValveExecutor - 阀门TV状态变化超时

每个级别都有独立的文件和滚动策略:

级别文件最大文件数单文件上限
ERRORerr.log30个50MB
WARNwarn.log15个30MB
INFOinfo.log10个100MB
DEBUGdebug.log3个50MB
PERFperf.log7个20MB

ERROR保留最多(30个),因为排查问题最需要历史错误记录。DEBUG只保留3个,调试日志量大但价值低。


缓冲Appender:性能优化

工业系统每秒可能产生上百条日志,直接写文件会拖慢主线程。用BufferingForwardingAppender批量写入:

<appendername="bufferedErrorAppender"type="log4net.Appender.BufferingForwardingAppender"><bufferSizevalue="5"/><lossyvalue="false"/><fixvalue="Partial"/><evaluatortype="log4net.Core.LevelEvaluator"><thresholdvalue="ERROR"/></evaluator><appender-refref="errorFileAppender"/></appender>
  • bufferSize=5:攒够5条一起写
  • lossy=false:不丢日志
  • fix=Partial:只序列化必要字段,减少开销
  • evaluator threshold=ERROR:遇到ERROR立即刷新缓冲区

ERROR级别缓冲5条就刷,DEBUG缓冲50条加5秒超时刷新。错误日志要快,调试日志可以慢。


MinimalLock:多进程不抢锁

<lockingModeltype="log4net.Appender.MinimalLockDeleteEmpty"/>

默认的ExclusiveLock会锁住文件,其他进程读不了。MinimalLock写完就释放锁,日志查看工具可以实时读取。DeleteEmpty额外处理空文件——滚动后如果文件为空就删掉,不占空间。


UI日志Appender:界面实时看日志

这是项目里最有用的自定义组件。UILogAppender继承AppenderSkeleton,把日志转发到界面的LogView面板:

publicclassUILogAppender:AppenderSkeleton{privatestaticbool_isInitialized=false;[ThreadStatic]privatestaticbool_isAppending;publicstaticvoidSetInitialized(boolinitialized){_isInitialized=initialized;}protectedoverridevoidAppend(LoggingEventloggingEvent){if(!_isInitialized||_isAppending)return;try{_isAppending=true;stringmessage=RenderLoggingEvent(loggingEvent);Application.Current?.Dispatcher?.BeginInvoke(newAction(()=>{LogViewModel.Current?.AddLogFromAppender(message);}));}finally{_isAppending=false;}}}

几个关键设计:

1._isInitialized延迟激活:LogViewModel构造完成后才设为true。之前没这个标志,程序启动阶段LogViewModel还没创建,日志转发报NullRef。

2._isAppending防循环[ThreadStatic]标志位防止LogViewModel添加日志时触发新的日志事件形成死循环。

3.Dispatcher.BeginInvoke:日志可能在后台线程产生,用Dispatcher切到UI线程更新界面。


log4net配置中注册UI Appender

<appendername="uiLogAppender"type="MyApp.Logging.UILogAppender, MyApp"><thresholdvalue="INFO"/></appender>

UI只显示INFO及以上级别。DEBUG日志太频繁,刷屏影响操作员看关键信息。

配置里还可以通过ShowUILog配置项控制是否启用:

publicboolShowUILog{get;set;}=true;

多环境配置

项目维护了多套log4net配置:

Configs/Log4netConfig/ ├── log4net.config # 默认 ├── log4net-development.config # 开发环境:DEBUG全开 ├── log4net-production.config # 生产环境:INFO以上 └── log4net-optimized.config # 性能优化:WARN以上

开发环境开DEBUG看详细信息,生产环境只留INFO减少IO压力。切换时改App.config的引用路径就行。


调试技巧:按关键词搜日志

排查问题时不是从头看日志,是搜关键词:

# 搜所有ERRORSelect-String-Path"Logs\err*.log"-Pattern"ValveExecutor"# 搜特定时间段的阀门操作Select-String-Path"Logs\info*.log"-Pattern"2024-12-20 14:3.*Valve"# 搜跨线程异常Select-String-Path"Logs\err*.log"-Pattern"InvalidOperationException"

调用方法名显示

配置里有个开关控制是否在UI日志里显示调用方法名和行号:

publicboolShowCallerInfoInUILog{get;set;}=false;

默认关闭,因为获取调用栈有性能开销。排查疑难bug时临时打开,能看到每条日志是哪个方法输出的。


日志踩坑清单

现象解决
配置文件没复制Logs文件夹空Build Action设为AlwaysCopy
启动时NullRefLogViewModel还没创建_isInitialized延迟激活
日志死循环界面卡死[ThreadStatic]防循环
文件被锁查看工具读不了MinimalLockDeleteEmpty
DEBUG刷屏操作员看不到关键信息UI只显示INFO以上

本篇小结

知识点关键做法
分级别输出每个级别独立Appender+文件
缓冲写入BufferingForwardingAppender批量
UI日志自定义UILogAppender转发到界面
防循环[ThreadStatic]标志位
多环境dev/prod/optimized三套配置
文件锁MinimalLockDeleteEmpty
日志搜索Select-String按关键词搜

日志系统的核心:出了问题能查到,不出问题不拖慢系统。


下期预告

第29篇:项目打包与部署

最后一篇,说打包——ClickOnce、混淆、配置文件打包、现场部署流程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询