c++ - 触发异常时应该如何记录?

标签 c++ exception logging raii

在我最近编写的一个程序中,当我的“业务逻辑”代码在第三方或项目 API 中触发异常时,我想记录下来。 (澄清一下,我想在使用 API 导致异常时记录日志。这可能比实际的 throw 高很多帧,也可能比实际的 catch 低很多帧>(可以记录异常负载的位置。))我做了以下操作:

void former_function()
{
    /* some code here */
    try
    {
       /* some specific code that I know may throw, and want to log about */
    }
    catch( ... )
    {
       log( "an exception occurred when doing something with some other data" );
       throw;
    }
    /* some code here */
}

简而言之,如果发生异常,创建一个 catch-all 子句,记录错误,然后重新抛出。在我看来,这是安全的。我知道一般来说,包罗万象被认为是不好的,因为根本没有对异常的引用来获取任何有用的信息。但是,我只是要重新抛出它,所以不会丢失任何东西。

现在,它自己没问题,但其他一些程序员修改了这个程序,最终违反了上述规定。具体来说,他们在一种情况下将大量代码放入 try block 中,而在另一种情况下删除了“throw”并放置了“return”。

现在我发现我的解决方案很脆弱;它不是 future 修改的证明。

我想要一个没有这些问题的更好的解决方案。

我有另一个没有上述问题的潜在解决方案,但我想知道其他人对此有何看法。它使用 RAII,特别是一个“Scoped Exit”对象,如果 std::uncaught_exception 在构建时不为真,但在时隐式触发销毁:

#include <ciso646> // not, and
#include <exception> // uncaught_exception

class ExceptionTriggeredLog
{
private:
    std::string const m_log_message;
    bool const m_was_uncaught_exception;
public:
    ExceptionTriggeredLog( std::string const& r_log_message )
      : m_log_message( r_log_message ),
        m_was_uncaught_exception( std::uncaught_exception() )
    {
    }
    ~ExceptionTriggeredLog()
    {
        if( not m_was_uncaught_exception
            and std::uncaught_exception() )
        {
            try
            {
                log( m_log_message );
            }
            catch( ... )
            {
                // no exceptions can leave an destructor.
                // especially when std::uncaught_exception is true.
            }
        }
    }
};

void potential_function()
{
    /* some code here */
    {
       ExceptionTriggeredLog exception_triggered_log( "an exception occurred when doing something with some other data" );
       /* some specific code that I know may throw, and want to log about */
    }
    /* some code here */
}

我想知道:

  • 从技术上讲,这会很有效吗?最初它似乎有效,但我知道使用 std::uncaught_exception 有一些注意事项。
  • 还有其他方法可以实现我想要的吗?

注意:我已经更新了这个问题。具体来说,我已经:

  • log 函数调用周围添加了最初缺失的 try/catch
  • 添加了跟踪构造时的 std::uncaught_exception 状态。这可以防止在另一个析构函数的“try” block 内创建此对象的情况,该析构函数作为异常堆栈展开的一部分被触发。
  • 修复了新的“potential_function”以创建一个命名对象,而不是像以前那样的临时对象。

最佳答案

我对你的方法没有意见,但看起来很有趣!我有另一种方法也可以满足您的需求,并且可能更通用一些。不过,它需要来自 C++11 的 lambda,这在您的情况下可能是也可能不是问题。

这是一个简单的函数模板,它接受 lambda、运行它并捕获、记录并重新抛出所有异常:

template <typename F>
void try_and_log (char const * log_message, F code_block)
{
    try {
        code_block ();
    } catch (...) {
        log (log_message);
        throw;
    }
}

你使用它的方式(在最简单的情况下)是这样的:

try_and_log ("An exception was thrown here...", [&] {
    this_is_the_code ();
    you_want_executed ();
    and_its_exceptions_logged ();
});

正如我之前所说,我不知道它与您自己的解决方案相比如何。请注意,lambda 正在捕获其封闭范围内的一切,这非常方便。另请注意,我还没有实际尝试过,因此可能会导致编译错误、逻辑问题和/或核 war 。

我在这里看到的问题是,将其包装到宏中并不容易,并希望您的同事正确编写 [=] {} 部分而且所有的时间可能太多了!

出于包装和防白痴的目的,您可能需要两个宏:一个 TRY_AND_LOG_BEGIN 发出第一行直到 lambda 的左大括号和一个 TRY_AND_LOG_END发出右大括号和括号。像这样:

#define TRY_AND_LOG_BEGIN(message)  try_and_log (message, [&] {
#define TRY_AND_LOG_END()           })

然后你像这样使用它们:

TRY_AND_LOG_BEGIN ("Exception happened!")  // No semicolons here!
    whatever_code_you_want ();
TRY_AND_LOG_END ();

这是 - 取决于您的观点 - 是净 yield 还是净损失! (我个人更喜欢直接的函数调用和 lambda 语法,这给了我更多的控制权和透明度。

另外,也可以在代码块的末尾写入日志信息;只需切换 try_and_log 函数的两个参数即可。

关于c++ - 触发异常时应该如何记录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15504166/

相关文章:

exception - cucumber / capybara : tests fail randomly under PhantomJS

javascript - 将 StackTrace.js 与 Angular.js 一起使用

python - 如何使用静态变量、自定义 getter 和 setter 扩展 SWIG 中的结构?

c++ - 获取 glFrustum 的坐标

c++ - 禁用 MSVC 的异常

java - 如何停止 quartz 警告记录器

ruby - 使用 Ruby 记录器根据日志记录级别记录到不同的文件

c++ - 如何从从 C++ 代码启动的批处理/shell 脚本中获取返回代码

C++ 逗号分隔的 Txt 文件

java - 什么时候应该抛出 IllegalArgumentException?