最好的办法是让界面尽可能简单。将日志记录用户界面与日志记录的实际实现方式完全分开。
横切关注点的维护成本总是很高,所以让事情变得更复杂会让你讨厌生活。
有些图书馆只想要这样简单的东西:
void logDebug(const std::string &msg);
void logWarning(const std::string &msg);
void logError(const std::string &msg);
他们不应添加或指定更多上下文。无论如何,没有人可以使用这些信息,所以不要过度设计它。
如果您开始向日志调用添加更多信息,那么重用使用它的客户端代码会变得更加困难。通常,当组件在不同的抽象级别上使用时,您会看到这个表面。尤其是当一些低级代码提供仅与更高级别相关的调试信息时。
这也不会强制您的日志实现(甚至日志实现所遵循的接口!)变成任何东西,因此您可以随时更改它。
更新:
就标记而言,这是一个高级别的问题。我将推测它不属于日志,但这既不是这里也不是那里。
将其排除在日志消息规范之外。低级代码不应该给飞行卡车你或你的经理是谁。
我不知道您在示例中如何指定 X 或 Y。从我们给出的描述中,您如何做到这一点并不是很明显。我将只使用一个字符串进行演示,但如果可能的话,您应该将其替换为类型安全的字符串。
如果它总是打开,那么只有一个实例上下文(可能是一个全局变量)可能是合适的。登录时,设置上下文并忘记它。如果它没有设置,请带着极端的偏见投掷。如果在未设置时不能投掷,那么它并不总是开启。
void setLoggingContext("X:");
如果这在不同的抽象级别发生变化,我会考虑基于堆栈的 RAII 实现。
LoggingTag tag("X:");
当不同的堆栈帧传入不同的值时,我不确定您在场景中的要求是什么。我可以看到堆栈的顶部或底部对于不同的用例来说是合理的。
void foo() {
LoggingTag tag("X:");
logWarning("foo");
bar();
baz();
}
void bar() {
LoggingTag tag("Y:");
logWarning("bar");
baz();
}
void baz() {
logWarning("baz");
}
无论哪种方式,这都不会影响您将消息添加到日志的方式。 baz 函数没有指定 LoggingTag 的上下文。由于这个原因,使用logWarning 不知道标签是非常重要的。
如果你想基于某种类型进行标记,你可以做一些简单的事情。
struct LoggingTag {
LoggingTag(const std::string &tag_) : tag(tag_) {}
template<typename T>
static LoggingTag ByType() {
return LoggingTag(typeid(T).name());
}
std::string tag;
};
void foo() {
LoggingTag tag = LogginTag::ByType<int>();
}
这不会强迫某人使用typeid(T).name(),如果他们不想这样做,但会给你带来方便。