Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy logging in C++

Let's suppose that we have several levels of logging: trace, debug, info, error. I was wondering if there is a way to write the following code:

enum log_level = {trace, debug, info, error};

log_level global_log_level = info;

void log(log_level level, string& message){
    if (level >= global_log_level){
        std::cout << message << std::endl;
    }
}

string create_message(){
    ...
}

log_level level = debug;
log (level, create_message());

without create_message being called if level is smaller that global_severity_level. Indeed, create_message can be quite long, and no matter what it creates a string. If there are a lot of "debug" logs, those ones can become a substantial overhead when running in non-debug mode.

I know it is possible to do so if the function "log" is a macro, calling create_message() only if severity > minimal_severity; but isn't there another way to do this without macros?

EDIT

In the above, I didn't specify create_message, because it could be anything, in particular:

log(level, "Created object " + my_object.getName());

In this case, is there a way to write log such that the full string is not created, in a relatively transparent way for the programmer calling log?

Many thanks

like image 645
GHL Avatar asked Feb 02 '13 18:02

GHL


3 Answers

Similar to @sftrabbit, but as suggested by @ipc.

Use a template to avoid the std::function machinery, and the compiler may be able to inline this and thus it hopefully will end up being faster.

template< typename F >
void log(log_level level, F message_creator){
    if (level >= global_log_level){
        std::cout << message_creator() << std::endl;
    }
}
like image 56
Scott Langham Avatar answered Oct 09 '22 20:10

Scott Langham


There are several alternatives. An interesting one is to pass create_message as a std::function<std::string()> and call it from within log:

void log(log_level level, std::function<std::string()> message_creator){
    if (level >= global_log_level){
        std::cout << message_creator() << std::endl;
    }
}

Then you would call it like so:

log(level, create_message);

This can work with arbitrary expressions as arguments if you wrap them in a lambda:

log(level, [&](){ return "Created object " + my_object.getName(); });

If you really don't want to argument to be evaluated at all (as you've described in the comments), then you'll need to check the level outside of the call:

if (level >= global_log_level) {
  log(level, create_message());
}
like image 21
Joseph Mansfield Avatar answered Oct 09 '22 22:10

Joseph Mansfield


@sftrabbit answer is prefered. But just if you dont want to change log(), you can call it:

log (level, (level >= global_log_level)? create_message() : "");
like image 21
qPCR4vir Avatar answered Oct 09 '22 20:10

qPCR4vir