BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
file_appender.cpp
Go to the documentation of this file.
2 #include <fc/io/fstream.hpp>
4 #include <fc/reflect/variant.hpp>
6 #include <fc/thread/thread.hpp>
7 #include <fc/variant.hpp>
8 #include <boost/thread/mutex.hpp>
9 #include <iomanip>
10 #include <queue>
11 #include <sstream>
12 #include <iostream>
13 
14 namespace fc {
15 
17  {
18  public:
21  boost::mutex slock;
22 
23  private:
24  future<void> _deletion_task;
25  boost::atomic<int64_t> _current_file_number;
26  const int64_t _interval_seconds;
27  time_point _next_file_time;
28 
29  public:
30  impl( const config& c) : cfg( c ), _interval_seconds( cfg.rotation_interval.to_seconds() )
31  {
32  try
33  {
35 
36  if( cfg.rotate )
37  {
38  FC_ASSERT( cfg.rotation_interval >= seconds( 1 ) );
40 
41  rotate_files( true );
42  delete_files();
43  } else {
44  out.open( cfg.filename, std::ios_base::out | std::ios_base::app);
45  }
46  }
47  catch( ... )
48  {
49  std::cerr << "error opening log file: " << cfg.filename.preferred_string() << "\n";
50  }
51  }
52 
54  {
55  try
56  {
57  _deletion_task.cancel_and_wait("file_appender is destructing");
58  }
59  catch( ... )
60  {
61  }
62  }
63 
64  void rotate_files( bool initializing = false )
65  {
66  if( !cfg.rotate ) return;
67 
69  if( now < _next_file_time ) return;
70 
71  int64_t new_file_number = now.sec_since_epoch() / _interval_seconds;
72  if( initializing )
73  _current_file_number.store( new_file_number );
74  else
75  {
76  int64_t prev_file_number = _current_file_number.load();
77  if( prev_file_number >= new_file_number ) return;
78  if( !_current_file_number.compare_exchange_weak( prev_file_number, new_file_number ) ) return;
79  }
80  fc::time_point_sec start_time = time_point_sec( (uint32_t)(new_file_number * _interval_seconds) );
81  _next_file_time = start_time + _interval_seconds;
82  string timestamp_string = start_time.to_non_delimited_iso_string();
83  fc::path link_filename = cfg.filename;
84  fc::path log_filename = link_filename.parent_path() / (link_filename.filename().string() + "." + timestamp_string);
85 
86  {
87  fc::scoped_lock<boost::mutex> lock( slock );
88 
89  if( !initializing )
90  {
91  out.flush();
92  out.close();
93  }
94  remove_all(link_filename); // on windows, you can't delete the link while the underlying file is opened for writing
95  out.open( log_filename, std::ios_base::out | std::ios_base::app );
96  create_hard_link(log_filename, link_filename);
97  }
98  }
99 
101  {
102  /* Delete old log files */
103  auto current_file = _current_file_number.load();
104  fc::time_point_sec start_time = time_point_sec( (uint32_t)(current_file * _interval_seconds) );
105  fc::time_point limit_time = time_point::now() - cfg.rotation_limit;
106  fc::path link_filename = cfg.filename;
107  string link_filename_string = link_filename.filename().string();
108  directory_iterator itr(link_filename.parent_path());
109  string timestamp_string = start_time.to_non_delimited_iso_string();
110  for( ; itr != directory_iterator(); itr++ )
111  {
112  try
113  {
114  string current_filename = itr->filename().string();
115  if( current_filename.compare(0, link_filename_string.size(), link_filename_string) != 0
116  || current_filename.size() <= link_filename_string.size() + 1 )
117  continue;
118  string current_timestamp_str = current_filename.substr(link_filename_string.size() + 1,
119  timestamp_string.size());
120  fc::time_point_sec current_timestamp = fc::time_point_sec::from_iso_string( current_timestamp_str );
121  if( current_timestamp < start_time
122  && ( current_timestamp < limit_time || file_size( current_filename ) <= 0 ) )
123  {
124  remove_all( *itr );
125  continue;
126  }
127  }
128  catch (const fc::canceled_exception&)
129  {
130  throw;
131  }
132  catch( ... )
133  {
134  }
135  }
136  uint64_t next_file = time_point::now().sec_since_epoch() / _interval_seconds + 1;
137  _deletion_task = schedule( [this]() { delete_files(); },
138  fc::time_point_sec( next_file * _interval_seconds),
139  "delete_files(3)" );
140  }
141  };
142 
144  format( "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}" ),
145  filename(p),
146  flush(true),
147  rotate(false)
148  {}
149 
151  my( new impl( args.as<config>( FC_MAX_LOG_OBJECT_DEPTH ) ) )
152  {}
153 
155 
156  // MS THREAD METHOD MESSAGE \t\t\t File:Line
158  {
159  my->rotate_files();
160 
161  std::stringstream line;
162  line << string(m.get_context().get_timestamp()) << " ";
163  line << std::setw( 21 ) << (m.get_context().get_thread_name().substr(0,9) + string(":") + m.get_context().get_task_name()).c_str() << " ";
164 
165  string method_name = m.get_context().get_method();
166  // strip all leading scopes...
167  if( method_name.size() )
168  {
169  uint32_t p = 0;
170  for( uint32_t i = 0;i < method_name.size(); ++i )
171  {
172  if( method_name[i] == ':' ) p = i;
173  }
174 
175  if( method_name[p] == ':' )
176  ++p;
177  line << std::setw( 20 ) << m.get_context().get_method().substr(p,20).c_str() <<" ";
178  }
179 
180  line << "] ";
181  std::string message = fc::format_string( m.get_format(), m.get_data(), my->cfg.max_object_depth );
182  line << message.c_str();
183 
184  {
185  fc::scoped_lock<boost::mutex> lock( my->slock );
186  my->out << line.str() << "\t\t\t" << m.get_context().get_file() << ":" << m.get_context().get_line_number() << "\n";
187  if( my->cfg.flush )
188  my->out.flush();
189  }
190  }
191 
192 } // fc
std::string get_file() const
std::string format_string(const std::string &, const variant_object &, uint32_t max_object_depth=200)
uint32_t sec_since_epoch() const
Definition: time.hpp:55
std::string get_task_name() const
std::string get_thread_name() const
uint64_t get_line_number() const
static time_point_sec from_iso_string(const std::string &s)
Definition: time.cpp:35
void flush()
Definition: fstream.cpp:51
void create_directories(const path &p)
Definition: filesystem.cpp:210
void remove_all(const path &p)
Definition: filesystem.cpp:240
fc::path filename() const
Definition: filesystem.cpp:151
void create_hard_link(const path &from, const path &to)
Definition: filesystem.cpp:319
#define FC_MAX_LOG_OBJECT_DEPTH
Definition: config.hpp:8
std::string string() const
Definition: filesystem.cpp:148
impl(const config &c)
std::string get_format() const
microseconds seconds(int64_t s)
Definition: time.hpp:34
void cancel_and_wait(const char *reason FC_CANCELATION_REASON_DEFAULT_ARG)
Definition: future.hpp:314
virtual void log(const log_message &m) override
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
uint64_t file_size(const path &p)
Definition: filesystem.cpp:219
log_context get_context() const
Defines exception&#39;s used by fc.
time_point get_timestamp() const
std::string preferred_string() const
Definition: filesystem.cpp:99
void open(const fc::path &file, std::ios_base::openmode m=std::ios_base::out|std::ios_base::binary)
Definition: fstream.cpp:32
aggregates a message along with the context and associated meta-information.
Definition: api.hpp:15
fc::path parent_path() const
Definition: filesystem.cpp:163
variant_object get_data() const
static time_point now()
Definition: time.cpp:13
config(const fc::path &p="log.txt")
wraps boost::filesystem::path to provide platform independent path manipulation.
Definition: filesystem.hpp:28
auto schedule(Functor &&f, const fc::time_point &t, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:231
std::string get_method() const
std::string to_non_delimited_iso_string() const
Definition: time.cpp:18
void close()
Definition: fstream.cpp:48
void rotate_files(bool initializing=false)
file_appender(const variant &args)
cerr_t & cerr
Definition: iostream.cpp:176