From d450e7f4282772a0d9ce478bdf8d8a9e1cfcd813 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Sat, 18 Jul 2015 20:51:53 +0200 Subject: [PATCH] upd: Reimported logger library by Florian Weber from updated upstream --- src/log/format.cpp | 107 +++++++++++++ src/log/format.hpp | 130 +++++++++++++++ src/log/logger.cpp | 182 +++++++++++++++++---- src/log/logger.hpp | 167 +++++++++++++++++-- test/src/log.cpp | 392 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 939 insertions(+), 39 deletions(-) create mode 100644 src/log/format.cpp create mode 100644 src/log/format.hpp create mode 100644 test/src/log.cpp diff --git a/src/log/format.cpp b/src/log/format.cpp new file mode 100644 index 0000000..1edd6a0 --- /dev/null +++ b/src/log/format.cpp @@ -0,0 +1,107 @@ +#include "log/format.hpp" + +#include +#include +#include + +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 index 0000000..827f7ea --- /dev/null +++ b/src/log/format.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace logger { + + namespace format { + + template + 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( value )}; + } + inline base_t operator"" _b( unsigned long long value ) { + return base_t{static_cast( 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 ::value>::type> + formated_integer operator()( Integer i ) const; + }; + + template + 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 + inline formated_integer format_data::operator()( Integer i ) const { + return {i, *this}; + } + + template + formated_string fmt( const std::string& str, const Args& ... args ) { + auto format = format_data{}; + std::ignore = std::initializer_list{( format.set( args ), 0 )...}; + return format( str ); + } + + template + formated_integer fmt( const Integer i, const Args& ... args ) { + auto format = format_data{}; + std::ignore = std::initializer_list{( format.set( args ), 0 )...}; + return format( i ); + } + + inline namespace literals { + format_data operator"" _fmt( const char*, std::size_t ); + } + + } // namespace format + + namespace conv { + + template + inline std::string to_string( const format::formated_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 diff --git a/src/log/logger.cpp b/src/log/logger.cpp index 9a70300..aee4d29 100644 --- a/src/log/logger.cpp +++ b/src/log/logger.cpp @@ -1,24 +1,154 @@ #include "log/logger.hpp" +#include +#include #include #include #include -#include +#include #include namespace logger { - namespace { + namespace impl { + + /** + * Manages the standard-logger and the logger-stack. + * + * CAREFULL: THIS FUNCTION CONTAINS GLOBAL STATE! + */ + std::vector& logger_stack() { + static auto stack = std::vector {}; + // 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 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 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& args ) { - const auto prefix = make_prefix( l ); - const auto length = prefix.length(); - get_stream() << prefix; - std::transform( args.begin(), args.end(), std::ostream_iterator {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& 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 args ) { + std::string format_msg( level l, const std::string& format, std::vector 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 diff --git a/src/log/logger.hpp b/src/log/logger.hpp index 6d1e5e5..3045365 100644 --- a/src/log/logger.hpp +++ b/src/log/logger.hpp @@ -1,16 +1,15 @@ #pragma once -#include +#include +#include +#include #include #include #include +#include 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 lst, auto_register = auto_register::on ); + + logger_set( logger_set&& ) noexcept; + logger_set& operator=( logger_set && ) noexcept; + ~logger_set(); + + template + void log( level l, Args&& ... args ); + template + void logf( level l, const std::string& format, Args&& ... args ); + + template + void debug( Args&& ... args ); + template + void debugf( const std::string& format, Args&& ... args ); + + template + void note( Args&& ... args ); + template + void notef( const std::string& format, Args&& ... args ); + + template + void warn( Args&& ... args ); + template + void warnf( const std::string& format, Args&& ... args ); + + template + void error( Args&& ... args ); + template + void errorf( const std::string& format, Args&& ... args ); + + template + void fatal( Args&& ... args ); + template + 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 further_targets ); + private: + void log_impl( level l, const std::string& msg ); + + std::vector m_loggers; + logger_set** m_stackpointer = nullptr; + level m_min_level; + }; + + logger_set current_logger_extended( std::initializer_list further_targets ); + namespace impl { - void log( level, const std::vector& args ); - void logf( level, const std::string&, std::vector args ); + std::string concat_msg( level l, const std::vector& args ); + std::string format_msg( level l, const std::string&, std::vector args ); + logger_set& active_logger(); } - void set_stream( std::ostream& ); + template + 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( data ) )...} ) ); + } template - void log( level l, Args&& ... data ) { - impl::log( l, {conv::to_string( std::forward( 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( data ) )...} ) ); } template - void logf( level l, const std::string& format, Args&& ... data ) { - impl::logf( l, format, {conv::to_string( std::forward( data ) )...} ); + void log( level l, Args&& ... args ) { + impl::active_logger().log( l, std::forward( args )... ); + } + template + void logf( level l, const std::string& format, Args&& ... args ) { + impl::active_logger().logf( l, format, std::forward( args )... ); + } + + // concat-based methods + template + void logger_set::debug( Args&& ... args ) { + log( level::debug, std::forward( args )... ); + } + template + void logger_set::note( Args&& ... args ) { + log( level::note, std::forward( args )... ); + } + template + void logger_set::warn( Args&& ... args ) { + log( level::warn, std::forward( args )... ); + } + template + void logger_set::error( Args&& ... args ) { + log( level::error, std::forward( args )... ); + } + template + void logger_set::fatal( Args&& ... args ) { + log( level::fatal, std::forward( args )... ); + } + + // format-based methods + template + void logger_set::debugf( const std::string& fmt, Args&& ... args ) { + logf( level::debug, fmt, std::forward( args )... ); + } + template + void logger_set::notef( const std::string& fmt, Args&& ... args ) { + logf( level::note, fmt, std::forward( args )... ); + } + template + void logger_set::warnf( const std::string& fmt, Args&& ... args ) { + logf( level::warn, fmt, std::forward( args )... ); + } + template + void logger_set::errorf( const std::string& fmt, Args&& ... args ) { + logf( level::error, fmt, std::forward( args )... ); + } + template + void logger_set::fatalf( const std::string& fmt, Args&& ... args ) { + logf( level::fatal, fmt, std::forward( args )... ); } + // global concat-based template void debug( Args&& ... args ) { log( level::debug, std::forward( args )... ); @@ -73,6 +215,7 @@ namespace logger { log( level::fatal, std::forward( args )... ); } + // global format-based template void debugf( const std::string& fmt, Args&& ... args ) { logf( level::debug, fmt, std::forward( args )... ); diff --git a/test/src/log.cpp b/test/src/log.cpp new file mode 100644 index 0000000..ce7a7ac --- /dev/null +++ b/test/src/log.cpp @@ -0,0 +1,392 @@ +#include +#include +#include + +#include + +#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() -- 2.39.2