日志
日志通过 darra::ethercat::LogManager 单例管理 (logging/logging.hpp)。用 LogManager::Instance().SetLogCallback(...) 注册回调。
日志 API 在 LogManager 单例上, 不在 master.Events()
MasterEvents 没有 LogEvent 事件字段。日志回调、级别、缓冲查询全部由
darra::ethercat::LogManager 单例提供。master.Events() 上的事件是 OnError() / OnWarning()
等业务事件, 与通用日志流是不同概念。
快速开始
using namespace darra;
using namespace darra::ethercat;
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
// 日志回调 (简单版: 类别 + 消息字符串)
LogManager::Instance().SetLogCallback([](int category, const char* msg) {
printf("[%d] %s\n", category, msg);
});
// 启用调试日志 (默认关闭)
LogManager::Instance().SetDebugLogging(true);
// 设置最低日志级别 (低于此级别不记录)
LogManager::Instance().SetLogLevel(LogCategory::Warning);
日志类别
LogCategory 是 enum class,定义在 data/types.hpp:
enum class LogCategory : int {
Error = 0, // 系统错误和异常
Warning = 1, // 潜在问题
Message = 2, // 一般信息 (状态切换等)
Mailbox = 3, // CoE/SoE/FoE/EoE 邮箱通信 (默认开启)
PDO = 4, // PDO 数据变化日志 (默认关闭)
Debug = 5, // 详细调试信息 (默认关闭)
Local = 6 // 控制台输出捕获
};
| 值 | 枚举 | 说明 | 默认状态 |
|---|---|---|---|
| 0 | LogCategory::Error | 系统错误和异常 | 启用 |
| 1 | LogCategory::Warning | 潜在问题 | 启用 |
| 2 | LogCategory::Message | 一般信息(状态切换等) | 启用 |
| 3 | LogCategory::Mailbox | CoE/SoE/FoE/EoE 邮箱通信 | 开启 (SetMailboxLogging) |
| 4 | LogCategory::PDO | PDO 数据变化日志 | 关闭 (SetPDOLogging) |
| 5 | LogCategory::Debug | 详细调试信息 | 关闭 (SetDebugLogging) |
| 6 | LogCategory::Local | 控制台输出捕获 | 启用 |
PDO 日志
PDO 日志只在 PDO 数据发生变化时输出,不会每个周期都产生日志。适合调试 PDO 映射问题。
容量与保留策略
日志列表设有上限,超出时自动移除最旧的条目。
自动保留模式: 进入 SafeOp/OP 状态后,日志自动切换为保留模式 -- 运行时事件(从站离线、PDO 丢帧、DC 同步丢失等)不会被后续操作清空。退出 SafeOp/OP 后恢复为普通模式。
日志开关
PDO、Debug 日志默认关闭,Mailbox 默认开启。通过 LogManager 单例的开关方法控制。
auto& mgr = LogManager::Instance();
// 启用 PDO 日志 (仅在 PDO 数据变化时输出, 影响实时性能, 慎用)
mgr.SetPDOLogging(true);
// 启用/禁用邮箱日志 (CoE/SoE/FoE/EoE 邮箱通信日志, 默认开启)
mgr.SetMailboxLogging(true);
// 启用调试日志 (详细调试信息)
mgr.SetDebugLogging(true);
// 查询开关状态
bool pdoOn = mgr.IsPDOLoggingEnabled();
级别过滤
SetLogLevel()
void LogManager::SetLogLevel(LogCategory level);
LogCategory LogManager::GetLogLevel() const;
设置最低日志级别, 低于此级别的日志不记录。
示例:
// 只记录 Warning 及以上 (Warning/Error 等)
LogManager::Instance().SetLogLevel(LogCategory::Warning);
日志查询
// 获取最近 N 条 (count=0 返回全部)
std::vector<LogEntry> GetRecentLogs(size_t count = 0) const;
// 按类别过滤
std::vector<LogEntry> GetLogsByCategory(const std::vector<LogCategory>& categories) const;
// 日志总数 / 清空 / 容量上限
size_t GetLogCount() const;
void ClearLogs();
void SetMaxLogCount(size_t count);
示例:
auto& mgr = LogManager::Instance();
// 取最近 100 条
auto recent = mgr.GetRecentLogs(100);
// 只看错误和警告
auto errs = mgr.GetLogsByCategory({LogCategory::Error, LogCategory::Warning});
for (auto& e : errs)
printf("[%s] %s\n", e.category_str(), e.message.c_str());
LogEntry 结构:
struct LogEntry {
int64_t timestamp_ms; // 时间戳 (毫秒, 自程序启动)
LogCategory category; // 日志类别
std::string message; // 日志消息内容
int slave_index; // 关联从站索引 (-1 表示主站级日志)
const char* category_str() const; // 类别字符串
};
线程安全
日志回调在非 UI 线程上触发。如需在回调中访问共享资源,请使用互斥锁:
#include <mutex>
#include <fstream>
std::ofstream log_file("ethercat.log");
std::mutex log_mutex;
LogManager::Instance().SetLogCallback([&](int category, const char* msg) {
std::lock_guard<std::mutex> lock(log_mutex);
log_file << "[" << category << "] " << msg << std::endl;
});
完整示例
#include "ethercat.hpp"
#include <cstdio>
#include <mutex>
#include <fstream>
using namespace darra;
int main() {
using namespace darra::ethercat;
// 1. 启用调试和邮箱日志 (LogManager 单例)
LogManager::Instance().SetDebugLogging(true);
LogManager::Instance().SetMailboxLogging(true);
try {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
// 2. 日志回调 (实时输出所有日志)
LogManager::Instance().SetLogCallback([](int category, const char* msg) {
const char* labels[] = {"ERROR", "WARN", "MSG", "MBOX", "PDO", "DBG", "LOCAL"};
const char* label = (category >= 0 && category <= 6) ? labels[category] : "?";
printf("[%s] %s\n", label, msg);
});
// 3. 事件驱动的状态监控 (用 On*() 注册)
master.Events().OnSlaveStateChanged([](uint16_t mi, uint16_t si,
EcState o, EcState n) {
printf("[状态] 从站 %d: %s -> %s\n", si,
EcStateFormat::Format(o).c_str(),
EcStateFormat::Format(n).c_str());
});
master.Events().OnEmergency([](uint16_t mi, uint16_t si,
uint16_t code, uint16_t reg,
uint8_t b1, uint16_t w1, uint16_t w2) {
printf("[紧急] 从站 %d: 0x%04X\n", si, code);
});
master.SetState(EcState::OP);
master.Start();
printf("运行中... 按 Enter 停止\n");
getchar();
} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}
多线程 UI 绑定
日志回调在非 UI 线程触发。在 Qt、MFC 等 UI 框架中更新控件时,需要通过消息队列或信号槽同步到 UI 线程:
// Qt 示例
LogManager::Instance().SetLogCallback([this](int cat, const char* msg) {
QString text = QString("[%1] %2").arg(cat).arg(msg);
QMetaObject::invokeMethod(this, [=]() {
ui->logList->addItem(text);
}, Qt::QueuedConnection);
});