Evo C++ Library v0.5.1
logger.h
Go to the documentation of this file.
1 // Evo C++ Library
2 /* Copyright 2019 Justin Crowell
3 Distributed under the BSD 2-Clause License -- see included file LICENSE.txt for details.
4 */
6 
7 #pragma once
8 #ifndef INCL_evo_logger_h
9 #define INCL_evo_logger_h
10 
11 #include "io.h"
12 #include "file.h"
13 #include "substring.h"
14 #include "atomic_buffer_queue.h"
15 #include "thread.h"
16 #include "time.h"
17 #include "enum.h"
18 
20 
35 #define EVO_LOG_ALERT(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_ALERT)) LOGGER.log_direct(LOG_LEVEL_ALERT, MSG); }
36 
51 #define EVO_LOG_ERROR(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_ERROR)) LOGGER.log_direct(LOG_LEVEL_ERROR, MSG); }
52 
67 #define EVO_LOG_WARN(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_WARN)) LOGGER.log_direct(LOG_LEVEL_WARN, MSG); }
68 
83 #define EVO_LOG_INFO(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_INFO)) LOGGER.log_direct(LOG_LEVEL_INFO, MSG); }
84 
99 #define EVO_LOG_DEBUG(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_DEBUG)) LOGGER.log_direct(LOG_LEVEL_DEBUG, MSG); }
100 
115 #define EVO_LOG_DEBUG_LOW(LOGGER, MSG) { if (LOGGER.check(LOG_LEVEL_DEBUG_LOW)) LOGGER.log_direct(LOG_LEVEL_DEBUG_LOW, MSG); }
116 
118 
119 namespace evo {
122 
126 
128 
130 enum LogLevel {
138 };
139 
141 static const LogLevel LOG_LEVEL_REMAP[] = {
148 };
149 
152  "alert",
153  "debug",
154  "debug_low",
155  "error",
156  "info",
157  "warn"
158 );
159 
161 
163 namespace impl {
164  template<uint SZ>
165  struct LoggerMsg {
166  typedef LoggerMsg<SZ> This;
167 
168  static const uint BUF_SIZE = SZ;
169 
170  SysNativeTimeStamp timestamp;
171  LogLevel level;
172  uint32 size;
173  uint32 aux_size;
174  char* aux_buf;
175  char buf[BUF_SIZE];
176 
177  LoggerMsg() : size(0), aux_size(0), aux_buf(NULL) {
178  }
179 
180  ~LoggerMsg() {
181  if (aux_buf != NULL)
182  ::free(aux_buf);
183  }
184 
185  void set(LogLevel msglevel, const SubString& msg) {
186  assert( msg.size() <= UInt32::MAX );
187 
188  timestamp.set_utc();
189  level = msglevel;
190 
191  const char* p = msg.data();
192  uint32 remain_size = msg.size();
193  if (remain_size > BUF_SIZE) {
194  ::memcpy(buf, p, BUF_SIZE);
195  p += BUF_SIZE;
196  remain_size -= BUF_SIZE;
197 
198  if (aux_buf != NULL) {
199  if (remain_size > aux_size)
200  ::free(aux_buf); // old buffer too small
201  else
202  goto copy; // old buffer ok
203  }
204 
205  // New aux_buf
206  aux_size = remain_size;
207  aux_buf = (char*)::malloc(aux_size);
208 
209  copy:
210  // Copy remaining part to aux_buf
211  ::memcpy(aux_buf, p, remain_size);
212  size = msg.size();
213  } else {
214  if (aux_buf != NULL) {
215  ::free(aux_buf);
216  aux_buf = NULL;
217  aux_size = 0;
218  }
219  ::memcpy(buf, p, remain_size);
220  size = remain_size;
221  }
222  }
223 
224  void get_buf_sizes(uint32& buf1_size, uint32& buf2_size) {
225  if (size <= BUF_SIZE) {
226  buf1_size = size;
227  buf2_size = 0;
228  } else {
229  buf1_size = BUF_SIZE;
230  buf2_size = size - BUF_SIZE;
231  }
232  }
233 
234  // aux_buf is swapped, buf is copied from src (not swapped), src.size is set to 0
235  This& operator=(const This& src) {
236  This& src_mutable = const_cast<This&>(src); // assign operator arg must be const, overridding for performance
237 
238  // Copy timestamp and level
239  timestamp = src.timestamp;
240  level = src.level;
241 
242  // Copy main buffer up to size from src
243  size = src.size;
244  if (src.size > 0) {
245  ::memcpy(buf, src.buf, size);
246  src_mutable.size = 0;
247  }
248 
249  // Swap aux buffers
250  swap(aux_buf, src_mutable.aux_buf);
251  swap(aux_size, src_mutable.aux_size);
252  return *this;
253  }
254  };
255 }
258 
273 class LoggerBase {
274 public:
276  virtual ~LoggerBase() {
277  }
278 
285  virtual bool get_error(String& msg) {
286  EVO_PARAM_UNUSED(msg);
287  return false;
288  }
289 
295  virtual void set_level(LogLevel level) {
296  level_.store(level);
297  }
298 
304  virtual void rotate() {
305  }
306 
320  virtual void log_direct(LogLevel level, const SubString& msg) = 0;
321 
335  bool check(LogLevel level) const {
336  return (level <= level_.load());
337  }
338 
354  bool log(LogLevel level, const SubString& msg) {
355  if (level <= level_.load()) {
356  log_direct(level, msg);
357  return true;
358  }
359  return false;
360  }
361 
362 protected:
364 };
365 
367 
376 template<class T=LoggerBase>
377 struct LoggerPtr {
378  typedef T LoggerType;
379 
380  LoggerType* ptr;
381 
383  LoggerPtr() : ptr(NULL) {
384  }
385 
387  LoggerPtr(LoggerType* newptr) : ptr(newptr) {
388  }
389 
393  LoggerPtr(const LoggerPtr& src) : ptr(src.ptr) {
394  }
395 
401  ptr = src.ptr;
402  return *this;
403  }
404 
406  void set() {
407  ptr = NULL;
408  }
409 
413  void set(LoggerType* newptr) {
414  ptr = newptr;
415  }
416 
418  bool check(LogLevel level) const {
419  return (ptr != NULL && ptr->check(level));
420  }
421 
423  void log_direct(LogLevel level, const SubString& msg) {
424  ptr->log_direct(level, msg);
425  }
426 
428  bool log(LogLevel level, const SubString& msg) {
429  if (ptr != NULL && ptr->check(level)) {
430  ptr->log_direct(level, msg);
431  return true;
432  }
433  return false;
434  }
435 };
436 
438 
508 template<uint MSG_BUF_SIZE=512>
509 class Logger : public LoggerBase {
510 public:
512 
513  static const uint MESSAGE_BUFFER_SIZE = MSG_BUF_SIZE;
514  static const SizeT DEFAULT_QUEUE_SIZE = 256;
515 
519  Logger(SizeT queue_size=DEFAULT_QUEUE_SIZE) : queue_(queue_size), outfile_(NL_SYS, false), thread_(consumer, this), local_time_(false) {
520  level_.store(LOG_LEVEL_WARN);
521  }
522 
525  shutdown();
526  }
527 
534  uint get_message_buffer_size() const {
535  return MESSAGE_BUFFER_SIZE;
536  }
537 
542  void set_local_time(bool local_time) {
543  local_time_ = local_time;
544  }
545 
547  Condition::Lock lock(condmutex_);
548  if (errmsg_.null())
549  return false;
550  msg = errmsg_;
551  errmsg_.set();
552  return true;
553  }
554 
555  void rotate() {
556  rotate_.store(1);
557  }
558 
559  void log_direct(LogLevel level, const SubString& msg) {
560  Msg qitem;
561  qitem.set(level, msg);
562  queue_.add(qitem);
563  if (condmutex_.trylock()) { // non-blocking
564  condmutex_.notify();
565  condmutex_.unlock();
566  }
567  }
568 
577  bool open(const SubString& path, bool excep=EVO_EXCEPTIONS) {
578  if (thread_.thread_active()) {
579  const SubString MSG("Logger can't open a file while thread already active");
580  Condition::Lock lock(condmutex_);
581  errmsg_.set().reserve(MSG.size() + filepath_.size()) << MSG << filepath_;
582  return false;
583  } else {
584  // No mutex locks needed while thread is inactive
585  filepath_ = path;
586  if (filepath_.empty()) {
587  errmsg_ = "Logger can't open empty file path";
589  return false;
590  }
591  #if defined(_WIN32)
592  if (filepath_.ends('/') || filepath_.ends('\\') || filepath_.ends(':')) {
593  #else
594  if (filepath_.ends('/')) {
595  #endif
596  const SubString MSG("Logger can't open invalid file path, must be a file not a directory: ");
597  errmsg_.set().reserve(MSG.size() + filepath_.size()) << MSG << filepath_;
599  return false;
600  }
601  if (!outfile_.open(filepath_.cstr(), oAPPEND)) {
602  const Error err = outfile_.error();
603  errmsg_.set() << "Logger can't open: " << filepath_;
604  EVO_THROW_ERR_CHECK(evo::ExceptionLogger, errmsg_, err, excep);
605  errormsg_out(errmsg_ << " -- ", err);
606  return false;
607  }
608  outfile_ << NL;
609  }
610  errmsg_.set();
611  return true;
612  }
613 
623  bool start_thread(bool excep=EVO_EXCEPTIONS) {
624  if (!thread_.thread_active()) {
625  // No mutex locks needed while thread is inactive
626  errmsg_.set();
627  if (!outfile_.isopen()) {
628  const SubString MSG("Logger file not open, must open() first before start_thread()");
629  errmsg_.set().reserve(MSG.size() + filepath_.size()) << MSG << filepath_;
631  return false;
632  } else if (!thread_.thread_start()) {
633  errmsg_ = "Logger thread failed to start";
635  return false;
636  }
637  }
638  return true;
639  }
640 
652  bool start(const SubString& path, bool excep=EVO_EXCEPTIONS) {
653  return (open(path, excep) && start_thread(excep));
654  }
655 
657  void shutdown() {
658  if (thread_.thread_active()) {
659  shutdown_.store(1);
660  condmutex_.lock_notify();
661  thread_.thread_join();
662  }
663  }
664 
665 private:
666  typedef impl::LoggerMsg<MSG_BUF_SIZE> Msg;
667 
668  AtomicBufferQueue<Msg> queue_;
669 
670  String errmsg_;
671  String filepath_;
672  File outfile_;
673  Thread thread_;
674  Condition condmutex_;
675  AtomicInt shutdown_;
676  AtomicInt rotate_;
677  bool local_time_;
678 
679  static void consumer(void* arg) {
680  const char BEGIN_DELIM = '[';
681  const SubString MSG_DELIM("] ", 2);
682  const char* LEVEL_STR[] = { "ALRT", "ERRR", "WARN", "INFO", "dbug", "dbgl" };
683  const StrSizeT LEVEL_LEN = 4;
684 
685  const ulong WAKE_TIMEOUT_MS = 500;
686  Logger& logger = *(Logger*)arg;
687  bool closed = false;
688  ulong drop_count = 0;
689 
690  DateTime dt;
691  Msg msg;
692  uint32 buf1_size, buf2_size;
693  for (;;) {
694  // Consume all messages in queue
695  while (logger.queue_.pop(msg)) {
696  if (closed) {
697  ++drop_count;
698  } else {
699  if (logger.local_time_)
700  msg.timestamp.convert_local_dt_notz(dt);
701  else
702  msg.timestamp.convert_utc_dt(dt);
703  dt.tz.set();
704 
705  logger.outfile_ << BEGIN_DELIM;
706  dt.format(logger.outfile_, ':') << ' ';
707  if (msg.level > LOG_LEVEL_DISABLED && msg.level <= LOG_LEVEL_DEBUG_LOW)
708  logger.outfile_ << SubString(LEVEL_STR[(int)msg.level - 1], LEVEL_LEN);
709  else
710  logger.outfile_ << FmtInt(msg.level).width(LEVEL_LEN, ' ');
711 
712  logger.outfile_ << MSG_DELIM;
713 
714  bool error = false;
715  msg.get_buf_sizes(buf1_size, buf2_size);
716  if (buf1_size > 0 && logger.outfile_.writebin(msg.buf, buf1_size) < buf1_size)
717  error = true;
718  else if (buf2_size > 0 && logger.outfile_.writebin(msg.aux_buf, buf2_size) < buf2_size)
719  error = true;
720  else
721  logger.outfile_ << NL;
722  if (error || !logger.outfile_) {
723  // Unable to write to file
724  ++drop_count;
725  logger.outfile_.close();
726  closed = true;
727 
728  Condition::Lock lock(logger.condmutex_);
729  logger.errmsg_.set() << "Logger file write error: " << logger.filepath_ << " -- ";
730  errormsg_out(logger.errmsg_, logger.outfile_.error());
731  break;
732  }
733  }
734  }
735  if (logger.shutdown_.load())
736  break;
737 
738  if (closed || logger.rotate_.load()) {
739  // Log re-open (rotation or log closed from error)
740  {
741  Condition::Lock lock(logger.condmutex_);
742  logger.outfile_.close();
743  if (logger.outfile_.open(logger.filepath_.cstr(), oAPPEND)) {
744  logger.outfile_ << NL;
745  if (closed)
746  logger.outfile_ << "[] Logger recovered from error (lost: " << drop_count << "): " << logger.errmsg_ << NL;
747  logger.errmsg_.set();
748  closed = false;
749  drop_count = 0;
750  } else if (!closed) {
751  logger.errmsg_.set() << "Logger can't open: " << logger.filepath_ << " -- ";
752  errormsg_out(logger.errmsg_, logger.outfile_.error());
753  closed = true;
754  }
755  }
756  logger.rotate_.store(0);
757  }
758 
759  // Wait for more messages -- logging is lock-free (non-blocking) so this must wake up regularly in case a message signal was missed
760  logger.condmutex_.wait(WAKE_TIMEOUT_MS, false);
761  logger.condmutex_.unlock();
762  }
763  logger.shutdown_.store(0);
764  }
765 };
766 
768 
775 class LoggerInert {
776 public:
777  LoggerInert(SizeT queue_size=0) {
778  EVO_PARAM_UNUSED(queue_size);
779  }
780 
782  { return false; }
783 
785  { return 0; }
786 
787  void set_level(LogLevel) { }
788 
789  void set_local_time(bool) { }
790 
791  void rotate() { }
792 
793  bool check(LogLevel) const
794  { return false; }
795 
796  void log_direct(LogLevel, const SubString&) { }
797 
798  bool log(LogLevel, const SubString&)
799  { return false; }
800 
801  bool start(const SubString&, bool excep=EVO_EXCEPTIONS) {
802  EVO_PARAM_UNUSED(excep);
803  return true;
804  }
805 
806  void shutdown() { }
807 };
808 
810 
811 }
812 #endif
Holds a system timestamp as native (platform specific) fields.
Definition: systime.h:27
Inert logger implementing the same interface as Logger.
Definition: logger.h:775
High-level debug message, used for showing debug info for higher-level behavior (DBUG) ...
Definition: logger.h:136
void log_direct(LogLevel level, const SubString &msg)
Log a message with given log level directly without checking the current log level.
Definition: logger.h:423
void store(T num, MemOrder mem_order=std::memory_order_seq_cst)
Store new value.
bool pop(Item &item)
Pop oldest item from queue.
Definition: atomic_buffer_queue.h:209
LoggerPtr()
Constructor sets as null.
Definition: logger.h:383
void shutdown()
Shutdown logging thread.
Definition: logger.h:657
bool open(const SubString &path, bool excep=1)
Open log file but don&#39;t start logging thread yet.
Definition: logger.h:577
void set()
Set as null (no time zone).
Definition: time.h:1164
bool get_error(String &)
Definition: logger.h:781
Evo SubString container.
T & format(T &out, char dt_delim='T', char d_delim='-', char t_delim=':', char msec_delim=0, char tz_delim=':') const
Format date and time to String or Stream using given delimiters.
Definition: time.h:1724
Invalid argument or data.
Definition: sys.h:1123
TOut & errormsg_out(TOut &out)
Write error message with errno to output stream/string.
Definition: sys.h:1272
virtual void set_level(LogLevel level)
Set current log level.
Definition: logger.h:295
Evo date and time classes.
bool log(LogLevel level, const SubString &msg)
Log a message with given severity level.
Definition: logger.h:354
void set_utc()
Set to current date/time (UTC).
Definition: systime.h:149
Invalid or unsupported operation.
Definition: sys.h:1122
static const LogLevel LOG_LEVEL_REMAP[]
Log level remapping for EVO_ENUM_REMAP().
Definition: logger.h:141
Logger< MSG_BUF_SIZE > This
This Logger type.
Definition: logger.h:511
T LoggerType
Logger type used from template.
Definition: logger.h:378
void set_local_time(bool local_time)
Set wheter to convert log date/time values to local time.
Definition: logger.h:542
void rotate()
Definition: logger.h:791
LoggerPtr(const LoggerPtr &src)
Copy constructor copies logger pointer.
Definition: logger.h:393
bool check(LogLevel level) const
Check whether a message with given level will actually be logged.
Definition: logger.h:335
Manages a single thread of execution.
Definition: thread.h:529
Wraps a logger pointer that can reference a logger to use or be disabled.
Definition: logger.h:377
uint get_message_buffer_size() const
Get message buffer size.
Definition: logger.h:534
const char * cstr(String &buffer) const
Get terminated string pointer, using given string buffer if needed (const).
Definition: string.h:1561
virtual bool get_error(String &msg)
Get last error that occurred.
Definition: logger.h:285
#define EVO_CREATE_EXCEPTION_IMPL(NAME, BASE)
Create an Evo exception implementation.
Definition: sys.h:1365
#define EVO_THROW_ERR_CHECK(TYPE, MSG, ERROR, COND)
Throw an Evo exception with error code if COND is true.
Definition: sys.h:1513
bool wait(ulong timeout_ms=Condition::INF, bool locked=true)
Wait for notification or timeout.
Definition: thread.h:317
Evo File I/O stream class.
static const T MAX
Maximum interger value.
Definition: type.h:996
void swap(T &a, T &b)
Swap contents of given objects.
Definition: sys.h:1602
Evo I/O streams and Console I/O.
Condition object for thread synchronization.
Definition: thread.h:243
Informational message for showing notices and context (INFO)
Definition: logger.h:135
TimeZoneOffset tz
TimeZoneOffset fields.
Definition: time.h:1370
virtual ~LoggerBase()
Destructor.
Definition: logger.h:276
Size size() const
Get size.
bool check(LogLevel) const
Definition: logger.h:793
Full calendar date and time of day with timezone offset.
Definition: time.h:1364
void log_direct(LogLevel level, const SubString &msg)
Log a message with given log level directly without checking the current log level.
Definition: logger.h:559
Evo threads implementation.
#define EVO_EXCEPTIONS
Whether to throw exceptions on error by default.
Definition: evo_config.h:35
Low-level debug message, used for showing debug info for lower-level internal or library details (DBG...
Definition: logger.h:137
High performance message logger.
Definition: logger.h:509
Error
General Evo error code stored in exceptions, or used directly when exceptions are disabled...
Definition: sys.h:1113
bool close()
Close stream.
Definition: iobase.h:888
LoggerPtr(LoggerType *newptr)
Constructor to set pointer.
Definition: logger.h:387
Evo enum helpers.
uint32 StrSizeT
Default Evo string size type.
Definition: sys.h:734
String container.
Definition: string.h:674
FmtIntT< int > FmtInt
Explicitly format an integer.
Definition: str.h:3107
bool log(LogLevel level, const SubString &msg)
Log a message with given severity level.
Definition: logger.h:428
#define EVO_ENUM_REMAP(ENUM, FIRST_VAL, LAST_VAL, UNKNOWN_VAL, REMAP_ARRAY,...)
Helper for creating enum string/value mappers with explicit first/last/unknown values, with unsorted enum remapped to sorted values.
Definition: enum.h:168
void set_level(LogLevel)
Definition: logger.h:787
Smart locking for synchronization.
Definition: lock.h:28
Alert message for critical alert that needs immediate attention, program may be unstable (ALRT) ...
Definition: logger.h:132
bool open(const char *path, Open mode=oREAD, bool flushlines=false)
Open file for read and/or writing.
Definition: file.h:124
void log_direct(LogLevel, const SubString &)
Definition: logger.h:796
File I/O stream.
Definition: file.h:77
Write/append only, created if needed.
Definition: sysio.h:198
LoggerType * ptr
Logger pointer, NULL to disable logging with this
Definition: logger.h:380
ulong writebin(const void *buf, ulong size)
Write binary output to stream.
Definition: iobase.h:946
virtual void rotate()
Set log rotation flag.
Definition: logger.h:304
Evo base exception class.
Definition: sys.h:1214
static const NewlineDefault & NL
Default newline type.
Definition: sys.h:785
Logger exception, see Exception.
Definition: logger.h:124
void unlock()
Unlock associated mutex.
Definition: thread.h:431
Evo C++ Library namespace.
Definition: alg.h:11
Logger(SizeT queue_size=DEFAULT_QUEUE_SIZE)
Constructor.
Definition: logger.h:519
bool get_error(String &msg)
Get last error that occurred.
Definition: logger.h:546
Warning message that can indicate a potential issue, this may lead to an error or alert (WARN) ...
Definition: logger.h:134
bool log(LogLevel, const SubString &)
Definition: logger.h:798
LoggerPtr & operator=(const LoggerPtr &src)
Assignment operator copies logger pointer.
Definition: logger.h:400
LoggerInert(SizeT queue_size=0)
Definition: logger.h:777
const char * msg() const
Get exception message.
Definition: sys.h:1254
Logging disabled.
Definition: logger.h:131
static const Newline NL_SYS
Current system newline type.
Definition: sys.h:763
Operation failed.
Definition: sys.h:1124
Error error() const
Get error code from last operation.
Definition: iobase.h:66
void shutdown()
Definition: logger.h:806
bool start(const SubString &, bool excep=1)
Definition: logger.h:801
Error error() const
Get error code.
Definition: sys.h:1260
bool check(LogLevel level) const
Check whether a message with given level will actually be logged.
Definition: logger.h:418
Error message showing something isn&#39;t working as expected, program may be able to work around it (ERR...
Definition: logger.h:133
String & set()
Set as null and empty.
Definition: string.h:995
uint get_message_buffer_size() const
Definition: logger.h:784
LogLevel
Log severity level used with Logger.
Definition: logger.h:130
uint32 SizeT
Default Evo container size type.
Definition: sys.h:729
Reference and access existing string data.
Definition: substring.h:229
T load(MemOrder mem_order=std::memory_order_seq_cst) const
Load and return current value.
Base class for Logger.
Definition: logger.h:273
~Logger()
Destructor, calls shutdown().
Definition: logger.h:524
AtomicInt level_
Log level, messages less severe than this are ignored (not logged)
Definition: logger.h:363
Evo AtomicBufferQueue.
void rotate()
Set log rotation flag.
Definition: logger.h:555
#define EVO_PARAM_UNUSED(NAME)
Mark function parameter as unused to suppress "unreferenced parameter" compiler warnings on it...
Definition: sys.h:427
bool start(const SubString &path, bool excep=1)
Open log file and start logging thread, which consumes the queue and actually writes to file...
Definition: logger.h:652
bool start_thread(bool excep=1)
Start logging thread for already open file, which consumes the queue and actually writes to file...
Definition: logger.h:623
void set_local_time(bool)
Definition: logger.h:789