]> WPIA git - cassiopeia.git/commitdiff
upd: Reimported logger library by Florian Weber from updated upstream
authorBenny Baumann <BenBE@geshi.org>
Sat, 18 Jul 2015 18:51:53 +0000 (20:51 +0200)
committerBenny Baumann <BenBE@geshi.org>
Sun, 19 Jul 2015 16:54:16 +0000 (18:54 +0200)
src/log/format.cpp [new file with mode: 0644]
src/log/format.hpp [new file with mode: 0644]
src/log/logger.cpp
src/log/logger.hpp
test/src/log.cpp [new file with mode: 0644]

diff --git a/src/log/format.cpp b/src/log/format.cpp
new file mode 100644 (file)
index 0000000..1edd6a0
--- /dev/null
@@ -0,0 +1,107 @@
+#include "log/format.hpp"
+
+#include <algorithm>
+#include <cctype>
+#include <tuple>
+
+namespace logger {
+
+    namespace format {
+
+        inline namespace literals {
+
+            format_data operator"" _fmt( const char* it, std::size_t len ) {
+                const auto end = it + len;
+                auto retval = format_data {};
+
+                if( it == end ) {
+                    return retval;
+                }
+
+                if( *it == '0' or !std::isalnum( *it ) ) {
+                    retval.fill = *it;
+                    ++it;
+                }
+
+                if( it == end ) {
+                    return retval;
+                }
+
+                if( std::isdigit( *it ) ) {
+                    const auto w_end = std::find_if_not( it, end,
+                        []( char c ) {
+                            return std::isdigit( c );
+                        } );
+                    retval.width = std::stoul( std::string{it, w_end} );
+                    it = w_end;
+                }
+
+                if( it == end ) {
+                    return retval;
+                }
+
+                switch( *it ) {
+                case 's':
+                    break;
+
+                case 'd':
+                    retval.base = 10;
+                    break;
+
+                case 'x':
+                    retval.base = 16;
+                    break;
+
+                case 'o':
+                    retval.base = 8;
+                    break;
+
+                case 'l':
+                    retval.align_right = false;
+                    break;
+
+                case 'r':
+                    retval.align_right = true;
+                    break;
+
+                default:
+                    throw std::invalid_argument{"invalid format_data-string"};
+                }
+
+                ++it;
+
+                if( it != end ) {
+                    throw std::invalid_argument{"invalid format_data-string"};
+                }
+
+                return retval;
+            }
+
+        } // inline namespace literals
+
+    } // namespace format
+
+    namespace conv {
+
+        std::string to_string( const format::formated_string& arg ) {
+            if( arg.value.size() >= arg.width ) {
+                return arg.value;
+            }
+
+            auto str = std::string {};
+            str.reserve( arg.width );
+
+            if( arg.align_right ) {
+                str.append( arg.width - arg.value.size(), arg.fill );
+                str.append( arg.value );
+            } else {
+                str.append( arg.value );
+                str.append( arg.width - arg.value.size(), arg.fill );
+            }
+
+            return str;
+        }
+
+    } // namespace conv
+
+} // namespace logger
diff --git a/src/log/format.hpp b/src/log/format.hpp
new file mode 100644 (file)
index 0000000..827f7ea
--- /dev/null
@@ -0,0 +1,130 @@
+#pragma once
+
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <initializer_list>
+#include <tuple>
+
+namespace logger {
+
+    namespace format {
+
+        template <typename Integer>
+        struct formated_integer;
+        struct formated_string;
+
+        struct width_t {
+            explicit width_t( unsigned v ): value{v} {}
+            unsigned value = 0u;
+        };
+        struct base_t {
+            explicit base_t( unsigned v ): value{v} {}
+            unsigned value = 10u;
+        };
+        struct fill_t {
+            explicit fill_t( char v ): value{v} {}
+            char value = ' ';
+        };
+        enum class align_t {
+            left, right
+        };
+
+        inline namespace literals {
+            inline width_t operator"" _w( unsigned long long value ) {
+                return width_t{static_cast<unsigned>( value )};
+            }
+            inline base_t operator"" _b( unsigned long long value ) {
+                return base_t{static_cast<unsigned>( value )};
+            }
+            inline fill_t operator"" _f( char c ) {
+                return fill_t{c};
+            }
+        }
+
+        struct format_data {
+            unsigned width = 0;
+            std::uint8_t base = 10;
+            char fill = ' ';
+            bool align_right = false;
+
+            void set( width_t w ) {
+                width = w.value;
+            }
+            void set( base_t b ) {
+                base = b.value;
+            }
+            void set( fill_t f ) {
+                fill = f.value;
+            }
+            void set( align_t a ) {
+                align_right = ( a == align_t::right );
+            }
+
+            formated_string operator()( const std::string& str ) const;
+
+            template <typename Integer,
+                      typename = typename std::enable_if<std::is_integral<Integer>::value>::type>
+            formated_integer<Integer> operator()( Integer i ) const;
+        };
+
+        template <typename Integer>
+        struct formated_integer : public format_data {
+            formated_integer( Integer i, format_data f ) : format_data( f ), value {i} {}
+            Integer value;
+        };
+
+        struct formated_string : public format_data {
+            formated_string( const std::string& s, format_data f ) :
+                format_data( f ), value( std::move( s ) ) {}
+
+            const std::string& value;
+        };
+
+        inline formated_string format_data::operator()( const std::string& str ) const {
+            return {str, *this};
+        }
+
+        template <typename Integer, typename>
+        inline formated_integer<Integer> format_data::operator()( Integer i ) const {
+            return {i, *this};
+        }
+
+        template <typename... Args>
+        formated_string fmt( const std::string& str, const Args& ... args ) {
+            auto format = format_data{};
+            std::ignore = std::initializer_list<int>{( format.set( args ), 0 )...};
+            return format( str );
+        }
+
+        template <typename Integer, typename... Args>
+        formated_integer<Integer> fmt( const Integer i, const Args& ... args ) {
+            auto format = format_data{};
+            std::ignore = std::initializer_list<int>{( format.set( args ), 0 )...};
+            return format( i );
+        }
+
+        inline namespace literals {
+            format_data operator"" _fmt( const char*, std::size_t );
+        }
+
+    } // namespace format
+
+    namespace conv {
+
+        template <typename Integer>
+        inline std::string to_string( const format::formated_integer<Integer>& arg ) {
+            std::ostringstream stream;
+            stream <<
+                std::setbase( arg.base ) <<
+                std::setw( arg.width ) <<
+                std::setfill( arg.fill ) <<
+                arg.value;
+            return stream.str();
+        }
+
+        std::string to_string( const format::formated_string& arg );
+
+    } // namespace conf
+
+} // namespace logger
index 9a703001ed0d0228bf1d174f3eb378409e9a519d..aee4d29d933671cd80eeb56fe4d2e9e85ba331ee 100644 (file)
 #include "log/logger.hpp"
 
