3
\$\begingroup\$

I wanted to make an extremely light-weight logging system. What are your thoughts on this and what can be done to improve it?

First, the usage:

int main( )
{
    logging::level = logging::fatal | logging::warning;
    mini_log( logging::fatal, "entry point", "nothing", " is ", "happening!" );
}

Now, the code:

#if defined( NDEBUG )
#define mini_log( ... ) static_cast< void >( 0 )
#else
namespace logging
{
    enum level_t
    {
        none = 0b00000,
        information = 0b00001,
        debug = 0b00010,
        warning = 0b00100,
        error = 0b01000,
        fatal = 0b10000,
        all = 0b11111   
    } inline level = all; // level should only be used in the entry point
    inline std::mutex stream;
}
#define mini_log \
    [ ]( logging::level_t const level_message, \
         std::string_view const location, \
         auto &&... message ) -> void \
    { \
        std::lock_guard< std::mutex > lock_stream( logging::stream ); \
        struct tm buf; \
        auto time = [ & ]( ) \
        { \
            auto t = std::time( nullptr ); \
            localtime_s( &buf, \
                         &t ); \
            return std::put_time( &buf, \
                                  "[%H:%M:%S]" ); \
        }; \
        auto level = [ = ]( ) -> std::string \
        { \
            switch( level_message ) \
            { \
                case logging::information: \
                    return " [" __FILE__ "@" stringize_val( __LINE__ ) "] [Info] ["; \
                case logging::debug: \
                    return " [" __FILE__ "@" stringize_val( __LINE__ ) "] [Dbug] ["; \
                case logging::warning: \
                    return " [" __FILE__ "@" stringize_val( __LINE__ ) "] [Warn] ["; \
                case logging::error: \
                    return " [" __FILE__ "@" stringize_val( __LINE__ ) "] [Erro] ["; \
                case logging::fatal: \
                    return " [" __FILE__ "@" stringize_val( __LINE__ ) "] [Fatl] ["; \
            } \
        }; \
        if( level_message & logging::level ) \
            ( ( std::cout << time( ) << level( ) << location << "]: " << message ) << ... ) << '\n'; \
    }
#endif
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Macros

Most of your code is in a macro. This makes it harder to write, read and debug.

Why not place the logic in normal functions and only use macros to pass __FILE__ and __LINE__?

That is until you can switch to C++20 which provides std::source_location

Naming

Why is the mutex called stream?

\$\endgroup\$
3
  • \$\begingroup\$ These feels like it should be a comment asking for clarity instead of an answer. Regardless, I made it a macro because it works. I will never need to debug or touch this again. As for naming, I name the mutex stream because it is the mutex for the console-out stream. \$\endgroup\$
    – user226075
    Commented Jul 19, 2020 at 8:20
  • \$\begingroup\$ You can actually avoid the std::mutex entirely if you first build the log message in a string, and then pass this string to std::cout in one go. Individual calls to operator<<, put(), write() and so on are thread-safe. \$\endgroup\$
    – G. Sliepen
    Commented Jul 19, 2020 at 9:31
  • \$\begingroup\$ I guess I could make a string stream and push it to cout \$\endgroup\$
    – user226075
    Commented Jul 19, 2020 at 10:59
1
\$\begingroup\$

If you want the logger to log the details in case the program crashes, you should flush the buffer before the crash happens.

( std::cout << time( ) << level( ) << location << "]: " << message ) << ... ) << '\n'

This does not flush the buffer to the console so you might lose info that is in the buffer when the abort happens. So use the following:

( std::cout << time( ) << level( ) << location << "]: " << message ) << ... ) << std::endl;

Also, remove the macro and directly execute the lambda. If that is not suitable, make it a normal static function and make it inline.

Unfortunately, it must remain a lambda because of __FILE__ and __LINE__.

Pass __FILE__ and __LINE__ directly to the function (if you make one) and the function signature will be like:

void log_to_file(std::string _file_, std::string _line_);
// usage:
log_to_file(__FILE__, __LINE__);

I personally like __func__.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Yes, this is one of those rare cases where std::endl is the right thing to use. \$\endgroup\$
    – G. Sliepen
    Commented Jul 19, 2020 at 9:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.