在我最近编写的程序中,我想记录我的“业务逻辑”代码在第三方或项目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 */
}

简而言之,如果发生异常,请创建一个包罗万象的子句,记录错误,然后重新抛出。在我看来,这是安全的。我知道一般来说,包罗万象被认为是不好的,因为人们根本没有引用异常来获取任何有用的信息。但是,我将重新抛出该球,因此不会丢失任何内容。

现在,单独运行就可以了,但是其他一些程序员修改了该程序,最终违反了上述规定。具体来说,在一种情况下,他们将大量代码放入try块,而在另一种情况下,它们删除了“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的一些注意事项。
  • 还有另一种方法可以实现我想要的吗?

  • 注意:我已经更新了这个问题。具体来说,我已经:
  • try函数调用周围添加了最初缺少的catch/log
  • 添加了在构造时跟踪std::uncaught_exception状态的信息。这可以防止在另一个析构函数的“try”块内创建该对象的情况,该对象是作为异常堆栈展开的一部分而触发的。
  • 修复了新的'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/

    10-10 23:16
    查看更多