diff --git a/include/fc/log/file_appender.hpp b/include/fc/log/file_appender.hpp index 8adaf6a..a6f0e76 100644 --- a/include/fc/log/file_appender.hpp +++ b/include/fc/log/file_appender.hpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace fc { @@ -16,10 +17,13 @@ class file_appender : public appender { fc::path filename; bool flush; bool truncate; + bool rotate; + microseconds rotation_interval; + microseconds rotation_limit; }; file_appender( const variant& args ); ~file_appender(); - virtual void log( const log_message& m ); + virtual void log( const log_message& m )override; private: class impl; @@ -28,4 +32,4 @@ class file_appender : public appender { } // namespace fc #include -FC_REFLECT( fc::file_appender::config, (format)(filename)(flush)(truncate) ) +FC_REFLECT( fc::file_appender::config, (format)(filename)(flush)(truncate)(rotate)(rotation_interval)(rotation_limit) ) diff --git a/src/log/file_appender.cpp b/src/log/file_appender.cpp index 7521d26..55a4693 100644 --- a/src/log/file_appender.cpp +++ b/src/log/file_appender.cpp @@ -16,19 +16,42 @@ namespace fc { config cfg; ofstream out; boost::mutex slock; + time_point_sec current_file_start_time; + + time_point_sec get_file_start_time( const time_point_sec& timestamp, const microseconds& interval ) + { + const auto interval_seconds = interval.to_seconds(); + const auto file_number = timestamp.sec_since_epoch() / interval_seconds; + return time_point_sec( file_number * interval_seconds ); + } }; file_appender::config::config( const fc::path& p ) :format( "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}" ), - filename(p),flush(true),truncate(true){} + filename(p),flush(true),truncate(true),rotate(false){} file_appender::file_appender( const variant& args ) :my( new impl() ) { - try { + std::string log_filename; + try + { my->cfg = args.as(); - fc::create_directories( fc::path( my->cfg.filename.string() ).parent_path() ); - my->out.open( my->cfg.filename.string().c_str() ); - } catch ( ... ) { + log_filename = my->cfg.filename.string(); + + fc::create_directories( fc::path( log_filename ).parent_path() ); + + if( my->cfg.rotate ) + { + const auto start_time = my->get_file_start_time( time_point::now(), my->cfg.rotation_interval ); + // TODO: Convert to proper timestamp string + log_filename += "." + std::to_string( start_time.sec_since_epoch() ); + my->current_file_start_time = start_time; + } + + my->out.open( log_filename.c_str() ); + } + catch( ... ) + { std::cerr << "error opening log file: " << my->cfg.filename.string() << "\n"; //elog( "%s", fc::except_str().c_str() ); } @@ -38,6 +61,7 @@ namespace fc { // MS THREAD METHOD MESSAGE \t\t\t File:Line void file_appender::log( const log_message& m ) { + const auto now = time_point::now(); std::stringstream line; //line << (m.get_context().get_timestamp().time_since_epoch().count() % (1000ll*1000ll*60ll*60))/1000 <<"ms "; line << std::string(m.get_context().get_timestamp()) << " "; @@ -60,14 +84,53 @@ namespace fc { fc::string message = fc::format_string( m.get_format(), m.get_data() ); line << message.c_str(); - //fc::variant lmsg(m); - // fc::string fmt_str = fc::format_string( my->cfg.format, mutable_variant_object(m.get_context())( "message", message) ); + // fc::string fmt_str = fc::format_string( my->cfg.format, mutable_variant_object(m.get_context())( "message", message) ); + + /* Write to log file (rotating file beforehand if necessary) */ { - fc::scoped_lock lock(my->slock); + fc::scoped_lock lock( my->slock ); + + if( my->cfg.rotate ) + { + const auto start_time = my->get_file_start_time( now, my->cfg.rotation_interval ); + if( start_time > my->current_file_start_time ) + { + my->out.close(); + auto log_filename = my->cfg.filename.string(); + + /* Delete old log files */ + // TODO: Delete on startup as well + const auto limit_time = now - my->cfg.rotation_limit; + auto itr = directory_iterator( fc::path( log_filename ).parent_path() ); + for( ; itr != directory_iterator(); itr++ ) + { + const auto current_filename = itr->string(); + const auto current_pos = current_filename.find( log_filename ); + if( current_pos != 0 ) continue; + const auto current_timestamp = current_filename.substr( log_filename.size() + 1 ); + try + { + if( std::stoi( current_timestamp ) < limit_time.sec_since_epoch() ) + remove_all( current_filename ); + //else compress if not already compressed + } + catch( ... ) + { + } + } + + // TODO: Convert to proper timestamp string + log_filename += "." + std::to_string( start_time.sec_since_epoch() ); + my->out.open( log_filename.c_str() ); + + my->current_file_start_time = start_time; + } + } + my->out << line.str() << "\t\t\t" << m.get_context().get_file() <<":"<cfg.flush ) my->out.flush(); } - if( my->cfg.flush ) my->out.flush(); } }