日志分析全攻略
概述
日志是 DarraRT 唯一不依赖现场复现的故障线索。PLC 产品不提供断点/单步调试, 日志系统承担了"事后还原"的全部职责。本文档系统梳理 DarraRT 的四类日志、 统一等级体系、时间戳对齐规则、按模块过滤技巧以及常见异常根因速查表。
日志质量直接决定故障处置速度。一个优秀的日志系统应当让工程师在 10 分钟内, 仅凭日志 就能还原异常发生前 30 秒的全部状态。
适用场景
- 现场无法复现的偶发崩溃
- 产线恢复后的根因分析
- 升级版本前的回归验证
- 远程技术支持协作
四类日志体系
DarraRT 日志按进程归属分为四类, 路径各异, 必须同步分析才能拼出完整时间线。
| 类别 | 路径 | 进程 | 典型内容 |
|---|---|---|---|
| IDE 日志 | %AppData%/DarraPLC/logs/ide_*.log | Darra.PLC.IDE.exe | UI 事件 / 编译 / 下载 |
| Service 日志 | <Service.exe 目录>/logs/service_*.log | Darra.PLC.Service.exe | 永驻中间层 / 健康 / 部署 |
| EtherCAT 日志 | Darra_EtherCAT_Master/debug_info.txt | EtherCAT SDK 宿主 | 主站状态 / WKC / DC |
| 驱动日志 | DbgView.exe 实时抓取 | 内核态三驱动 | DbgPrint 输出 |
日志路径速查
| 路径 | 环境变量展开 | 备注 |
|---|---|---|
%AppData%/DarraPLC/logs/ | C:\Users\<user>\AppData\Roaming\DarraPLC\logs\ | 当前用户 |
%LocalAppData%/DarraPLC/dumps/ | C:\Users\<user>\AppData\Local\DarraPLC\dumps\ | 崩溃转储 |
<Service.exe>/logs/ | C:\Program Files\DarraRT\Service\logs\ | 系统服务 |
<EtherCAT.exe>/debug_info.txt | 根据部署位置 | 追加模式 |
%ProgramData%/DarraPLC/last_layout.xml | Dock 布局持久化 | 布局异常看这个 |
统一日志等级
所有 DarraRT 日志使用 LogManager.Get("模块名"), 共 6 个等级:
| 等级 | 数值 | 用途 | 生产环境 |
|---|---|---|---|
| Trace | 0 | 细粒度追踪 (每次函数进入/退出) | 关闭 |
| Debug | 1 | 调试信息 (变量值/分支判定) | 关闭 |
| Info | 2 | 正常事件 (启动/连接/下载) | 默认开 |
| Warn | 3 | 可恢复异常 (重连/降级) | 默认开 |
| Error | 4 | 业务错误 (未抛到顶层) | 默认开 |
| Fatal | 5 | 进程级崩溃 | 默认开 |
日志格式标准
[2026-04-18 09:15:32.481] [Info] [MainForm] IDE 启动完成, 版本 25.2.0
[2026-04-18 09:15:32.512] [Warn] [DockManager] 布局文件版本不匹配, 已重建
[2026-04-18 09:15:33.127] [Error] [Compiler] 语法错误 FB_Motor:12 expected ';'
字段定义:
时间戳: ISO 8601 + 毫秒, 本地时区等级:Trace/Debug/Info/Warn/Error/Fatal模块: 方括号, 与LogManager.Get("xxx")参数一致消息: 自由文本, 允许多行 (换行前置 4 空格)
前置条件与工具
| 工具 | 用途 |
|---|---|
DbgView.exe (SysInternals) | 捕获三驱动 DbgPrint |
BareTail / glogg | 实时尾部跟踪 |
ripgrep (rg) | 快速跨文件搜索 |
| PowerShell 5.1+ | Select-String 过滤 |
| Timeline Explorer | Eric Zimmerman, 时间线对齐神器 |
诊断步骤
步骤 1: 定位时间窗
故障发生时间 ± 5 分钟 = 有效时间窗。过大则噪音太多, 过小易遗漏前置原因。
# 示例: 故障发生在 2026-04-18 09:15:00
$start = [datetime]"2026-04-18 09:14:30"
$end = [datetime]"2026-04-18 09:15:30"
步骤 2: 按等级初筛 (Fatal → Error → Warn)
# IDE 日志
Select-String -Path "$env:APPDATA\DarraPLC\logs\ide_*.log" `
-Pattern "\[Fatal\]|\[Error\]" | Select-Object -Last 200
预期输出: 若有 Fatal, 跳步骤 4 看堆栈; 若只有 Error, 进入步骤 3。
步骤 3: 按模块收窄
IDE 常用模块名:
MainForm — 主窗口生命周期
DockManager — DevExpress Dock 布局
RibbonController — Ribbon 按钮绑定
Compiler — IEC 编译器
Downloader — 程序下载
OnlineMonitor — 在线监视
VariableBus — 变量总线
# 只看 DockManager 异常
Select-String -Path "$env:APPDATA\DarraPLC\logs\ide_*.log" `
-Pattern "\[DockManager\]" -Context 0,2
步骤 4: 拉异常堆栈 (Fatal/Error)
DarraRT 异常写出规范:
[2026-04-18 09:15:33.481] [Fatal] [MainForm] 未处理异常
类型: System.InvalidOperationException
消息: Collection was modified; enumeration operation may not execute.
栈:
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at Darra.PLC.IDE.Panels.ProjectTree.RefreshAll() in ProjectTree.cs:line 213
at Darra.PLC.IDE.MainForm.OnProjectChanged() in MainForm.cs:line 487
判定规则:
- 堆栈第一行 = 最内层触发点, 通常就是 bug 所在文件/行号
- 堆栈最后一行 = 消息传入顶层, 看调用来源
- 忽略
System.*/Microsoft.*直到看见Darra.*才是代码
步骤 5: 四日志时间线对齐
故障涉及 PLC + EtherCAT + 驱动时, 必须把四份日志的相同时间窗放一起看。
# 把四份日志裁剪到同一分钟窗口, 合并成一个大文件按时间排序
$window = "2026-04-18 09:15"
Get-Content ide_*.log, service_*.log, debug_info.txt |
Where-Object { $_ -match [regex]::Escape($window) } |
Sort-Object |
Out-File merged_09_15.log
预期输出: 合并后一行一事件, 肉眼即可看出"IDE 先 Warn, 2 秒后 Service Error,
再 200ms 后驱动 DbgPrint: NDIS send fail"这种因果链。
步骤 6: 驱动日志 (DbgView) 抓取
1. 以管理员启动 DbgView.exe
2. Capture → Capture Kernel (勾选)
3. Capture → Capture Events (勾选)
4. Edit → Filter/Highlight → Include: Darra
5. File → Log to File... → kernel_YYYYMMDD.log
三驱动的 DbgPrint 前缀:
[DarraRT]— Global 驱动[DarraRT_Eth]— Eth 驱动[DarraRT_PLC]— PLC 驱动
常见异常根因速查表
| 异常消息 | 根因 | 定位方法 | 解决 |
|---|---|---|---|
NullReferenceException | 字段未初始化 / 时序错位 | 堆栈第一行定位变量 | 构造函数初始化 or null 防护 |
Object reference not set | 同上 | 同上 | 同上 |
Collection was modified | foreach 中改集合 | 栈里找 MoveNext | .ToArray() 快照再遍历 |
InvalidOperationException: Cross-thread | UI 线程越界 | Control.InvokeRequired | BeginInvoke 封装 |
DockPanel.DockTo ... not supported | DevExpress API 过时 | 报错版本号 | 换 DockController.Dock |
MSB3027 The process cannot access | 文件被 Service 锁 | handle.exe <dll> | pre-build taskkill |
The type initializer for 'X' threw | 静态构造函数异常 | InnerException | 看被初始化类的 static ctor |
A task was canceled | CancellationToken 触发 | 谁 Cancel 的 | 延长超时 or 合理 Cancel |
The operation has timed out | 网络/Service 未响应 | netstat 看端口 | 重启 Service / 扩大超时 |
ThreadAbortException | Thread.Abort 调用 | 栈里找 Abort | 禁用 Abort, 用 Cancel |
OutOfMemoryException | 真泄漏 / 大对象堆碎片 | dotnet-dump | Dispose 及时 / 分批处理 |
EntryPointNotFoundException | DLL 版本不匹配 | Fusion Log | 清理 bin + rebuild |
BadImageFormatException | x86/x64 混用 | 查项目 Platform | 统一 x64 |
FileNotFoundException: X.dll | 依赖缺失 | ProcMon NAME NOT FOUND | 补 NuGet 包 |
TypeLoadException | 接口不匹配 | Fusion Log | 重新编译依赖方 |
日志时间戳对齐规则
DarraRT 四类日志使用的时间源不同, 分析时要清楚差异:
| 日志 | 时间源 | 精度 | 跨机一致性 |
|---|---|---|---|
| IDE | DateTime.Now (用户进程) | ms | 不一致 |
| Service | DateTime.Now (系统服务) | ms | 不一致 |
| EtherCAT | DateTime.UtcNow + 毫秒 | ms | UTC, 需+时区 |
| 驱动 | TSC (KeQueryPerformanceCounter) | 100ns | 单机精确 |
对齐技巧: 在 IDE 日志里故意打一条"锚点日志":
[2026-04-18 09:15:00.000] [Info] [Anchor] ANCHOR TSC=12345678901234
然后用这个 TSC 值反推驱动日志中的相对时间, 即可做毫秒级对齐。
按模块过滤技巧
推荐每个模块独立 LogManager 实例, 分析时按模块过滤。常见过滤模式:
# 只看下载流程
rg "\[Downloader\]|\[Compiler\]" ide_*.log
# 看 EtherCAT 在 Init -> OP 过程
rg "\[ECAT\].*(Init|PreOP|SafeOP|OP)" debug_info.txt
# 看 PLC 扫描周期 (每次扫描都有 Debug)
rg "\[PlcCycle\] cycle=\d+" service_*.log | Select-Object -Last 100
高级技巧
- 日志反向切片:
Select-String -Tail 0 -Context 30,0可以抓到 Error 前的 30 行上下文, 还原事故前状态机。 - 结构化日志: Service 支持
JsonLogEnabled=true, 输出logs/json/*.ndjson, 可直接导入 Kibana / DuckDB 做 SQL 查询。 - 崩溃转储: Fatal 级日志同时触发
MiniDumpWriteDump, 文件落在%LocalAppData%/DarraPLC/dumps/<pid>_<ts>.dmp, 用 WinDbg 打开看线程栈。 - 对比分析: 把正常运行一小时的日志与异常运行一小时并列打开, 看模块日志频率差异, 异常模块的日志频率会有明显突变。
禁忌
- 禁止: 生产环境开
Trace/Debug级 — 日志洪水会填满 SSD - 禁止: 在
catch { }写空块 — 至少log.Error(ex) - 禁止: 捕获
Exception后继续抛裸 throw — 用throw;保持栈 - 禁止: 用
Console.WriteLine— 无时间戳/模块/等级, 不可检索
相关文档
