2011-10-25 79 views
5

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

因此,我需要某种形式的日志记录包装的足够灵活地利用几乎任何下属记录库的功能。

对于这种包装设计的任何建议?

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

+2

你真的*需要包装吗?问问你自己。是否真的有可能转换你的记录器(可能是多次),或者你会利用多个日志记录框架?编写一个包装器需要资源(时间),如果你不真的需要包装器,它们可以放入其他东西。 – Xeo

+0

谢谢,你可以信任我真的这样做。为了简单起见,我愿意用我提到的标记功能来解决一个非常简化的日志版本。 – Leo

回答

2

最好的选择是尽可能简化界面。将日志记录用户的界面与记录实际执行的方式完全分开。

横切关注点始终是维护费用昂贵,所以使任何事情更复杂会让你讨厌的生活。

某些库只想要简单的东西是这样的:

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

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

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

这不会强迫你的日志实现(或连界面日志实现符合!)到任何东西,所以你可以改变它时。

UPDATE:

只要该标记,这是一个高层次的关注。我会推测它不属于日志,但那不是在这里也不在那里。

将其保留在日志消息规范之外。低层次的代码不应该让你或你的经理是飞行卡车。

我不知道你如何指定你的榜样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(),如果他们不想,而是给你的便利。

+0

我必须考虑的一个问题是不同的组件标签。 – Leo

+0

我在脑海里有这样的语法:Log log = GetLogger(MyClassName); log.Warn(...)。我不确定MyClassName应该是什么,如果它是一个字符串,我从哪里得到它?如果将类名转换为字符串是一些窍门,那怎么办呢? – Leo

+0

@ user991339我不明白你在问什么。你想记录一个类型吗? –

1

我喜欢这种方法:

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方法...

+0

这是一个好主意。你能建议一种实现标记功能的方法吗? – Leo

+0

@CatPlusPlus它很容易修改使用模板... –

+0

@ user991339我会添加一个标记属性到记录器和一个布尔表明一个开始行,所以在第一次日志它获得标记戳。你可以专门为<< endl case <<操作符,所以你知道下一个输入是行的开始。 –

0

看看zf_log库。它非常小(〜2000k行,编译时〜10KB)和快速(见README.md中的对照表)。这与您所描述的封装非常接近。它为您提供了一个抽象API,您可以在您的项目中使用它,并允许指定要使用的实际日志记录实施。请参见custom_output.c示例,其中syslog用作输出工具。它也可以在图书馆内私下使用,而不会与其他可能使用此库的代码发生冲突(请参阅ZF_LOG_LIBRARY_PREFIX定义以获取更多信息)。 即使它不是你正在寻找的东西,我想这可能是你的包装的一个很好的例子。