跳到主要内容

日志

日志通过 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);

日志类别

LogCategoryenum 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 // 控制台输出捕获
};
枚举说明默认状态
0LogCategory::Error系统错误和异常启用
1LogCategory::Warning潜在问题启用
2LogCategory::Message一般信息(状态切换等)启用
3LogCategory::MailboxCoE/SoE/FoE/EoE 邮箱通信开启 (SetMailboxLogging)
4LogCategory::PDOPDO 数据变化日志关闭 (SetPDOLogging)
5LogCategory::Debug详细调试信息关闭 (SetDebugLogging)
6LogCategory::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);
});