dodo  0.0.1
A C++ library to create containerized Linux services
logger.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the dodo library (https://github.com/jmspit/dodo).
3  * Copyright (c) 2019 Jan-Marten Spit.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * @file logger.cpp
20  * Implements the dodo::common::Logger class.
21  */
22 
23 #include <fstream>
24 #include <iostream>
25 
26 #include <common/config.hpp>
27 #include "common/logger.hpp"
28 #include <common/exception.hpp>
29 #include <common/util.hpp>
30 
31 #include <yaml-cpp/yaml.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <filesystem>
36 #include <regex>
37 #include <syslog.h>
38 #include <sys/syscall.h>
39 
40 namespace dodo::common {
41 
42  threads::Mutex Logger::mutex_;
43 
44  Logger* Logger::logger_ = nullptr;
45 
46  Logger::Logger( const Config& config ) {
48  destinations_ = Console;
49  char hostname[256];
50  gethostname( hostname, 256 );
51  hostname_ = hostname;
52  threads::Mutexer lock( mutex_ );
54 
57  getFileParams(config);
58  }
59 
61  destinations_ = destinations_ | Syslog;
64  } else syslog_params_.facility = 1;
66  if ( levels_[Syslog] > LogLevel::Info ) throw_Exception( "syslog LogLevel cannot be higher than info" );
67  }
68 
69  }
70 
72  // wait until other threads have released. they may be writing.
73  threads::Mutexer lock( mutex_ );
74  file_params_.file.close();
75  }
76 
77  Logger* Logger::initialize( const Config& config ) {
78  if ( logger_ ) throw_Exception( "calling initialize on an existing Logger singleton" );
79  logger_ = new Logger( config );
80  return logger_;
81  }
82 
84  if ( !logger_ ) throw_Exception( "null singleton - call initialize before getLogger" );
85  return logger_;
86  }
87 
88  void Logger::log( LogLevel level, const std::string &message ) {
89  threads::Mutexer lock( mutex_ );
90  std::string entry = "";
91 
92  if ( level <= levels_[Console] ) {
93  entry = formatMessage( level, message );
94  std::cout << entry << std::endl;
95  }
96 
97  if ( destinations_ | File && level <= levels_[File] ) {
98  checkRotate();
99  if ( entry.length() == 0 ) entry = formatMessage( level, message );
100  file_params_.file << entry << std::endl;
101  file_params_.filesize += entry.length() + 1;
102  }
103 
104  if ( destinations_ | Syslog && level <= levels_[Syslog] && level <= LogLevel::Info ) {
105  std::stringstream ss;
106  ss << Config::getConfig()->getAppName() << "[" << syscall(SYS_gettid) << "]: ";
107  ss << LogLevelAsString(level, true) << ": " << message;
108  syslog( syslog_params_.facility | mapLeveltoSyslog(level), "%s", ss.str().c_str() );
109  }
110  }
111 
112  void Logger::fatal( const std::string &message ) {
113  log( LogLevel::Fatal, message );
114  }
115 
116  void Logger::error( const std::string &message ) {
117  log( LogLevel::Error, message );
118  }
119 
120  void Logger::warning( const std::string &message ) {
121  log( LogLevel::Warning, message );
122  }
123 
124  void Logger::info( const std::string &message ) {
125  log( LogLevel::Info, message );
126  }
127 
128  void Logger::statistics( const std::string &message ) {
129  log( LogLevel::Statistics, message );
130  }
131 
132  void Logger::debug( const std::string &message ) {
133  log( LogLevel::Debug, message );
134  }
135 
136  void Logger::trace( const std::string &message ) {
137  log( LogLevel::Trace, message );
138  }
139 
141  switch ( level ) {
142  case LogLevel::Fatal : return 2;
143  case LogLevel::Error : return 3;
144  case LogLevel::Warning : return 4;
145  case LogLevel::Info :
146  case LogLevel::Statistics :
147  case LogLevel::Debug :
148  case LogLevel::Trace : return 6;
149  }
150  return 6;
151  }
152 
154  if ( file_params_.filesize > file_params_.max_size_mib * 1024 * 1024 ) {
155  file_params_.file.close();
156  std::stringstream ss;
157  struct timeval tv;
158  gettimeofday( &tv, nullptr );
159  ss << file_params_.active_log << "." << formatDateTimeUTC( tv );
160  rename( file_params_.active_log.c_str(), ss.str().c_str() );
161  file_params_.file.open( file_params_.active_log, std::ofstream::out | std::ofstream::app );
163 
164  std::set<std::string> trail;
165  std::regex log_regex( "^" + file_params_.active_log + "\\..*" );
166  for ( const auto & entry : std::filesystem::directory_iterator( file_params_.directory ) ) {
167  if ( std::regex_match( entry.path().string(), log_regex ) ) {
168  trail.insert( entry.path().string() );
169  }
170  }
171 
172  while ( trail.size() > file_params_.max_file_trail ) {
173  std::string path = *trail.begin();
174  unlink( path.c_str() );
175  trail.erase( trail.begin() );
176  }
177  }
178  }
179 
180  void Logger::getFileParams( const Config& config ) {
183 
186  else
188 
191  else
193 
195  throw_Exception( "directory '" << file_params_.directory << "' does not exist" );
197  throw_Exception( "directory '" << file_params_.directory << "' not writable" );
198  file_params_.active_log = file_params_.directory + "/" + config.getAppName() + ".log";
199  file_params_.file.open( file_params_.active_log, std::ofstream::out | std::ofstream::app );
201  if ( !file_params_.file.good() )
202  throw_Exception( "failed to open '" << file_params_.active_log << "' for writing" );
203  }
204 
205  std::string Logger::formatMessage( LogLevel level, const std::string &message ) {
206  std::stringstream ss;
207  struct timeval tv;
208  gettimeofday( &tv, nullptr );
209  ss << formatDateTimeUTC( tv ) << " ";
210  ss << hostname_ << " ";
211  ss << Config::getConfig()->getAppName() << "[" << syscall(SYS_gettid) << "] ";
212  ss << LogLevelAsString( level, true ) << " ";
213  ss << message;
214  return ss.str();
215  }
216 
217  Logger::LogLevel Logger::StringAsLogLevel( const std::string slevel ) {
218  if ( slevel == "fatal" ) return LogLevel::Fatal;
219  else if ( slevel == "error" ) return LogLevel::Error;
220  else if ( slevel == "warning" ) return LogLevel::Warning;
221  else if ( slevel == "info" ) return LogLevel::Info;
222  else if ( slevel == "statistics" ) return LogLevel::Statistics;
223  else if ( slevel == "debug" ) return LogLevel::Debug;
224  else if ( slevel == "trace" ) return LogLevel::Trace;
225  else return LogLevel::Info;
226  }
227 
228  std::string Logger::LogLevelAsString( Logger::LogLevel level, bool acronym ) {
229  if ( acronym ) {
230  switch ( level ) {
231  case LogLevel::Fatal : return "FAT";
232  case LogLevel::Error : return "ERR";
233  case LogLevel::Warning : return "WRN";
234  case LogLevel::Info : return "INF";
235  case LogLevel::Statistics : return "STA";
236  case LogLevel::Debug : return "DBG";
237  case LogLevel::Trace : return "TRC";
238  }
239  } else {
240  switch ( level ) {
241  case LogLevel::Fatal : return "fatal";
242  case LogLevel::Error : return "error";
243  case LogLevel::Warning : return "warning";
244  case LogLevel::Info : return "info";
245  case LogLevel::Statistics : return "statistics";
246  case LogLevel::Debug : return "debug";
247  case LogLevel::Trace : return "trace";
248  }
249  }
250  return ""; // never reaches but g++ complains.
251  }
252 
253 }
dodo::common::Logger::FileParams::max_file_trail
size_t max_file_trail
The maximum number of files to keep besides the active log file.
Definition: logger.hpp:74
dodo::common::Logger::LogLevelAsString
static std::string LogLevelAsString(LogLevel level, bool acronym)
Return the LogLevel as a string (as used in console and file).
Definition: logger.cpp:228
dodo::common::Logger::LogLevel::Error
@ Error
The program signaled an error.
dodo::common::Config::exists
bool exists(const KeyPath &keypath) const
Return true if the KeyPath exists.
Definition: config.hpp:201
dodo::common::Config::getConfig
static Config * getConfig()
return the singleton.
Definition: config.cpp:68
dodo::threads::Mutexer
Waits for and locks the Mutex on construction, unlocks the Mutex when this Mutexer is destructed.
Definition: mutex.hpp:100
dodo::common::Logger::file_params_
FileParams file_params_
Parameters for the file Destination.
Definition: logger.hpp:248
dodo::common::Logger::error
void error(const std::string &message)
Log a Error log entry.
Definition: logger.cpp:116
dodo::common::Logger
A Logger interface.
Definition: logger.hpp:40
dodo::common::Config::getAppName
std::string getAppName() const
Return the application name.
Definition: config.hpp:174
config.hpp
dodo::common::Logger::mutex_
static threads::Mutex mutex_
threads::Mutex to serialize log writing.
Definition: logger.hpp:223
dodo::common::Logger::SyslogParams::facility
int facility
The syslog facility to use.
Definition: logger.hpp:88
dodo::common::Logger::log
void log(LogLevel level, const std::string &message)
Log a log entry.
Definition: logger.cpp:88
dodo::common::Logger::FileParams::active_log
std::string active_log
the filename of the active log
Definition: logger.hpp:76
dodo::common::directoryExists
bool directoryExists(const std::string &path)
Return true when the directory exists.
Definition: util.cpp:230
dodo::common::Logger::getFileParams
void getFileParams(const Config &config)
Get FileParams from the Config into file_params_.
Definition: logger.cpp:180
dodo::common::Logger::Logger
Logger(const Config &config)
Construct against a config.
Definition: logger.cpp:46
dodo::common::Logger::statistics
void statistics(const std::string &message)
Log a Statistics log entry.
Definition: logger.cpp:128
dodo::common::Config::config_dodo_common_logger_syslog_level
static const Config::KeyPath config_dodo_common_logger_syslog_level
The dodo.common.logger.syslog.level node.
Definition: config.hpp:119
dodo::common::Logger::LogLevel::Statistics
@ Statistics
The program signaled runtime statistics.
dodo::common::Logger::FileParams::filesize
size_t filesize
current file size
Definition: logger.hpp:80
dodo::common::Logger::~Logger
virtual ~Logger()
Destructor.
Definition: logger.cpp:71
dodo::common::Logger::logger_
static Logger * logger_
The singleton.
Definition: logger.hpp:228
dodo::common::Logger::LogLevel::Fatal
@ Fatal
The program could not continue.
dodo::common::Logger::warning
void warning(const std::string &message)
Log a Warning log entry.
Definition: logger.cpp:120
dodo::common::Logger::debug
void debug(const std::string &message)
Log a Debug log entry.
Definition: logger.cpp:132
dodo::common::Config::config_dodo_common_logger_file_directory
static const Config::KeyPath config_dodo_common_logger_file_directory
The dodo.common.logger.file.directory node.
Definition: config.hpp:107
dodo::common::Logger::FileParams::file
std::ofstream file
the ofstream of the active log.
Definition: logger.hpp:78
dodo::common::Logger::LogLevel::Warning
@ Warning
The program signaled a warning.
dodo::common::Logger::levels_
std::map< uint8_t, LogLevel > levels_
Map Destination to LogLevel.
Definition: logger.hpp:238
dodo::common::Logger::hostname_
std::string hostname_
The hostname cached in the constructor.
Definition: logger.hpp:243
dodo::common::Config::config_dodo_common_logger_syslog
static const Config::KeyPath config_dodo_common_logger_syslog
The dodo.common.logger.syslog node.
Definition: config.hpp:116
dodo::common::Logger::formatMessage
std::string formatMessage(LogLevel level, const std::string &message)
Format a LogLine.
Definition: logger.cpp:205
dodo::common::Config::config_dodo_common_logger_console_level
static const Config::KeyPath config_dodo_common_logger_console_level
The dodo.common.logger.console.level node.
Definition: config.hpp:98
dodo::common::Logger::LogLevel::Trace
@ Trace
The program produced trace info.
dodo::common::Config
Singleton interface to a (read-only) deployment configuration, combining data from the deployment con...
Definition: config.hpp:52
dodo::common::Config::config_dodo_common_logger_file_max_file_trail
static const Config::KeyPath config_dodo_common_logger_file_max_file_trail
The dodo.common.logger.file.max-file-trail node.
Definition: config.hpp:113
throw_Exception
#define throw_Exception(what)
Throws an Exception, passes FILE and LINE to constructor.
Definition: exception.hpp:174
dodo::common::Logger::trace
void trace(const std::string &message)
Log a Trace log entry.
Definition: logger.cpp:136
dodo::common::Logger::LogLevel::Info
@ Info
The program signaled an informational message.
dodo::common::Logger::FileParams::max_size_mib
size_t max_size_mib
The maximum size of a logfile.
Definition: logger.hpp:72
dodo::common::Logger::syslog_params_
SyslogParams syslog_params_
Parameters for the syslog Destination.
Definition: logger.hpp:253
dodo::common::getFileSize
size_t getFileSize(const std::string &path)
Return the size of the file.
Definition: util.cpp:256
dodo::common::Logger::getLogger
static Logger * getLogger()
Get the Logger singleton - initialize() must have been called first.
Definition: logger.cpp:83
dodo::common
Common and utility interfaces.
Definition: application.hpp:29
dodo::common::Config::config_dodo_common_logger_file_level
static const Config::KeyPath config_dodo_common_logger_file_level
The dodo.common.logger.file.level node.
Definition: config.hpp:104
dodo::common::Config::config_dodo_common_logger_syslog_facility
static const Config::KeyPath config_dodo_common_logger_syslog_facility
The dodo.common.logger.syslog.facility node.
Definition: config.hpp:122
dodo::common::Logger::initialize
static Logger * initialize(const Config &config)
Initialize the Logger singleton.
Definition: logger.cpp:77
dodo::common::Logger::LogLevel
LogLevel
The level of a log entry.
Definition: logger.hpp:46
dodo::common::Config::getValue
T getValue(const KeyPath &keypath) const
Get the value at keypath.
Definition: config.hpp:187
dodo::common::Logger::destinations_
uint8_t destinations_
Destinations as bit flags.
Definition: logger.hpp:233
exception.hpp
dodo::common::Logger::mapLeveltoSyslog
int mapLeveltoSyslog(LogLevel level)
Map a LogLevel to a syslog level.
Definition: logger.cpp:140
dodo::common::Logger::fatal
void fatal(const std::string &message)
Log a Fatal log entry.
Definition: logger.cpp:112
dodo::common::Logger::info
void info(const std::string &message)
Log a Info log entry.
Definition: logger.cpp:124
dodo::common::Logger::rotate_throttle_counter_
size_t rotate_throttle_counter_
Track when rotation was last checked.
Definition: logger.hpp:258
logger.hpp
dodo::common::Logger::StringAsLogLevel
static LogLevel StringAsLogLevel(const std::string slevel)
Return the string as a LogLevel.
Definition: logger.cpp:217
dodo::common::Logger::checkRotate
void checkRotate()
Check and rotate the log file if it exceeds the size limit, delete older logs if needed.
Definition: logger.cpp:153
dodo::common::formatDateTimeUTC
std::string formatDateTimeUTC(const struct timeval &tv)
Return a datetime string in UTC (2020-07-01T20:14:36.442929Z)
Definition: util.cpp:262
dodo::common::Config::config_dodo_common_logger_file
static const Config::KeyPath config_dodo_common_logger_file
The dodo.common.logger.file node.
Definition: config.hpp:101
dodo::common::Logger::LogLevel::Debug
@ Debug
The program produced debug info.
dodo::common::directoryWritable
bool directoryWritable(const std::string &path)
Return true when the directory exists and is writable to the caller.
Definition: util.cpp:239
util.hpp
dodo::common::Config::config_dodo_common_logger_file_max_size_mib
static const Config::KeyPath config_dodo_common_logger_file_max_size_mib
The dodo.common.logger.file.max-size-mib node.
Definition: config.hpp:110
dodo::common::Logger::FileParams::directory
std::string directory
The directory to write the log files in.
Definition: logger.hpp:70