触发异常时如何记录?

Cha*_*cox 7 c++ logging exception 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 */
}
Run Code Online (Sandbox Code Playgroud)

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

现在,它本身很好,但其他一些程序员修改了这个程序,最终违反了上述规定.具体来说,他们在一个案例中将大量代码放入try-block,在另一个案例中删除了'throw'并放置了'return'.

我现在看到我的解决方案很脆弱; 这不是未来修改的证据.

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

我有另一个没有上述问题的潜在解决方案,但我想知道别人怎么想.它使用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 */
}
Run Code Online (Sandbox Code Playgroud)

我想知道:

  • 从技术上讲,这会有效吗?最初它似乎工作,但我知道有一些关于使用的警告std::uncaught_exception.
  • 还有另一种方法来实现我想要的吗?

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

  • 在函数调用周围添加了最初缺少的try/ .catchlog
  • 增加跟踪std::uncaught_exception建设状态.这样可以防止在另一个析构函数的"try"块中创建此对象,该析构函数作为异常堆栈展开的一部分触发.
  • 修复了新的'potential_function'以创建命名对象,而不是像以前那样创建临时对象.

yzt*_*yzt 1

我对你的方法没有评论,但看起来很有趣!我有另一种方法,可能也适合你想要的,并且可能更通用一些。不过,它需要 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我在这里看到的问题是,将其包装到宏中并不容易,并且期望您的同事始终正确编写[=] {}部分可能太过分了!

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

#define TRY_AND_LOG_BEGIN(message)  try_and_log (message, [&] {
#define TRY_AND_LOG_END()           })
Run Code Online (Sandbox Code Playgroud)

你像这样使用它们:

TRY_AND_LOG_BEGIN ("Exception happened!")  // No semicolons here!
    whatever_code_you_want ();
TRY_AND_LOG_END ();
Run Code Online (Sandbox Code Playgroud)

这取决于您的观点,要么是净收益,要么是净损失!(我个人更喜欢简单的函数调用和 lambda 语法,这给了我更多的控制力和透明度。

此外,还可以将日志消息写入代码块的末尾;只需切换函数的两个参数即可try_and_log