【问题标题】:C++ logging wrapper designC++ 日志包装设计
【发布时间】:2011-12-15 02:55:39
【问题描述】:

我想在我的应用程序中添加一个日志。我选择了一个日志库,但我希望能够切换到不同的库,而无需更改任何使用日志记录的代码。

因此,我需要某种足够灵活的日志包装器,以利用几乎所有底层日志库的功能。

对这种包装器的设计有什么建议吗?

编辑:我在这个包装器中必须具备的一个特性是组件标记。我希望我的算法类在其日志行之前出现“X:”,并且我的管理器类中出现“Y:”。如何将这些标签传播到底层日志以及如何构建组件标签命名机制是这里的一个主要设计问题。

【问题讨论】:

  • 真的需要包装器吗?你自己问问。您是否真的有可能切换您的记录器(可能多次)或者您将使用多个日志记录框架?编写包装器需要资源(时间),如果您真的不需要包装器,它们可以放入其他事物中。
  • 谢谢,你可以相信我真的做到了。为简单起见,我愿意使用我提到的标记功能的非常精简的日志版本。

标签: c++ logging


【解决方案1】:

最好的办法是让界面尽可能简单。将日志记录用户界面与日志记录的实际实现方式完全分开。

横切关注点的维护成本总是很高,所以让事情变得更复杂会让你讨厌生活。

有些图书馆只想要这样简单的东西:

void logDebug(const std::string &msg);
void logWarning(const std::string &msg);
void logError(const std::string &msg);

他们不应添加或指定更多上下文。无论如何,没有人可以使用这些信息,所以不要过度设计它。

如果您开始向日志调用添加更多信息,那么重用使用它的客户端代码会变得更加困难。通常,当组件在不同的抽象级别上使用时,您会看到这个表面。尤其是当一些低级代码提供仅与更高级别相关的调试信息时。

这也不会强制您的日志实现(甚至日志实现所遵循的接口!)变成任何东西,因此您可以随时更改它。

更新:

就标记而言,这是一个高级别的问题。我将推测它不属于日志,但这既不是这里也不是那里。

将其排除在日志消息规范之外。低级代码不应该给飞行卡车你或你的经理是谁。

我不知道您在示例中如何指定 XY。从我们给出的描述中,您如何做到这一点并不是很明显。我将只使用一个字符串进行演示,但如果可能的话,您应该将其替换为类型安全的字符串。

如果它总是打开,那么只有一个实例上下文(可能是一个全局变量)可能是合适的。登录时,设置上下文并忘记它。如果它没有设置,请带着极端的偏见投掷。如果在未设置时不能投掷,那么它并不总是开启。

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(),如果他们不想这样做,但会给你带来方便。

【讨论】:

  • 我必须考虑的一个问题是不同的组件标记。
  • 我脑子里有这样的语法:Log log = GetLogger(MyClassName);日志。警告(...)。我不确定 MyClassName 应该是什么,如果它是一个字符串,我从哪里得到它?如果将类名转换为字符串是一些技巧,那该怎么做呢?
  • @user991339 我完全不明白你在问什么。您是否尝试记录类型?
  • 不,我想将标签名称与使用日志的类相关联
  • @user991339 更新了我将如何处理它。您希望日志记录是声明性的,使用与您提供冲突的日志对象,并且更难维护。
【解决方案2】:

我喜欢这种方法:

class Log {
public:
    virtual logString(const std::string&)=0;
};

template <typename T>
Log& operator<<(Log& logger, const T& object) {
        std::stringstream converter;
        converter << object;
        logger.logString(converter.str());
        return logger;
}

简单快捷!您需要做的就是重新实现 logString 方法...

【讨论】:

  • 这是个好主意。您能建议一种实现标记功能的方法吗?
  • @user991339 我会在记录器中添加一个 Tag 属性和一个 bool 来指示行首,因此在第一次记录时它会获得标签戳记。您可以将 operator
  • Boost.Log 如何知道何时打印没有 bool 或 endls 的行? log
【解决方案3】:

看看zf_log 库。它非常小(~2000k 行,编译时~10KB)而且速度很快(参见README.md 中的比较表)。它非常接近您描述的包装器。它为您提供了可以在项目中使用的抽象 API,并允许指定要使用的实际日志记录实现。请参阅custom_output.c 示例,其中 syslog 用作输出工具。它也可以在库中私下使用,而不会与可能使用该库的其他代码发生冲突(有关更多信息,请参阅ZF_LOG_LIBRARY_PREFIXdefine)。 即使它不是您正在寻找的东西,我想它也可能是您的包装器的一个很好的例子。

【讨论】:

    猜你喜欢
    • 2015-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    • 2011-07-25
    • 1970-01-01
    相关资源
    最近更新 更多