2014-07-03 05:27:58 +00:00
|
|
|
#include <fc/compress/lzma.hpp>
|
2014-07-03 14:02:40 +00:00
|
|
|
#include <fc/exception/exception.hpp>
|
2014-07-03 05:16:46 +00:00
|
|
|
#include <fc/io/fstream.hpp>
|
2013-06-05 19:19:00 +00:00
|
|
|
#include <fc/log/file_appender.hpp>
|
2014-07-03 05:16:46 +00:00
|
|
|
#include <fc/reflect/variant.hpp>
|
2013-06-05 19:19:00 +00:00
|
|
|
#include <fc/thread/scoped_lock.hpp>
|
2014-07-03 14:02:40 +00:00
|
|
|
#include <fc/thread/thread.hpp>
|
2013-06-05 19:19:00 +00:00
|
|
|
#include <fc/variant.hpp>
|
2014-07-03 05:16:46 +00:00
|
|
|
#include <boost/thread/mutex.hpp>
|
2013-07-03 17:26:23 +00:00
|
|
|
#include <iomanip>
|
2014-07-03 14:02:40 +00:00
|
|
|
#include <queue>
|
2013-07-03 17:26:23 +00:00
|
|
|
#include <sstream>
|
2013-02-05 05:06:16 +00:00
|
|
|
|
|
|
|
|
namespace fc {
|
2014-07-03 14:02:40 +00:00
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
static const string compression_extension( ".lzma" );
|
2014-07-03 14:02:40 +00:00
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
class file_appender::impl : public fc::retainable
|
|
|
|
|
{
|
2013-02-05 05:06:16 +00:00
|
|
|
public:
|
2014-07-03 14:02:40 +00:00
|
|
|
config cfg;
|
|
|
|
|
ofstream out;
|
|
|
|
|
boost::mutex slock;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
future<void> _rotation_task;
|
|
|
|
|
time_point_sec _current_file_start_time;
|
|
|
|
|
std::unique_ptr<thread> _compression_thread;
|
2014-07-02 21:36:21 +00:00
|
|
|
|
|
|
|
|
time_point_sec get_file_start_time( const time_point_sec& timestamp, const microseconds& interval )
|
|
|
|
|
{
|
2014-07-27 21:37:21 +00:00
|
|
|
int64_t interval_seconds = interval.to_seconds();
|
|
|
|
|
int64_t file_number = timestamp.sec_since_epoch() / interval_seconds;
|
|
|
|
|
return time_point_sec( (uint32_t)(file_number * interval_seconds) );
|
2014-07-02 21:36:21 +00:00
|
|
|
}
|
2014-07-03 05:16:46 +00:00
|
|
|
|
2014-07-03 06:17:03 +00:00
|
|
|
string timestamp_to_string( const time_point_sec& timestamp )
|
|
|
|
|
{
|
2014-07-27 01:05:11 +00:00
|
|
|
return timestamp.to_iso_string();
|
2014-07-03 06:17:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time_point_sec string_to_timestamp( const string& str )
|
|
|
|
|
{
|
|
|
|
|
return time_point::from_iso_string( str );
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
void compress_file( const fc::path& filename )
|
2014-07-03 14:02:40 +00:00
|
|
|
{
|
|
|
|
|
FC_ASSERT( cfg.rotate && cfg.rotation_compression );
|
|
|
|
|
FC_ASSERT( _compression_thread );
|
|
|
|
|
if( !_compression_thread->is_current() )
|
|
|
|
|
{
|
2014-07-27 04:08:35 +00:00
|
|
|
_compression_thread->async( [this, filename]() { compress_file( filename ); }, "compress_file" ).wait();
|
2014-07-03 14:02:40 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
lzma_compress_file( filename, filename.parent_path() / (filename.filename().string() + compression_extension) );
|
2014-07-03 14:02:40 +00:00
|
|
|
remove_all( filename );
|
|
|
|
|
}
|
|
|
|
|
catch( ... )
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
impl( const config& c) : cfg( c )
|
|
|
|
|
{
|
|
|
|
|
if( cfg.rotate )
|
|
|
|
|
{
|
2014-07-05 20:14:25 +00:00
|
|
|
FC_ASSERT( cfg.rotation_interval >= seconds( 1 ) );
|
|
|
|
|
FC_ASSERT( cfg.rotation_limit >= cfg.rotation_interval );
|
|
|
|
|
|
2014-07-03 14:02:40 +00:00
|
|
|
if( cfg.rotation_compression )
|
|
|
|
|
_compression_thread.reset( new thread( "compression") );
|
|
|
|
|
|
2014-07-27 21:37:21 +00:00
|
|
|
_rotation_task = async( [this]() { rotate_files( true ); }, "rotate_files(1)" );
|
2014-07-03 14:02:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~impl()
|
|
|
|
|
{
|
2014-07-26 19:05:26 +00:00
|
|
|
try
|
|
|
|
|
{
|
2014-08-27 18:07:44 +00:00
|
|
|
_rotation_task.cancel_and_wait("file_appender is destructing");
|
2014-07-26 19:05:26 +00:00
|
|
|
}
|
|
|
|
|
catch( ... )
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if( _compression_thread )
|
|
|
|
|
_compression_thread->quit();
|
|
|
|
|
}
|
|
|
|
|
catch( ... )
|
|
|
|
|
{
|
|
|
|
|
}
|
2014-07-03 14:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2014-07-03 05:16:46 +00:00
|
|
|
void rotate_files( bool initializing = false )
|
|
|
|
|
{
|
2014-07-03 14:02:40 +00:00
|
|
|
FC_ASSERT( cfg.rotate );
|
2014-09-25 12:57:31 +00:00
|
|
|
fc::time_point now = time_point::now();
|
|
|
|
|
fc::time_point_sec start_time = get_file_start_time( now, cfg.rotation_interval );
|
|
|
|
|
string timestamp_string = timestamp_to_string( start_time );
|
|
|
|
|
fc::path link_filename = cfg.filename;
|
|
|
|
|
fc::path log_filename = link_filename.parent_path() / (link_filename.filename().string() + "." + timestamp_string);
|
2014-07-03 14:02:40 +00:00
|
|
|
|
2014-07-03 05:16:46 +00:00
|
|
|
{
|
2014-07-16 17:39:14 +00:00
|
|
|
fc::scoped_lock<boost::mutex> lock( slock );
|
2014-07-05 20:14:25 +00:00
|
|
|
|
2014-07-16 17:39:14 +00:00
|
|
|
if( !initializing )
|
|
|
|
|
{
|
|
|
|
|
if( start_time <= _current_file_start_time )
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
_rotation_task = schedule( [this]() { rotate_files(); },
|
|
|
|
|
_current_file_start_time + cfg.rotation_interval.to_seconds(),
|
|
|
|
|
"rotate_files(2)" );
|
2014-07-16 17:39:14 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2014-07-03 05:16:46 +00:00
|
|
|
|
2014-07-16 17:39:14 +00:00
|
|
|
out.flush();
|
|
|
|
|
out.close();
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
out.open( log_filename.to_native_ansi_path() );
|
2014-07-16 17:39:14 +00:00
|
|
|
}
|
2014-07-03 06:35:18 +00:00
|
|
|
remove_all( link_filename );
|
2014-07-03 14:02:40 +00:00
|
|
|
create_hard_link( log_filename, link_filename );
|
2014-07-03 05:16:46 +00:00
|
|
|
|
|
|
|
|
/* Delete old log files */
|
2014-09-25 12:57:31 +00:00
|
|
|
fc::time_point limit_time = now - cfg.rotation_limit;
|
|
|
|
|
string link_filename_string = link_filename.filename().to_native_ansi_path();
|
|
|
|
|
directory_iterator itr(link_filename.parent_path());
|
2014-07-03 05:16:46 +00:00
|
|
|
for( ; itr != directory_iterator(); itr++ )
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
string current_filename = itr->filename().string();
|
|
|
|
|
if (current_filename.compare(0, link_filename_string.size(), link_filename_string) != 0 ||
|
|
|
|
|
current_filename.size() <= link_filename_string.size() + 1)
|
|
|
|
|
continue;
|
|
|
|
|
string current_timestamp_str = current_filename.substr(link_filename_string.size() + 1,
|
|
|
|
|
timestamp_string.size());
|
|
|
|
|
fc::time_point_sec current_timestamp = string_to_timestamp( current_timestamp_str );
|
2014-07-03 14:02:40 +00:00
|
|
|
if( current_timestamp < start_time )
|
2014-07-03 05:27:58 +00:00
|
|
|
{
|
2014-07-03 14:02:40 +00:00
|
|
|
if( current_timestamp < limit_time || file_size( current_filename ) <= 0 )
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
remove_all( *itr );
|
2014-07-03 14:02:40 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
if( !cfg.rotation_compression )
|
|
|
|
|
continue;
|
|
|
|
|
if( current_filename.find( compression_extension ) != string::npos )
|
|
|
|
|
continue;
|
|
|
|
|
compress_file( *itr );
|
2014-07-03 05:27:58 +00:00
|
|
|
}
|
2014-07-03 05:16:46 +00:00
|
|
|
}
|
|
|
|
|
catch( ... )
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-03 14:02:40 +00:00
|
|
|
_current_file_start_time = start_time;
|
2014-09-25 12:57:31 +00:00
|
|
|
_rotation_task = schedule( [this]() { rotate_files(); },
|
|
|
|
|
_current_file_start_time + cfg.rotation_interval.to_seconds(),
|
|
|
|
|
"rotate_files(3)" );
|
2014-07-03 05:16:46 +00:00
|
|
|
}
|
2013-02-05 05:06:16 +00:00
|
|
|
};
|
|
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
file_appender::config::config(const fc::path& p) :
|
|
|
|
|
format( "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}" ),
|
|
|
|
|
filename(p),
|
|
|
|
|
flush(true),
|
|
|
|
|
truncate(true),
|
|
|
|
|
rotate(false),
|
|
|
|
|
rotation_compression(true)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
file_appender::file_appender( const variant& args ) :
|
|
|
|
|
my( new impl( args.as<config>() ) )
|
2013-02-05 05:06:16 +00:00
|
|
|
{
|
2014-07-02 21:36:21 +00:00
|
|
|
try
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
fc::create_directories(my->cfg.filename.parent_path());
|
2014-07-02 21:36:21 +00:00
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
if(!my->cfg.rotate)
|
|
|
|
|
my->out.open(my->cfg.filename.to_native_ansi_path().c_str());
|
2014-07-02 21:36:21 +00:00
|
|
|
}
|
|
|
|
|
catch( ... )
|
|
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
std::cerr << "error opening log file: " << my->cfg.filename.string() << "\n";
|
2013-02-05 05:06:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
2014-09-25 12:57:31 +00:00
|
|
|
|
2013-02-05 05:06:16 +00:00
|
|
|
file_appender::~file_appender(){}
|
|
|
|
|
|
2013-07-03 17:26:23 +00:00
|
|
|
// MS THREAD METHOD MESSAGE \t\t\t File:Line
|
2013-02-05 05:06:16 +00:00
|
|
|
void file_appender::log( const log_message& m )
|
|
|
|
|
{
|
2013-07-03 17:26:23 +00:00
|
|
|
std::stringstream line;
|
2014-06-24 20:29:59 +00:00
|
|
|
//line << (m.get_context().get_timestamp().time_since_epoch().count() % (1000ll*1000ll*60ll*60))/1000 <<"ms ";
|
2014-09-25 12:57:31 +00:00
|
|
|
line << string(m.get_context().get_timestamp()) << " ";
|
|
|
|
|
line << std::setw( 21 ) << (m.get_context().get_thread_name().substr(0,9) + string(":") + m.get_context().get_task_name()).c_str() << " ";
|
2013-07-03 17:26:23 +00:00
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
string method_name = m.get_context().get_method();
|
2013-07-03 17:26:23 +00:00
|
|
|
// strip all leading scopes...
|
2014-09-25 12:57:31 +00:00
|
|
|
if( method_name.size() )
|
2013-07-03 17:26:23 +00:00
|
|
|
{
|
|
|
|
|
uint32_t p = 0;
|
2014-09-25 12:57:31 +00:00
|
|
|
for( uint32_t i = 0;i < method_name.size(); ++i )
|
2013-07-03 17:26:23 +00:00
|
|
|
{
|
2014-09-25 12:57:31 +00:00
|
|
|
if( method_name[i] == ':' ) p = i;
|
2013-07-03 17:26:23 +00:00
|
|
|
}
|
|
|
|
|
|
2014-09-25 12:57:31 +00:00
|
|
|
if( method_name[p] == ':' )
|
|
|
|
|
++p;
|
2013-07-03 17:26:23 +00:00
|
|
|
line << std::setw( 20 ) << m.get_context().get_method().substr(p,20).c_str() <<" ";
|
|
|
|
|
}
|
2014-09-25 12:57:31 +00:00
|
|
|
|
2013-07-03 17:26:23 +00:00
|
|
|
line << "] ";
|
2013-06-05 19:19:00 +00:00
|
|
|
fc::string message = fc::format_string( m.get_format(), m.get_data() );
|
2013-07-03 17:26:23 +00:00
|
|
|
line << message.c_str();
|
|
|
|
|
|
|
|
|
|
//fc::variant lmsg(m);
|
2013-02-05 05:06:16 +00:00
|
|
|
|
2014-07-02 21:36:21 +00:00
|
|
|
// fc::string fmt_str = fc::format_string( my->cfg.format, mutable_variant_object(m.get_context())( "message", message) );
|
|
|
|
|
|
2013-02-05 05:06:16 +00:00
|
|
|
{
|
2014-07-02 21:36:21 +00:00
|
|
|
fc::scoped_lock<boost::mutex> lock( my->slock );
|
2014-09-25 12:57:31 +00:00
|
|
|
my->out << line.str() << "\t\t\t" << m.get_context().get_file() << ":" << m.get_context().get_line_number() << "\n";
|
|
|
|
|
if( my->cfg.flush )
|
|
|
|
|
my->out.flush();
|
2013-02-05 05:06:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
2014-07-03 14:02:40 +00:00
|
|
|
|
|
|
|
|
} // fc
|