记录审计日志
记录审计日志
当你的应用程序与 PLC 之间出现问题时,真正值得关注的问题几乎从来不是“我的代码做了什么?”,而是“究竟有哪些内容通过网络传输出去,又传回了什么?”。
审计日志正是用来回答这个问题的。开启它之后,PLC4X 会将一次连接的完整、有序的追踪记录写入文件:你发起的 API 调用、驱动据此构建的协议消息、发送到网络上的原始字节、返回的字节,以及这期间的每一次状态变化。
无需改动任何一行代码即可启用它,只需设置一个连接串参数即可。
| 审计日志是 PLC4J(Java)的一部分。PLC4Go、PLC4Py 和 PLC4C 没有等价的功能。 |
|---|
开启审计日志
在连接串中添加 log.audit-log-file,并指向你希望写入的文件:
modbus-tcp://192.168.1.100?log.audit-log-file=/tmp/modbus-debug.log这就是全部的配置。该参数适用于所有驱动和所有传输层,并且可以像其他连接字符串选项一样自由组合使用:
s7://10.0.0.1?remote-rack=0&remote-slot=1&log.audit-log-file=/var/log/plc4x/s7-line-3.log父目录会在尚不存在时自动为你创建。
如果该参数缺失或为空,审计日志功能将关闭,且完全不产生任何开销——驱动会持有一个空实现,它甚至不会去格式化那些它根本不写入的消息。
类路径上需要什么
审计日志被拆分为 API 和实现两部分,这样普通部署就无需携带写文件的机制。驱动针对 API 进行编译;实现在运行时通过反射方式加载——前提是它确实存在。
若要真正生成文件,请以运行时作用域添加实现:
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-utils-audit-log-impl</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>该实现通过 Logback 写入日志,而 PLC4X 将其声明为 provided——因此 logback-classic 也必须出现在你的运行时类路径中。如果你的应用程序已经把 Logback 用作 SLF4J 绑定(大多数如此),那你已经有了。如果你使用的是其他绑定,就在需要审计日志期间,将 ch.qos.logback:logback-classic 一并加入其中。
这是唯一会悄无声息地失败的一环。缺少实现模块时,PLC4X 只会通过你平常使用的日志器在 INFO 级别输出 Audit log implementation not found on classpath. Audit logging will be disabled.,然后照常运行,不会生成任何审计文件。如果你配置了路径却没有得到文件,请先检查这条消息。 |
|---|
当它确实找到了所有依赖时,你看到的将是这样:
INFO org.apache.plc4x.java.utils.auditlog.api.AuditLog -- Audit log implementation found on classpath. Audit logging enabled.
INFO org.apache.plc4x.java.utils.auditlog.api.AuditLog -- Audit log initialized for file: /tmp/modbus-debug.log日志的样子
每一行的格式都相同:
[timestamp] [eventType] [source] messagesource 是产生该条目的驱动协议代码——例如 modbus-tcp、s7、ads。如果你把两个连接指向同一个文件,正是它让你能够区分这两个连接。
下面是一条完整轨迹,记录了某个连接打开、读取一个保持寄存器然后再次关闭的全过程:
[2026-09-05 18:33:37.883] [SYSTEM] [modbus-tcp] Creating Transport with config: {"connectTimeout":5000,"readTimeout":0,"writeTimeout":0,"tcpNoDelay":true,"keepAlive":false,"sendBufferSize":81920,"receiveBufferSize":81920,"localAddress":null,"localPort":0,"defaultPort":502}
[2026-09-05 18:33:37.895] [CONNECT] [modbus-tcp] Connected to: localhost:61188 with local address: localhost:61189
[2026-09-05 18:33:37.900] [CONFIG] [modbus-tcp] Starting connection using configuration: ModbusTcpConfiguration{requestTimeout=5000, unitIdentifier=1, pingAddress=4x00001:BOOL, defaultPayloadByteOrder=BIG_ENDIAN, maxCoilsPerRequest=2000, maxRegistersPerRequest=125}
[2026-09-05 18:33:37.907] [SYSTEM] [modbus-tcp] Started async event-driven receive mode
[2026-09-05 18:33:37.907] [CONNECT] [modbus-tcp] Modbus TCP connection established
[2026-09-05 18:33:37.907] [SYSTEM] [modbus-tcp] Connection state changed: CONNECTED
[2026-09-05 18:33:37.912] [API_REQUEST] [modbus-tcp] Read request: [temperature]
[2026-09-05 18:33:37.921] [OUTGOING_MESSAGE] [modbus-tcp] Sending Modbus TCP request, txId=1
[2026-09-05 18:33:37.932] [OUTGOING_BYTES] [modbus-tcp] Write: 000100000006010300000001
[2026-09-05 18:33:37.932] [INCOMING_BYTES] [modbus-tcp] 00010000000701030412345678
[2026-09-05 18:33:37.943] [INCOMING_MESSAGE] [modbus-tcp] Received Modbus TCP response, txId=1
[2026-09-05 18:33:37.945] [API_RESPONSE] [modbus-tcp] Block read response: 4 bytes for 1 tags
[2026-09-05 18:33:37.954] [CLOSE] [modbus-tcp] Connection closed
[2026-09-05 18:33:37.954] [SYSTEM] [modbus-tcp] Connection state changed: DISCONNECTED从上往下读,那就是一个请求的完整生命周期:你的 read() 调用(API_REQUEST)、驱动构建的 Modbus PDU(OUTGOING_MESSAGE)、发出的十二个字节(OUTGOING_BYTES)、返回的十三个字节(INCOMING_BYTES)、解析后的响应(INCOMING_MESSAGE),以及最终交还给你的那些值(API_RESPONSE)。
OUTGOING_BYTES 和 INCOMING_BYTES 这两行是纯十六进制。把其中一行粘贴到 Wireshark 的 "Import from Hex Dump",你就能得到与抓包相同的解析结果——而不必在出问题时正好守在那台机器旁。 |
|---|
事件类型
| 事件类型 | 产生它的来源 |
|---|---|
CONFIG | 启动该连接时所用的配置。 |
SYSTEM | 传输层设置、接收模式选择、连接状态变化。 |
CONNECT | 传输层与协议层的连接建立。 |
API_REQUEST | 你发起的一次调用:读、写、订阅、浏览、ping。 |
API_RESPONSE | 交还给你代码的结果。 |
OUTGOING_MESSAGE | 一条即将发出的、已解析的协议消息。 |
OUTGOING_BYTES | 写入线路的原始字节,以十六进制表示。 |
INCOMING_BYTES | 从线路读出的原始字节,以十六进制表示。 |
INCOMING_MESSAGE | 一条收到的、已解析的协议消息。 |
API_EVENT | 异步事件,例如订阅通知。 |
CLOSE | 连接拆除。 |
ERROR | 任何失败的情况——意外断开、协议错误、超时。 |
每种事件类型能拿到多少细节,取决于具体驱动。对于所有驱动,框架都会统一贡献 API_*、CONFIG、CLOSE 以及连接状态相关条目;字节级和消息级条目则来自传输层与驱动本身,越是成熟的驱动,记录得越详细。
滚动与文件大小
繁忙连接的字节级跟踪增长得很快,因此文件会自行滚动:
- 当前文件达到 10 MB 时会开启一个新文件,此外每天也会开启一次
- 归档文件命名为
{filename}.{date}.{index}.gz——例如modbus-debug.log.2026-09-05.0.gz - 保留 30 天的历史,总量上限为 1 GB
- 归档文件采用 GZIP 压缩
以上这些都不可配置。审计日志是一件你临时打开一阵子的调试工具,而不是一条常驻的日志管道——如果你想永久保留跟踪记录,请把它们复制到别处。
从代码中读写日志
PLC4X 构建的每个连接都实现了 AuditLogProvider,因此如果需要,同一个日志对象也可以从你自己的代码中获取:
import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
import org.apache.plc4x.java.utils.auditlog.api.AuditLogEventType;
import org.apache.plc4x.java.utils.auditlog.api.AuditLogProvider;
try (PlcConnection connection = PlcDriverManager.getDefault().getConnectionFactory()
.getConnection("modbus-tcp://192.168.1.100?log.audit-log-file=/tmp/modbus-debug.log")) {
if (connection instanceof AuditLogProvider auditLogProvider) {
AuditLog auditLog = auditLogProvider.getAuditLog();
if (auditLog.isEnabled()) { (1)
auditLog.write(AuditLogEventType.SYSTEM, "Starting batch 47"); (2)
auditLog.write(AuditLogEventType.SYSTEM, "Recipe", recipe); (3)
}
}
// ... your reads and writes, which land in the same file, in order ...
}| 1 | 成本低廉,值得在构建可能用不到的消息之前先检查一下。 |
|---|---|
| 2 | 你的条目会以相同的 [timestamp] [type] [source] 前缀出现在跟踪记录中。 |
| 3 | 三参数形式会使用 Jackson 将对象序列化为 JSON,无法序列化时则回退到 toString()。 |
这是将跟踪记录与你自己的应用程序关联起来的实用技巧:标记一批操作、一次配方变更或一个班次的起点,你就能在 10 MB 的十六进制跟踪记录中再次找到该位置。
如果你需要一份不与连接绑定的审计日志——例如用于你自己的工具——可以直接这样构建:
AuditLog auditLog = AuditLog.builder()
.withSource("line-3-supervisor")
.withAuditLogFile("/var/log/plc4x/supervisor.log")
.build();
auditLog.write(AuditLogEventType.SYSTEM, "Supervisor started");
// ...
auditLog.close();构建器遵循与连接字符串相同的规则:未提供文件路径,或类路径上没有相应实现,那么得到的将是空操作实例,而不是异常。
将追踪记录转化为测试
构建审计日志时还考虑了第二个用途。一条同时包含原始字节和已解析消息的真实会话追踪记录,几乎就是把该会话复现为驱动测试所需的全部内容——这正是你在某台无法触及的机器上见过的缺陷,变成每次构建都会运行的回归测试的方式。
如果你遇到驱动缺陷,随问题单附上审计日志是你能提供的最有价值的东西。关于哪些内容还有帮助,请参见 准备问题与缺陷报告。
审计日志记录了你的应用向 PLC 说出的全部内容,以及 PLC 回复的全部内容。请据此对待它:
- 负载数据完整无缺。 你读取的每个值,尤其是你写入的每个值,都会以十六进制形式记录,通常还附带解析后的消息。
CONFIG行包含连接配置。 驱动会将声明为机密的参数做掩码处理——OPC UA 密码会显示为 -——但这个文件从来就不是设计用来脱敏的边界。在将其附到公开问题单之前,请先阅读开头几行。- TLS 会话密钥,如果你请求了的话。
tls-psk.log-session-keys传输选项会以SSLKEYLOGFILE格式将会话密钥写入审计日志,从而让 Wireshark 能够解密该连接的抓包数据。它的敏感程度正如你所想;请只在测试系统上使用。
已知缺口
simulated驱动绕过了标准连接建立流程,因此不会生成审计日志,即使设置了该参数也是如此。它不涉及任何网络通信,也就没有可追踪的内容。ctrlx驱动不支持审计日志。API_EVENT已定义,但目前尚无任何驱动会输出它。
评论
登录后参与评论
KnowForge