如何在项目中实现良好的调试/日志记录功能

15

我正在进行一个较小的项目,大约有3-4人。我希望能够通过日志等方式拥有一种可靠的调试应用程序的方法。有没有关于如何构建它的好资源呢?我听说过许多项目经理说一个良好的日志功能对每个项目都至关重要,但我不确定如何做到这一点。


8
我认为良好的日志记录对于任何项目的重要性(如果不是必要的话)都是至关重要的。尽管对于长时间运行的服务器进程来说可能更加重要,但它通常可以帮助解决客户端应用程序中的漏洞问题,特别是当用户反馈可能会无意中误导或遗漏关键细节时。 - Mitch Wheat
请提供最适合本地C ++的日志记录框架。 - Mitch Wheat
17
抱歉,如果有可能的话,我会对你的评论进行反对。它相当具有反生产力和居高临下的气息。 - davka
在我的情况下,我从不使用日志记录,而是更喜欢在检测到问题时让断言使程序崩溃。这迫使开发人员尽快修复错误。我不确定日志记录能否帮助您解决问题。我认为它只有在检测到触发错误的因素时才有用,这可以帮助重现错误并加以纠正。 - neodelphi
3个回答

17
我发现这篇Dr. Dobb's文章, Logging In C++,对于这个问题非常有用。
还在Dr. Dobb's上可以看到: 一个高度可配置的C++日志框架 如果你只需要一个简单的线程安全的日志记录类,它总是输出到stderr,那么你可以使用我编写的这个类:
#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_

#include <iostream>
#include <sstream>

/* consider adding boost thread id since we'll want to know whose writting and
 * won't want to repeat it for every single call */

/* consider adding policy class to allow users to redirect logging to specific
 * files via the command line
 */

enum loglevel_e
    {logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4};

class logIt
{
public:
    logIt(loglevel_e _loglevel = logERROR) {
        _buffer << _loglevel << " :" 
            << std::string(
                _loglevel > logDEBUG 
                ? (_loglevel - logDEBUG) * 4 
                : 1
                , ' ');
    }

    template <typename T>
    logIt & operator<<(T const & value)
    {
        _buffer << value;
        return *this;
    }

    ~logIt()
    {
        _buffer << std::endl;
        // This is atomic according to the POSIX standard
        // http://www.gnu.org/s/libc/manual/html_node/Streams-and-Threads.html
        std::cerr << _buffer.str();
    }

private:
    std::ostringstream _buffer;
};

extern loglevel_e loglevel;

#define log(level) \
if (level > loglevel) ; \
else logIt(level)

#endif

使用方法如下:

// define and turn off for the rest of the test suite
loglevel_e loglevel = logERROR;

void logTest(void) {
    loglevel_e loglevel_save = loglevel;

    loglevel = logDEBUG4;

    log(logINFO) << "foo " << "bar " << "baz";

    int count = 3;
    log(logDEBUG) << "A loop with "    << count << " iterations";
    for (int i = 0; i != count; ++i)
    {
        log(logDEBUG1) << "the counter i = " << i;
        log(logDEBUG2) << "the counter i = " << i;
    }

    loglevel = loglevel_save;
}

你好,我尝试使用你的代码,但是 #define log(level) \ if (level > loglevel) ; \ else logIt(level) 给了我很多错误。它说 语法错误:if语法错误:else。有什么想法吗?Visual Studio 2012 Express - Boyang
@CharlesW。这真的很奇怪。我在Linux上使用g++编译了它。你是否将#define语句保留为三个单独的行?你必须确保反斜杠\字符后面没有空格或其他任何内容。 - Robert S. Barnes
非常奇怪。我通过避免多次包含 logger.hpp 来解决了这个问题。我在这里提出了一个单独的问题 http://stackoverflow.com/questions/19795038/can-define-preprocessor-directive-contain-if-and-else/19795951?noredirect=1#19795951 - Boyang
顺便问一下,在 if (level > loglevel) ; \ 中的分号应该被空块 {} 替换吗? - Boyang
顺便问一下,if (level > loglevel) ; \\ 中的分号是否应该被空块 {} 替换?如果不是,log(levelDEBUG) << "test" 将变成 if (logDEBUG > loglevel); else logIt(logInfo) << "foo " << "bar " << "baz";,而 else 将没有 if 可以附加,因为分号关闭了 if,不是吗?实际上,为什么不只是做 #define log(level) if (level <= loglevel) 呢?我对预处理器指令不太了解。谢谢! - Boyang
显示剩余2条评论

4
如果你想了解日志框架,并且使用的是C++,可以查看Apache的log4cxx。需要花费一些时间来理解其架构,但一旦理解,你会意识到它在灵活性、易用性和性能(正如他们所说)之间保持良好平衡。 log4cxx具有非常灵活的配置,可以通过它对输出目标(文件/旋转文件/控制台等)、子组件的调试级别(例如,你希望集中关注特定类/组件,因此将其设置为DEBUG级别,而其余部分则为INFO级别)、日志条目格式等进行控制,而无需重新编译。
如果你想了解关于如何记录日志的一般指导方针,我没有看到过这样的内容(不是我实际上在寻找)。我认为这主要是经验性的——你决定每个日志级别(如INFO、DEBUG等)所需的信息,并根据你和客户的需求进行细化(不要忘记你的客户也可能是日志的客户,这取决于你的项目)。

2

取决于你所说的“日志记录”是什么意思。一种形式就是提供一种方法,将某个对象的内容打印到输出流中。对于类型为 ClassName 的对象,这需要为该类编写插入运算符:

std::ostream &operator<< (std::ostream &stream, const ClassName & obj) {
   // Implementation elided
}

有了这个,您可以将 ClassName 类型的对象打印到输出流中。这非常有用,以至于一些组织要求每个类都实现这样的方法。


有趣的运算符用法。一个可以在ClassName中提供toString()方法。 - Jean-Christophe Blanchard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接