// Copyright (C) 2006 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_LOGGER_KERNEl_1_ #define DLIB_LOGGER_KERNEl_1_ #include <limits> #include <memory> #include <cstring> #include <streambuf> #include <vector> #include "../threads.h" #include "../misc_api.h" #include "../set.h" #include "logger_kernel_abstract.h" #include "../algs.h" #include "../assert.h" #include "../uintn.h" #include "../map.h" #include "../member_function_pointer.h" namespace dlib { // ---------------------------------------------------------------------------------------- class log_level { public: log_level( int priority_, const char* name_ ) : priority(priority_) { strncpy(name,name_,19); name[19] = '\0'; } bool operator< (const log_level& rhs) const { return priority < rhs.priority; } bool operator<=(const log_level& rhs) const { return priority <= rhs.priority; } bool operator> (const log_level& rhs) const { return priority > rhs.priority; } bool operator>=(const log_level& rhs) const { return priority >= rhs.priority; } int priority; char name[20]; }; inline std::ostream& operator<< (std::ostream& out, const log_level& item) { out << item.name; return out; } const log_level LALL (std::numeric_limits<int>::min(),"ALL"); const log_level LNONE (std::numeric_limits<int>::max(),"NONE"); const log_level LTRACE(-100,"TRACE"); const log_level LDEBUG(0 ,"DEBUG"); const log_level LINFO (100,"INFO "); const log_level LWARN (200,"WARN "); const log_level LERROR(300,"ERROR"); const log_level LFATAL(400,"FATAL"); // ---------------------------------------------------------------------------------------- void set_all_logging_output_streams ( std::ostream& out ); void set_all_logging_levels ( const log_level& new_level ); typedef void (*print_header_type)( std::ostream& out, const std::string& logger_name, const log_level& l, const uint64 thread_id ); void set_all_logging_headers ( const print_header_type& new_header ); // ---------------------------------------------------------------------------------------- void print_default_logger_header ( std::ostream& out, const std::string& logger_name, const log_level& l, const uint64 thread_id ); template < typename T > void set_all_logging_output_hooks ( T& object, void (T::*hook_)(const std::string& logger_name, const log_level& l, const uint64 thread_id, const char* message_to_log) ); template < typename T > void set_all_logging_output_hooks ( T& object ) { set_all_logging_output_hooks(object, &T::log); } // ---------------------------------------------------------------------------------------- class logger { /*! INITIAL VALUE - print_header == print_default_logger_header - out.rdbuf() == std::cout.rdbuf() - cur_level == LERROR - auto_flush_enabled == true - hook.is_set() == false CONVENTION - print_header == logger_header() - if (hook.is_set() == false) then - out.rdbuf() == output_streambuf() - else - out.rdbuf() == &gd.hookbuf - output_streambuf() == 0 - cur_level == level() - logger_name == name() - auto_flush_enabled == auto_flush() - logger::gd::loggers == a set containing all currently existing loggers. - logger::gd::m == the mutex used to lock everything in the logger - logger::gd::thread_names == a map of thread ids to thread names. - logger::gd::next_thread_name == the next thread name that will be given out to a thread when we find that it isn't already in thread_names. !*/ // ------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------ class logger_stream { /*! INITIAL VALUE - been_used == false CONVENTION - enabled == is_enabled() - if (been_used) then - logger::gd::m is locked - someone has used the << operator to write something to the output stream. !*/ public: logger_stream ( const log_level& l_, logger& log_ ) : l(l_), log(log_), been_used(false), enabled (l.priority >= log.cur_level.priority) {} inline ~logger_stream( ) { if (!been_used) { return; } else { print_end_of_line(); } } bool is_enabled ( ) const { return enabled; } template <typename T> inline logger_stream& operator << ( const T& item ) { if (!enabled) { return *this; } else { print_header_and_stuff(); log.out << item; return *this; } } private: void print_header_and_stuff ( ); /*! ensures - if (!been_used) then - prints the logger header - locks log.gd.m - #been_used == true !*/ void print_end_of_line ( ); /*! ensures - prints a newline to log.out - unlocks log.gd.m !*/ const log_level& l; logger& log; bool been_used; const bool enabled; }; // end of class logger_stream // ------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------ friend class logger_stream; public: typedef member_function_pointer<const std::string&, const log_level&, const uint64, const char*> hook_mfp; logger ( const std::string& name_ ); virtual ~logger ( ); const std::string& name ( ) const { return logger_name; } logger_stream operator << ( const log_level& l ) const { return logger_stream(l,const_cast<logger&>(*this)); } bool is_child_of ( const logger& log ) const { return (name().find(log.name() + ".") == 0) || (log.name() == name()); } const log_level level ( ) const { auto_mutex M(gd.m); return log_level(cur_level); }; void set_level ( const log_level& new_level ) { auto_mutex M(gd.m); gd.loggers.reset(); while (gd.loggers.move_next()) { if (gd.loggers.element()->is_child_of(*this)) gd.loggers.element()->cur_level = new_level; } gd.set_level(logger_name, new_level); } bool auto_flush ( ) const { auto_mutex M(gd.m); return auto_flush_enabled; }; void set_auto_flush ( bool enabled ) { auto_mutex M(gd.m); gd.loggers.reset(); while (gd.loggers.move_next()) { if (gd.loggers.element()->is_child_of(*this)) gd.loggers.element()->auto_flush_enabled = enabled; } gd.set_auto_flush(logger_name, enabled); } std::streambuf* output_streambuf ( ) { auto_mutex M(gd.m); // if there is an output hook set then we are supposed to return 0. if (hook) return 0; else return out.rdbuf(); } template < typename T > void set_output_hook ( T& object, void (T::*hook_)(const std::string& logger_name, const log_level& l, const uint64 thread_id, const char* message_to_log) ) { auto_mutex M(gd.m); hook.set(object, hook_); gd.loggers.reset(); while (gd.loggers.move_next()) { if (gd.loggers.element()->is_child_of(*this)) { gd.loggers.element()->out.rdbuf(&gd.hookbuf); gd.loggers.element()->hook = hook; } } gd.set_output_hook(logger_name, hook); gd.set_output_stream(logger_name, gd.hookbuf); } void set_output_stream ( std::ostream& out_ ) { auto_mutex M(gd.m); gd.loggers.reset(); while (gd.loggers.move_next()) { if (gd.loggers.element()->is_child_of(*this)) { gd.loggers.element()->out.rdbuf(out_.rdbuf()); gd.loggers.element()->hook.clear(); } } gd.set_output_stream(logger_name, out_); hook.clear(); gd.set_output_hook(logger_name, hook); } print_header_type logger_header ( ) const { return print_header; } void set_logger_header ( print_header_type ph ) { auto_mutex M(gd.m); gd.loggers.reset(); while (gd.loggers.move_next()) { if (gd.loggers.element()->is_child_of(*this)) gd.loggers.element()->print_header = ph; } gd.set_logger_header(logger_name, ph); } private: // ------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------ struct global_data { rmutex m; set<logger*>::kernel_1b loggers; map<thread_id_type,uint64>::kernel_1b thread_names; uint64 next_thread_name; // Make a very simple streambuf that writes characters into a std::vector<char>. We can // use this as the output target for hooks. The reason we don't just use a std::ostringstream // instead is that this way we can be guaranteed that logging doesn't perform memory allocations. // This is because a std::vector never frees memory. I.e. its capacity() doesn't go down when // you resize it back to 0. It just stays the same. class hook_streambuf : public std::streambuf { public: std::vector<char> buffer; int_type overflow ( int_type c) { if (c != EOF) buffer.push_back(static_cast<char>(c)); return c; } std::streamsize xsputn ( const char* s, std::streamsize num) { buffer.insert(buffer.end(), s, s+num); return num; } }; hook_streambuf hookbuf; global_data ( ); ~global_data( ); uint64 get_thread_name ( ); /*! requires - m is locked ensures - returns a unique id for the calling thread. also makes the number small and nice unlike what you get from get_thread_id() !*/ void thread_end_handler ( ); /*! ensures - removes the terminated thread from thread_names !*/ struct level_container { level_container (); log_level val; map<std::string,std::unique_ptr<level_container> >::kernel_1b_c table; } level_table; const log_level level ( const std::string& name ) const; /*! ensures - returns the level loggers with the given name are supposed to have !*/ void set_level ( const std::string& name, const log_level& new_level ); /*! ensures - for all children C of name: - #level(C) == new_level - if name == "" then - for all loggers L: - #level(L) == new_level !*/ struct auto_flush_container { bool val; map<std::string,std::unique_ptr<auto_flush_container> >::kernel_1b_c table; } auto_flush_table; bool auto_flush ( const std::string& name ) const; /*! ensures - returns the auto_flush value loggers with the given name are supposed to have !*/ void set_auto_flush ( const std::string& name, bool enabled ); /*! ensures - for all children C of name: - #auto_flush_enabled(C) == enabled - if name == "" then - for all loggers L: - #auto_flush_enabled(L) == enabled !*/ struct output_streambuf_container { std::streambuf* val; map<std::string,std::unique_ptr<output_streambuf_container> >::kernel_1b_c table; } streambuf_table; std::streambuf* output_streambuf ( const std::string& name ); /*! ensures - returns the streambuf loggers with the given name are supposed to have !*/ void set_output_stream ( const std::string& name, std::ostream& out_ ); /*! ensures - for all children C of name: - #output_streambuf(C) == out_.rdbuf() - if name == "" then - for all loggers L: - #output_streambuf(L) == out_.rdbuf() !*/ void set_output_stream ( const std::string& name, std::streambuf& buf ); /*! ensures - for all children C of name: - #output_streambuf(C) == &buf - if name == "" then - for all loggers L: - #output_streambuf(L) == &buf !*/ struct output_hook_container { hook_mfp val; map<std::string,std::unique_ptr<output_hook_container> >::kernel_1b_c table; } hook_table; hook_mfp output_hook ( const std::string& name ); /*! ensures - returns the hook loggers with the given name are supposed to have !*/ void set_output_hook ( const std::string& name, const hook_mfp& hook ); /*! ensures - for all children C of name: - #output_hook(C) == hook - if name == "" then - for all loggers L: - #output_hook(L) == hook !*/ struct logger_header_container { print_header_type val; map<std::string,std::unique_ptr<logger_header_container> >::kernel_1b_c table; } header_table; print_header_type logger_header ( const std::string& name ); /*! ensures - returns the header function loggers with the given name are supposed to have !*/ void set_logger_header ( const std::string& name, print_header_type ph ); /*! ensures - for all children C of name: - #logger_header(C) == ph - if name == "" then - for all loggers L: - #logger_header(L) == ph !*/ }; // end of struct global_data static global_data& get_global_data(); // ------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------ friend void set_all_logging_levels ( const log_level& new_level ); friend void set_all_logging_headers ( const print_header_type& new_header ); friend void set_all_logging_output_streams ( std::ostream& out ); template < typename T > friend void set_all_logging_output_hooks ( T& object, void (T::*hook_)(const std::string& logger_name, const log_level& l, const uint64 thread_id, const char* message_to_log) ) { logger::hook_mfp hook; // There is a bug in one of the versions (but not all apparently) of // Visual studio 2005 that causes it to error out if <T> isn't in the // following line of code. However, there is also a bug in gcc-3.3 // that causes it to error out if <T> is present. So this works around // this problem. #if defined(_MSC_VER) && _MSC_VER == 1400 hook.set<T>(object, hook_); #else hook.set(object, hook_); #endif logger::global_data& gd = logger::get_global_data(); auto_mutex M(gd.m); gd.loggers.reset(); while (gd.loggers.move_next()) { gd.loggers.element()->out.rdbuf(&gd.hookbuf); gd.loggers.element()->hook = hook; } gd.set_output_stream("",gd.hookbuf); gd.set_output_hook("",hook); } // ------------------------------------------------------------------------------------ global_data& gd; const std::string logger_name; print_header_type print_header; bool auto_flush_enabled; std::ostream out; log_level cur_level; hook_mfp hook; // restricted functions logger(const logger&); // copy constructor logger& operator=(const logger&); // assignment operator }; // ---------------------------------------------------------------------------------------- } #ifdef NO_MAKEFILE #include "logger_kernel_1.cpp" #endif #endif // DLIB_LOGGER_KERNEl_1_