触发异常时应该如何记录日志? [英] How should one log when an exception is triggered?

查看:279
本文介绍了触发异常时应该如何记录日志?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我最近写的程序中,当我的业务逻辑代码在第三方或项目API中触发异常时,我想记录日志。 (为了澄清,我想记录当使用一个API引起一个异常,这可以是许多框架高于实际 throw ,并且可能会在实际 catch (其中可以记录异常有效负载。)我做了以下操作:

  void former_function()
{
/ *这里的一些代码* /
try
{
/ *我知道的一些特定代码可能会抛出,想要记录* /
}
catch(...)
{
log(与某些其他数据做某事时发生异常);
throw;
}
/ *这里有一些代码* /
}

简而言之,如果发生异常,请创建一个catch-all子句,记录错误,然后重新抛出。在我看来这是安全的。我知道一般catch-all被认为是坏的,因为没有一个参考的例外,以获得任何有用的信息。



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



现在我的解决方案是脆弱的;它不是未来修改的。



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



<我有另一个潜在的解决方案,没有上述问题,但我不知道别人的想法。它使用RAII,特别是一个Scoped Exit对象,如果 std :: uncaught_exception 不是 true on destroy:

  #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(...)
{
//没有异常可以离开析构函数。
//特别是当std :: uncaught_exception为true时。
}
}
}
};

void potential_function()
{
/ *这里的一些代码* /
{
ExceptionTriggeredLog exception_triggered_log(数据);
/ *我知道的一些特定代码可能抛出,并想要记录* /
}
/ *一些代码在这里* /
}

我想知道:




  • ,这会工作强大吗?最初它似乎工作,但我知道有使用 std :: uncaught_exception 有一些注意事项。

  • 有另一种方法来完成
  • 具体来说,我有:




    • 添加了 try / 函数调用

    • 添加跟踪 c $ c> std :: uncaught_exception 建设状态。

    • 修复了新的potential_function以创建一个新的potential_function对象,该对象在另一个析构函数的try 对象

我没有评论你的方法,但它似乎很有趣!我有另一种方式,也可能为你想要的工作,并可能是一个更一般的目的。



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

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

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

  try_and_log(在此处抛出异常...,[&] {
this_is_the_code();
you_want_executed();
and_its_exceptions_logged();
});

正如我之前说的,我不知道它如何与你自己的解决方案叠加。请注意,lambda正在从其封闭范围捕获所有 ,这是非常方便的。还要注意,我没有实际尝试过这样,所以编译错误,逻辑问题和/或核战争可能会导致这种。



我看到的问题是,不太容易将它包装到宏中,并期望您的同事写 [=] {} 部分



对于包装和傻瓜的目的,你可能需要两个宏:a TRY_AND_LOG_BEGIN 发出第一行直到lambda的开始大括号和 TRY_AND_LOG_END 发出结束括号和括号。像这样:

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

您可以这样使用它们:


$ b b

  TRY_AND_LOG_BEGIN(Exception happened!)//这里没有分号! 
whatever_code_you_want();
TRY_AND_LOG_END();

这取决于你的观点 - 是净收益还是净亏损! (我个人更喜欢简单的函数调用和lambda语法,这给我更多的控制和透明度。



此外,可以在日志消息的末尾代码块;只需切换 try_and_log 函数的两个参数。


In a program I recently wrote, I wanted to log when my "business logic" code triggered an exception in third-party or project APIs. ( To clarify, I want to log when use of an an API causes an exception. This can be many frames above the actual throw, and may be many frames below the actual catch ( where logging of the exception payload can occur. ) ) I did the following:

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 */
}

In short, if an exception occurs, create a catch-all clause, log the error, and re-throw. In my mind this is safe. I know in general catch-all is considered bad, since one doesn't have a reference to the exception at all to get any useful information. However, I'm just going to re-throw it, so nothing is lost.

Now, on its own it was fine, but some other programmers modified this program, and ended up violating the above. Specifically, they put a huge amount of code into the try-block in one case, and in another removed the 'throw' and placed a 'return'.

I see now my solution was brittle; it wasn't future-modification-proof.

I want a better solution that does not have these problems.

I have another potential solution that doesn't have the above issue, but I wonder what others think of it. It uses RAII, specifically a "Scoped Exit" object that implicitly triggers if std::uncaught_exception is not true on construction, yet is true on destruction:

#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 */
}

I want to know:

  • technically, would this work robustly? Initially it seems to work, but I know there are some caveats about using std::uncaught_exception.
  • is there another way to accomplish what I want?

Note: I've updated this question. Specifically, I've:

  • added the try/catch that was initially missing, around the log function-call.
  • added tracking the std::uncaught_exception state on construction. This guards against the case where this object is created inside a 'try' block of another destructor which is triggered as part of exception stack-unwinding.
  • fixed the new 'potential_function' to create a named object, not a temporary object as before.

解决方案

I have no comment on your method, but it seems interesting! I have another way that might also work for what you want, and might be a little more general-purpose. It requires lambdas from C++11 though, which might or might not be an issue in your case.

It's a simple function template that accepts a lambda, runs it and catches, logs and rethrows all exceptions:

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

The way you use it (in the simplest case) is like this:

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

As I said before, I don't know how it stacks against your own solution. Note that the lambda is capturing everything from its enclosing scope, which is quite convenient. Also note that I haven't actually tried this, so compile errors, logical problems and/or nuclear wars may result from this.

The problem I see here is that it is not easy to wrap this into a macro, and expecting your colleagues to write the [=] { and } parts correctly and all the time might be too much!

For wrapping and idiot-proofing purposes, you'll probably need two macros: a TRY_AND_LOG_BEGIN to emit the first line till the opening brace for the lambda and an TRY_AND_LOG_END to emit the closing brace and parenthesis. Like so:

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

And you use them like this:

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

Which is - depending on your perspective - is either a net gain or a net loss! (I personally prefer the straightforward function call and lambda syntax, which gives me more control and transparency.

Also, it is possible to write the log message at the end of the code block; just switch the two parameters of the try_and_log function.

这篇关于触发异常时应该如何记录日志?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