+#include <cassert>
+#include <iterator>
 #include <algorithm>
 #include <chrono>
 #include <cstring>
-#include <iostream>
+#include <ostream>
 #include <iterator>
 
 namespace logger {
 
-    namespace {
+    namespace impl {
+
+        /**
+         * Manages the standard-logger and the logger-stack.
+         *
+         * CAREFULL: THIS FUNCTION CONTAINS GLOBAL STATE!
+         */
+        std::vector<logger_set*>& logger_stack() {
+            static auto stack = std::vector<logger_set*> {};
+            // To avoid infinite recursion, the base-logger must
+            // not auto-register but be added manually
+            static auto std_logger = logger_set {{std::cout}, auto_register::off};
+            // in order to avoid use-after-free bugs, the logger must be created after
+            // the stack, to avoid that it's destructor tries to access
+            // parts of the destroyed stack
+
+            static auto dummy = [&] {
+                stack.push_back( &std_logger );
+                return 0;
+            }();
+
+            ( void ) dummy;
+            return stack;
+        }
+
+        void reassign_stack_pointer( logger_set*& ptr ) {
+            const auto old_ptr = ptr;
+
+            if( ptr ) {
+                ptr->m_stackpointer = &ptr;
+            }
+
+            ( void ) old_ptr;
+            assert( ptr == old_ptr );
+        }
+
+        void register_logger( logger_set& set ) {
+            auto& stack = logger_stack();
+
+            // we need to reassign everything if the vector reallocated:
+            const auto old_capacity = stack.capacity();
+            stack.push_back( &set );
+
+            if( stack.capacity() == old_capacity ) {
+                reassign_stack_pointer( stack.back() );
+            } else {
+                for( auto& ptr : stack ) {
+                    reassign_stack_pointer( ptr );
+                }
+            }
+        }
+
+        /**
+         * Pops loggers from the stack until the last one is not a nullptr
+         */
+        void pop_loggers() {
+            auto& stack = logger_stack();
+
+            while( !stack.empty() and stack.back() == nullptr ) {
+                stack.pop_back();
+            }
+
+            assert( stack.empty() or stack.back() != nullptr );
+        }
+
+        logger_set& active_logger() {
+            const auto result = logger_stack().back();
+            assert( result != nullptr );
+            return *result;
+        }
+
+    } // namespace impl
+
+    logger_set::logger_set( std::initializer_list<log_target> lst, auto_register r ):
+        m_loggers{lst}, m_min_level{default_level} {
+        if( lst.size() > 0 ) {
+            m_min_level = std::min_element( lst.begin(), lst.end(),
+                []( const log_target& l, const log_target& r ) {
+                    return l.min_level < r.min_level;
+                } )->min_level;
+        }
+
+        if( r == auto_register::on ) {
+            impl::register_logger( *this );
+        }
+    }
 
-        std::ostream*& ostream_pointer() {
-            static std::ostream* stream = &std::cout;
-            return stream;
+    logger_set::~logger_set() {
+        if( m_stackpointer ) {
+            *m_stackpointer = nullptr;
+            impl::pop_loggers();
         }
+    }
+
+    logger_set::logger_set( logger_set&& other ) noexcept :
+        m_loggers{std::move( other.m_loggers )}, m_stackpointer{other.m_stackpointer}, m_min_level{other.m_min_level} {
+        other.m_stackpointer = nullptr;
+
+        if( m_stackpointer ) {
+            *m_stackpointer = this;
+        }
+    }
+
+    logger_set& logger_set::operator=( logger_set && other ) noexcept {
+        if( m_stackpointer ) {
+            *m_stackpointer = nullptr;
+            impl::pop_loggers();
+        }
+
+        m_loggers = std::move( other.m_loggers );
+        m_stackpointer = other.m_stackpointer;
+        m_min_level = other.m_min_level;
+        other.m_stackpointer = nullptr;
 
-        std::ostream& get_stream() {
-            return *ostream_pointer();
+        if( m_stackpointer ) {
+            *m_stackpointer = this;
         }
 
+        return *this;
+    }
+
+    void logger_set::log_impl( level l, const std::string& msg ) {
+        for( auto& logger : m_loggers ) {
+            if( l >= logger.min_level ) {
+                *logger.stream << msg << std::flush;
+            }
+        }
+    }
+
+    logger_set current_logger_extended( std::initializer_list<log_target> further_targets ) {
+        auto& active = impl::active_logger();
+        auto returnvalue = logger_set{further_targets};
+        returnvalue.m_loggers.insert( returnvalue.m_loggers.end(), active.m_loggers.begin(), active.m_loggers.end() );
+        returnvalue.m_min_level = active.m_min_level;
+        return returnvalue;
+    }
+
+    namespace {
+
         std::string make_prefix( level l ) {
             auto prefix = std::string {};
 
@@ -74,18 +204,20 @@ namespace logger {
             return returnstring;
         }
 
-        void log( level l, const std::vector<std::string>& args ) {
-            const auto prefix = make_prefix( l );
-            const auto length = prefix.length();
-            get_stream() << prefix;
-            std::transform( args.begin(), args.end(), std::ostream_iterator<std::string> {get_stream()},
-                [length]( const std::string & str ) {
-                    return replace_newlines( str, length );
-                } );
-            get_stream() << '\n' << std::flush;
+
+        std::string concat_msg( level l, const std::vector<std::string>& args ) {
+            auto msg = make_prefix( l );
+            const auto prefix_length = msg.length();
+
+            for( const auto& arg : args ) {
+                msg += replace_newlines( arg, prefix_length );
+            }
+
+            msg += '\n';
+            return msg;
         }
 
-        void logf( level l, const std::string& format, std::vector<std::string> args ) {
+        std::string format_msg( level l, const std::string& format, std::vector<std::string> args ) {
             const auto prefix = make_prefix( l );
             const auto length = prefix.length();
             const auto fmt = replace_newlines( format, length );
@@ -94,14 +226,14 @@ namespace logger {
                     return replace_newlines( str, length );
                 } );
 
-            auto mesg = prefix;
+            auto msg = prefix;
             auto arg_index = std::size_t {0};
             auto it = fmt.begin();
             const auto end = fmt.end();
 
             while( it != end ) {
                 auto pos = std::find( it, end, '%' );
-                mesg.append( it, pos );
+                msg.append( it, pos );
 
                 if( pos == end ) {
                     break;
@@ -115,7 +247,7 @@ namespace logger {
 
                 switch( *pos ) {
                 case '%':
-                    mesg.push_back( '%' );
+                    msg.push_back( '%' );
                     break;
 
                 case 's':
@@ -123,7 +255,7 @@ namespace logger {
                         throw std::invalid_argument {"Invalid formatstring (not enough arguments)"};
                     }
 
-                    mesg.append( args[arg_index++] );
+                    msg.append( args[arg_index++] );
                     break;
 
                 default:
@@ -133,14 +265,10 @@ namespace logger {
                 it = std::next( pos );
             }
 
-            mesg.push_back( '\n' );
-            get_stream() << mesg << std::flush;
+            msg.push_back( '\n' );
+            return msg;
         }
 
     } //  namespace impl
 
-    void set_stream( std::ostream& stream ) {
-        ostream_pointer() = &stream;
-    }
-
 } // namespace logger
index 6d1e5e50d0f9b9b0a33075ac69a8300ea3f32c05..3045365417c2db7c527c61e56dbdc233c981ae7b 100644 (file)
@@ -1,16 +1,15 @@
 #pragma once
 
-#include <ostream>
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
 #include <string>
 #include <sstream>
 #include <vector>
+#include <fstream>
 
 namespace logger {
 
-    enum class level {
-        debug, note, warn, error, fatal
-    };
-
     /**
      * conv::to_string will be used to convert whatever argument is send
      * to the logger to a string. If another type shall be supported,
@@ -33,25 +32,168 @@ namespace logger {
         inline std::string to_string( const char* arg ) {
             return arg;
         }
+
+    } // namespace conv
+
+    enum class level {
+        debug, note, warn, error, fatal
+    };
+
+    const auto default_level = level::note;
+
+    struct log_target {
+        log_target( std::ostream& stream, level min_level = default_level ):
+            stream {&stream}, min_level {min_level} {}
+        log_target( std::ofstream& stream, level min_level = default_level ):
+            stream {&stream}, min_level {min_level} {
+            if( !stream.is_open() ) {
+                throw std::runtime_error {"logfile not open"};
+            }
+        }
+        std::ostream* stream;
+        level min_level;
+    };
+
+    class logger_set;
+    namespace impl {
+        void reassign_stack_pointer( logger_set*& ptr );
     }
 
+    /**
+     * Provides controll over wether a logger should automatically register itself
+     */
+    enum class auto_register {
+        on, off
+    };
+
+    class logger_set {
+    public:
+        logger_set( std::initializer_list<log_target> lst, auto_register = auto_register::on );
+
+        logger_set( logger_set&& ) noexcept;
+        logger_set& operator=( logger_set && ) noexcept;
+        ~logger_set();
+
+        template<typename... Args>
+        void log( level l, Args&& ... args );
+        template<typename... Args>
+        void logf( level l, const std::string& format, Args&& ... args );
+
+        template<typename... Args>
+        void debug( Args&& ... args );
+        template<typename... Args>
+        void debugf( const std::string& format, Args&& ... args );
+
+        template<typename... Args>
+        void note( Args&& ... args );
+        template<typename... Args>
+        void notef( const std::string& format, Args&& ... args );
+
+        template<typename... Args>
+        void warn( Args&& ... args );
+        template<typename... Args>
+        void warnf( const std::string& format, Args&& ... args );
+
+        template<typename... Args>
+        void error( Args&& ... args );
+        template<typename... Args>
+        void errorf( const std::string& format, Args&& ... args );
+
+        template<typename... Args>
+        void fatal( Args&& ... args );
+        template<typename... Args>
+        void fatalf( const std::string& format, Args&& ... args );
+
+        friend void impl::reassign_stack_pointer( logger_set*& ptr );
+        friend logger_set current_logger_extended( std::initializer_list<log_target> further_targets );
+    private:
+        void log_impl( level l, const std::string& msg );
+
+        std::vector<log_target> m_loggers;
+        logger_set** m_stackpointer = nullptr;
+        level m_min_level;
+    };
+
+    logger_set current_logger_extended( std::initializer_list<log_target> further_targets );
+
     namespace impl {
-        void log( level, const std::vector<std::string>& args );
-        void logf( level, const std::string&, std::vector<std::string> args );
+        std::string concat_msg( level l, const std::vector<std::string>& args );
+        std::string format_msg( level l, const std::string&, std::vector<std::string> args );
+        logger_set& active_logger();
     }
 
-    void set_stream( std::ostream& );
+    template <typename... Args>
+    void logger_set::log( level l, Args&& ... data ) {
+        if( l < m_min_level ) {
+            return;
+        }
+
+        log_impl( l, impl::concat_msg( l, {conv::to_string( std::forward<Args>( data ) )...} ) );
+    }
 
     template <typename... Args>
-    void log( level l, Args&& ... data ) {
-        impl::log( l, {conv::to_string( std::forward<Args>( data ) )...} );
+    void logger_set::logf( level l, const std::string& format, Args&& ... data ) {
+        if( l < m_min_level ) {
+            return;
+        }
+
+        log_impl( l, impl::format_msg( l, format, {conv::to_string( std::forward<Args>( data ) )...} ) );
     }
 
     template <typename... Args>
-    void logf( level l, const std::string& format, Args&& ... data ) {
-        impl::logf( l, format, {conv::to_string( std::forward<Args>( data ) )...} );
+    void log( level l, Args&& ... args ) {
+        impl::active_logger().log( l, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logf( level l, const std::string& format, Args&& ... args ) {
+        impl::active_logger().logf( l, format, std::forward<Args>( args )... );
+    }
+
+    // concat-based methods
+    template <typename... Args>
+    void logger_set::debug( Args&& ... args ) {
+        log( level::debug, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::note( Args&& ... args ) {
+        log( level::note, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::warn( Args&& ... args ) {
+        log( level::warn, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::error( Args&& ... args ) {
+        log( level::error, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::fatal( Args&& ... args ) {
+        log( level::fatal, std::forward<Args>( args )... );
+    }
+
+    // format-based methods
+    template <typename... Args>
+    void logger_set::debugf( const std::string& fmt, Args&& ... args ) {
+        logf( level::debug, fmt, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::notef( const std::string& fmt, Args&& ... args ) {
+        logf( level::note, fmt, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::warnf( const std::string& fmt, Args&& ... args ) {
+        logf( level::warn, fmt, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::errorf( const std::string& fmt, Args&& ... args ) {
+        logf( level::error, fmt, std::forward<Args>( args )... );
+    }
+    template <typename... Args>
+    void logger_set::fatalf( const std::string& fmt, Args&& ... args ) {
+        logf( level::fatal, fmt, std::forward<Args>( args )... );
     }
 
+    // global concat-based
     template <typename... Args>
     void debug( Args&& ... args ) {
         log( level::debug, std::forward<Args>( args )... );
@@ -73,6 +215,7 @@ namespace logger {
         log( level::fatal, std::forward<Args>( args )... );
     }
 
+    // global format-based
     template <typename... Args>
     void debugf( const std::string& fmt, Args&& ... args ) {
         logf( level::debug, fmt, std::forward<Args>( args )... );
diff --git a/test/src/log.cpp b/test/src/log.cpp
new file mode 100644 (file)
index 0000000..ce7a7ac
--- /dev/null
@@ -0,0 +1,392 @@
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+#include <boost/test/unit_test.hpp>
+
+#include "log/logger.hpp"
+#include "log/format.hpp"
+
+BOOST_AUTO_TEST_SUITE( TestLogger )
+
+static inline bool head_and_tail_equal( const std::string& str, const std::string& head, const std::string& tail ) {
+    return str.size() >= head.size() + tail.size()
+            and std::equal( head.begin(), head.end(), str.begin() )
+            and std::equal( tail.rbegin(), tail.rend(), str.rbegin() )
+            ;
+}
+
+BOOST_AUTO_TEST_CASE( basic_log ) {
+    auto stream = std::ostringstream{};
+    auto logger = logger::logger_set{stream};
+
+    logger.log( logger::level::note, "foo", " bar ", 23, ' ', 42.0, " baz" );
+
+    BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: foo bar 23 42 baz\n" ) );
+}
+
+BOOST_AUTO_TEST_CASE( basic_logf ) {
+    auto stream = std::ostringstream{};
+    auto logger = logger::logger_set{stream};
+
+    logger.logf( logger::level::note, "bla%sblub%s%%", "foo", 42 );
+
+    BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: blafooblub42%\n" ) );
+}
+
+BOOST_AUTO_TEST_CASE( log_hiding ) {
+    auto stream1 = std::ostringstream{};
+    auto logger1 = logger::logger_set{stream1};
+
+    auto stream2 = std::ostringstream{};
+    auto logger2 = logger::logger_set{stream2};
+
+    logger::note( "foobar" );
+
+    BOOST_CHECK( stream1.str().empty() );
+    BOOST_CHECK( head_and_tail_equal( stream2.str(), "[note ][", "]: foobar\n" ) );
+}
+
+BOOST_AUTO_TEST_CASE( log_restoration ) {
+    auto stream1 = std::ostringstream{};
+    auto logger1 = logger::logger_set{stream1};
+
+    {
+        auto stream2 = std::ostringstream{};
+        auto logger2 = logger::logger_set{stream2};
+    }
+
+    logger::note( "foobar" );
+
+    BOOST_CHECK( head_and_tail_equal( stream1.str(), "[note ][", "]: foobar\n" ) );
+}
+
+BOOST_AUTO_TEST_CASE( non_global_log ) {
+    auto stream1 = std::ostringstream{};
+    auto logger1 = logger::logger_set{stream1};
+
+    auto stream2 = std::ostringstream{};
+    auto logger2 = logger::logger_set{{stream2}, logger::auto_register::off};
+
+    logger::note( "foobar" );
+
+    BOOST_CHECK( head_and_tail_equal( stream1.str(), "[note ][", "]: foobar\n" ) );
+    BOOST_CHECK( stream2.str().empty() );
+}
+
+BOOST_AUTO_TEST_CASE( concat_alias_methods ) {
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::debug}};
+
+        logger.debug( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[debug][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::note}};
+
+        logger.note( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::warn}};
+
+        logger.warn( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[warn ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::error}};
+
+        logger.error( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[error][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::fatal}};
+
+        logger.fatal( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[fatal][", "]: foo\n" ) );
+    }
+}
+
+BOOST_AUTO_TEST_CASE( format_alias_methods ) {
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::debug}};
+
+        logger.debugf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[debug][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::note}};
+
+        logger.notef( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::warn}};
+
+        logger.warnf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[warn ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::error}};
+
+        logger.errorf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[error][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::fatal}};
+
+        logger.fatalf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[fatal][", "]: foo\n" ) );
+    }
+}
+
+BOOST_AUTO_TEST_CASE( concat_alias_functions ) {
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::debug}};
+
+        logger::debug( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[debug][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::note}};
+
+        logger::note( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::warn}};
+
+        logger::warn( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[warn ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::error}};
+
+        logger::error( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[error][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::fatal}};
+
+        logger::fatal( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[fatal][", "]: foo\n" ) );
+    }
+}
+
+BOOST_AUTO_TEST_CASE( format_alias_functions ) {
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::debug}};
+
+        logger::debugf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[debug][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::note}};
+
+        logger::notef( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[note ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::warn}};
+
+        logger::warnf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[warn ][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::error}};
+
+        logger::errorf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[error][", "]: foo\n" ) );
+    }
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{{stream, logger::level::fatal}};
+
+        logger::fatalf( "foo" );
+
+        BOOST_CHECK( head_and_tail_equal( stream.str(), "[fatal][", "]: foo\n" ) );
+    }
+}
+
+BOOST_AUTO_TEST_CASE( formatting_exceptions ) {
+    auto stream = std::ostringstream{};
+    auto logger = logger::logger_set{stream};
+
+    BOOST_CHECK_THROW( logger.notef( "%" ), std::invalid_argument );
+    BOOST_CHECK_THROW( logger.notef( "%s" ), std::invalid_argument );
+    BOOST_CHECK_THROW( logger.notef( "%e" ), std::invalid_argument );
+}
+
+BOOST_AUTO_TEST_CASE( multiple_calls ) {
+    auto stream = std::ostringstream{};
+    auto logger = logger::logger_set{stream};
+
+    logger::note( "foo1" );
+    logger::debug( "foo2" );
+    logger::warn( "foo3" );
+    logger::note( "foo4" );
+
+    const auto result = stream.str();
+
+    const auto foo1 = result.find( "foo1" );
+    const auto foo2 = result.find( "foo2" );
+    const auto foo3 = result.find( "foo3" );
+    const auto foo4 = result.find( "foo4" );
+
+    BOOST_CHECK_LT( foo1, foo3 );
+    BOOST_CHECK_LT( foo3, foo4 );
+    BOOST_CHECK_NE( foo4, std::string::npos );
+    BOOST_CHECK_EQUAL( foo2, std::string::npos );
+}
+
+BOOST_AUTO_TEST_CASE( multiple_calls_nested ) {
+    auto stream = std::ostringstream{};
+    auto logger = logger::logger_set{stream};
+
+    logger::note( "foo1" );
+
+    {
+        auto stream = std::ostringstream{};
+        auto logger = logger::logger_set{stream};
+
+        logger::note( "foo2" );
+    }
+
+    logger::note( "foo3" );
+
+    const auto result = stream.str();
+    const auto foo1 = result.find( "foo1" );
+    const auto foo2 = result.find( "foo2" );
+    const auto foo3 = result.find( "foo3" );
+
+    BOOST_CHECK_LT( foo1, foo3 );
+    BOOST_CHECK_NE( foo3, std::string::npos );
+    BOOST_CHECK_EQUAL( foo2, std::string::npos );
+}
+
+BOOST_AUTO_TEST_CASE( extending_current_logger ) {
+    auto stream1 = std::ostringstream{};
+    auto logger1 = logger::logger_set{stream1};
+
+    auto stream2 = std::ostringstream{};
+    {
+        auto logger2 = logger::current_logger_extended( {stream2} );
+        logger::note( "foo1" );
+    }
+
+    BOOST_CHECK( head_and_tail_equal( stream1.str(), "[note ][", "]: foo1\n" ) );
+    BOOST_CHECK( head_and_tail_equal( stream2.str(), "[note ][", "]: foo1\n" ) );
+
+    stream1.str( "" );
+    stream2.str( "" );
+
+    logger::note( "foo2" );
+
+    BOOST_CHECK( head_and_tail_equal( stream1.str(), "[note ][", "]: foo2\n" ) );
+    BOOST_CHECK( stream2.str().empty() );
+}
+
+BOOST_AUTO_TEST_CASE( closed_filestream_exception ) {
+    std::ofstream stream;
+
+    BOOST_CHECK_THROW( logger::logger_set {stream}, std::runtime_error );
+}
+
+BOOST_AUTO_TEST_CASE( formated_strings ) {
+    using namespace logger::format::literals;
+    using logger::conv::to_string;
+
+    BOOST_CHECK_EQUAL( to_string( ""_fmt( "foo" ) ), "foo" );
+    BOOST_CHECK_EQUAL( to_string( "_3"_fmt( "foo" ) ), "foo" );
+    BOOST_CHECK_EQUAL( to_string( "_6"_fmt( "foo" ) ), "foo___" );
+    BOOST_CHECK_EQUAL( to_string( "_10l"_fmt( "foo" ) ), "foo_______" );
+    BOOST_CHECK_EQUAL( to_string( "_10r"_fmt( "foo" ) ), "_______foo" );
+}
+
+BOOST_AUTO_TEST_CASE( formated_ints ) {
+    using namespace logger::format::literals;
+    using logger::conv::to_string;
+
+    BOOST_CHECK_EQUAL( to_string( ""_fmt( 3 ) ), "3" );
+    BOOST_CHECK_EQUAL( to_string( "03"_fmt( 3 ) ), "003" );
+    BOOST_CHECK_EQUAL( to_string( "03"_fmt( 13 ) ), "013" );
+    BOOST_CHECK_EQUAL( to_string( "03x"_fmt( 13 ) ), "00d" );
+    BOOST_CHECK_EQUAL( to_string( "03o"_fmt( 13 ) ), "015" );
+    BOOST_CHECK_EQUAL( to_string( "03d"_fmt( 13 ) ), "013" );
+    BOOST_CHECK_EQUAL( to_string( "03s"_fmt( 13 ) ), "013" );
+}
+
+BOOST_AUTO_TEST_CASE( formated_ints_variadic_api ) {
+    using logger::conv::to_string;
+    using logger::format::fmt;
+
+    BOOST_CHECK_EQUAL( to_string( fmt( 3 ) ), "3" );
+    BOOST_CHECK_EQUAL( to_string( fmt( 3, logger::format::width_t {3} ) ), "  3" );
+}
+
+BOOST_AUTO_TEST_CASE( formated_ints_variadic_api_literals ) {
+    using logger::conv::to_string;
+    using logger::format::fmt;
+
+    using namespace logger::format::literals;
+
+    BOOST_CHECK_EQUAL( to_string( fmt( 3 ) ), "3" );
+    BOOST_CHECK_EQUAL( to_string( fmt( 3, 3_w ) ), "  3" );
+    BOOST_CHECK_EQUAL( to_string( fmt( 10, 3_w, 8_b, 'x'_f ) ), "x12" );
+}
+
+BOOST_AUTO_TEST_SUITE_END()