Core Library  1.7.0.0
Library containing core utilities and tools for threading, networking, logging, INI and CSV file management etc.
DebugLog.h
Go to the documentation of this file.
1 // This file is part of CoreLibrary containing useful reusable utility
2 // classes.
3 //
4 // Copyright (C) 2014 to present, Duncan Crutchley
5 // Contact <dac1976github@outlook.com>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Lesser General Public License as published
9 // by the Free Software Foundation, either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License and GNU Lesser General Public License
16 // for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // and GNU Lesser General Public License along with this program. If
20 // not, see <http://www.gnu.org/licenses/>.
21 
27 #ifndef DEBUGLOG
28 #define DEBUGLOG
29 
30 #include "CoreLibraryDllGlobal.h"
32 
33 #include <ctime>
34 #include <chrono>
35 #include <string>
36 #include <fstream>
37 #include <sstream>
38 #include <set>
39 #include <unordered_map>
40 #include <algorithm>
41 #ifdef USE_EXPLICIT_MOVE_
42 #include <utility>
43 #endif
44 #include <memory>
45 #include <functional>
46 #include <type_traits>
47 #include <stdexcept>
48 #define BOOST_NO_CXX11_SCOPED_ENUMS
49 #include <boost/filesystem.hpp>
50 #include <boost/throw_exception.hpp>
52 
54 namespace core_lib
55 {
57 namespace log
58 {
59 
61 enum class eLogMessageLevel
62 {
64  not_defined = 0,
66  debug,
68  info,
70  warning,
72  error,
74  fatal
75 };
76 
77 } // namespace log
78 } // namespace core_lib
79 
81 namespace std
82 {
83 
85 template <> struct hash<core_lib::log::eLogMessageLevel>
86 {
90  using result_t = std::size_t;
92  using enumType_t = TYPENAME_DECL_ std::underlying_type<argument_t>::type;
93 
99  result_t operator()(const argument_t& a) const
100  {
101  auto a2 = static_cast<enumType_t>(a);
102  std::hash<enumType_t> h;
103  return h(a2);
104  }
105 };
106 
107 } // namespace std
108 
110 namespace core_lib
111 {
113 namespace log
114 {
115 
128 struct CORE_LIBRARY_DLL_SHARED_API DefaultLogFormat
129 {
141  void operator()(std::ostream& os, std::time_t timeStamp, const std::string& message,
142  const std::string& logMsgLevel, const std::string& file,
143  const std::string& function, int lineNo, const std::thread::id& threadID) const;
144 };
145 
147 enum eDefLogSize : size_t
148 {
149  BYTES_IN_MEBIBYTE = 1024 * 1024
150 };
151 
152 namespace dl_private
153 {
154 
156 enum eMsgId : int
157 {
158  MESSAGE_ID = 1
159 };
160 
168 class CORE_LIBRARY_DLL_SHARED_API LogQueueMessage
169 {
170 public:
172  LogQueueMessage() = default;
186  LogQueueMessage(const std::string& message, time_t timeStamp, const std::string& file,
187  const std::string& function, int lineNo, const std::thread::id& threadID,
188  eLogMessageLevel errorLevel);
190  LogQueueMessage(const LogQueueMessage&) = default;
192  ~LogQueueMessage() = default;
194  LogQueueMessage& operator=(const LogQueueMessage&) = default;
195 #ifdef USE_EXPLICIT_MOVE_
196 
199  LogQueueMessage& operator=(LogQueueMessage&& msg);
200 #else
201 
202  LogQueueMessage(LogQueueMessage&&) = default;
204  LogQueueMessage& operator=(LogQueueMessage&&) = default;
205 #endif
206 
210  const std::string& Message() const;
215  time_t TimeStamp() const;
220  const std::string& File() const;
225  const std::string& Function() const;
230  int LineNo() const;
235  const std::thread::id& ThreadID() const;
240  eLogMessageLevel ErrorLevel() const;
241 
242 private:
244  std::string m_message{};
246  time_t m_timeStamp{0};
248  std::string m_file{};
250  std::string m_function{};
252  int m_lineNo{0};
254  std::thread::id m_threadID{};
257 };
258 
259 } // namespace dl_private
260 
276 template <class Formatter> class DebugLog final
277 {
278 public:
289 #ifdef USE_DEFAULT_CONSTRUCTOR_
290  DebugLog()
291  : m_unknownLogMsgLevel("?")
292  {
293  InitialiseLogMessageLevelLookupMap();
294  }
295 #else
296  DebugLog() = default;
297 #endif
298 
308  DebugLog(const std::string& softwareVersion, const std::string& logFolderPath,
309  const std::string& logName, long maxLogSize = 5 * BYTES_IN_MEBIBYTE)
310  :
311 #ifdef USE_DEFAULT_CONSTRUCTOR_
312  m_unknownLogMsgLevel("?")
313  ,
314 #endif
315  m_maxLogSize(maxLogSize)
316  , m_softwareVersion(softwareVersion)
317  , m_logFilePath(logFolderPath)
318  , m_oldLogFilePath(logFolderPath)
319  {
320  m_logFilePath.append(logName);
321  m_logFilePath.append(".txt");
322 
323  m_oldLogFilePath.append(logName);
324  m_oldLogFilePath.append("_old.txt");
325 
326 #ifdef USE_DEFAULT_CONSTRUCTOR_
327  InitialiseLogMessageLevelLookupMap();
328 #endif
329  RegisterLogQueueMessageId();
330  OpenOfStream(m_logFilePath, eFileOpenOptions::append_file);
331  }
333  DebugLog(const DebugLog&) = delete;
335  DebugLog& operator=(const DebugLog&) = delete;
337  DebugLog(DebugLog&&) = delete;
339  DebugLog& operator=(DebugLog&&) = delete;
342  {
343  // Manually reset so we process all remaining
344  // messages before closing file.
345  m_logMsgQueueThread.reset();
346  CloseOfStream();
347  }
363  void Instantiate(const std::string& softwareVersion, const std::string& logFolderPath,
364  const std::string& logName, long maxLogSize = 5 * BYTES_IN_MEBIBYTE)
365  {
366  m_maxLogSize = maxLogSize;
367 
368  if ((m_softwareVersion != "") || (m_logFilePath != "") || (m_oldLogFilePath != ""))
369  {
370  BOOST_THROW_EXCEPTION(std::runtime_error("DebugLog already instantiated"));
371  }
372 
373  m_softwareVersion = softwareVersion;
374  m_logFilePath = logFolderPath;
375  m_oldLogFilePath = logFolderPath;
376 
377  m_logFilePath.append(logName);
378  m_logFilePath.append(".txt");
379 
380  m_oldLogFilePath.append(logName);
381  m_oldLogFilePath.append("_old.txt");
382 
383  RegisterLogQueueMessageId();
384  OpenOfStream(m_logFilePath, eFileOpenOptions::append_file);
385  }
399  {
400  std::lock_guard<std::mutex> lock{m_mutex};
401 
402  if (!IsLogMsgLevelFilterSetNoMutex(logMessageLevel))
403  {
404  m_logMsgFilterSet.insert(logMessageLevel);
405  }
406  }
419  {
420  std::lock_guard<std::mutex> lock{m_mutex};
421 
422  if (IsLogMsgLevelFilterSetNoMutex(logMessageLevel))
423  {
424  m_logMsgFilterSet.insert(logMessageLevel);
425  }
426  }
434  {
435  std::lock_guard<std::mutex> lock{m_mutex};
436  m_logMsgFilterSet.clear();
437  }
445  void AddLogMessage(const std::string& message)
446  {
447  using std::chrono::system_clock;
448  time_t messageTime = system_clock::to_time_t(system_clock::now());
449  std::thread::id noThread;
450  m_logMsgQueueThread->Push(dl_private::LogQueueMessage(
451  message, messageTime, "", "", -1, noThread, eLogMessageLevel::not_defined));
452  }
453 
468  void AddLogMessage(const std::string& message, const std::string& file,
469  const std::string& function, int lineNo, eLogMessageLevel logMsgLevel)
470  {
471  if (!IsLogMsgLevelFilterSet(logMsgLevel))
472  {
473  using std::chrono::system_clock;
474  time_t messageTime = system_clock::to_time_t(system_clock::now());
475  m_logMsgQueueThread->Push(dl_private::LogQueueMessage(message,
476  messageTime,
477  file,
478  function,
479  lineNo,
480  std::this_thread::get_id(),
481  logMsgLevel));
482  }
483  }
484 
485 private:
486 #ifdef USE_DEFAULT_CONSTRUCTOR_
487 
488  void InitialiseLogMessageLevelLookupMap()
489  {
490  m_logMsgLevelLookup.emplace(eLogMessageLevel::not_defined, "");
491  m_logMsgLevelLookup.emplace(eLogMessageLevel::debug, "Debug");
492  m_logMsgLevelLookup.emplace(eLogMessageLevel::info, "Info");
493  m_logMsgLevelLookup.emplace(eLogMessageLevel::warning, "Warning");
494  m_logMsgLevelLookup.emplace(eLogMessageLevel::error, "Error");
495  m_logMsgLevelLookup.emplace(eLogMessageLevel::fatal, "Fatal");
496  }
497 #endif
498 
499  long MaxLogSize() const
500  {
501  return m_maxLogSize;
502  }
505  {
506  m_logMsgQueueThread->RegisterMessageHandler(
507  dl_private::MESSAGE_ID,
508  std::bind(&DebugLog<Formatter>::MessageHandler, this, std::placeholders::_1));
509  }
516  {
517  return dl_private::MESSAGE_ID;
518  }
525  {
526  CheckLogFileSize(static_cast<long>(message.Message().size()));
527  WriteMessageToLog(std::forward<dl_private::LogQueueMessage>(message));
528  return true;
529  }
535  bool IsLogMsgLevelInLookup(eLogMessageLevel logMessageLevel) const
536  {
537  return m_logMsgLevelLookup.count(logMessageLevel) > 0;
538  }
544  const std::string& GetLogMsgLevelAsString(eLogMessageLevel logMessageLevel) const
545  {
546  return IsLogMsgLevelInLookup(logMessageLevel)
547  ? m_logMsgLevelLookup.find(logMessageLevel)->second
548  : m_unknownLogMsgLevel;
549  }
556  {
557  return (m_logMsgFilterSet.find(logMessageLevel) != m_logMsgFilterSet.end());
558  }
564  bool IsLogMsgLevelFilterSet(eLogMessageLevel logMessageLevel) const
565  {
566  std::lock_guard<std::mutex> lock{m_mutex};
567  return IsLogMsgLevelFilterSetNoMutex(logMessageLevel);
568  }
571  {
575  append_file
576  };
582  void OpenOfStream(const std::string& filePath, eFileOpenOptions fileOptions)
583  {
584  if (m_ofStream.is_open())
585  {
586  return;
587  }
588 
589  m_ofStream.open(filePath,
590  fileOptions == eFileOpenOptions::truncate_file ? std::ofstream::trunc
591  : std::ofstream::app);
592 
593  using std::chrono::system_clock;
594  time_t messageTime = system_clock::to_time_t(system_clock::now());
595  std::thread::id noThread;
596  WriteMessageToLog(dl_private::LogQueueMessage(
597  "DEBUG LOG STARTED", messageTime, "", "", -1, noThread, eLogMessageLevel::not_defined));
598 
599  if (m_softwareVersion != "")
600  {
601  std::string message("Software Version ");
602  message += m_softwareVersion;
603  WriteMessageToLog(dl_private::LogQueueMessage(
604  message, messageTime, "", "", -1, noThread, eLogMessageLevel::not_defined));
605  }
606  }
609  {
610  if (!m_ofStream.is_open())
611  {
612  return;
613  }
614 
615  using std::chrono::system_clock;
616  time_t messageTime = system_clock::to_time_t(system_clock::now());
617  std::thread::id noThread;
618  WriteMessageToLog(dl_private::LogQueueMessage(
619  "DEBUG LOG STOPPED", messageTime, "", "", -1, noThread, eLogMessageLevel::not_defined));
620  m_ofStream.close();
621  }
626  void CheckLogFileSize(long requiredSpace)
627  {
628  if (!m_ofStream.is_open())
629  {
630  return;
631  }
632 
633  auto pos = static_cast<long>(m_ofStream.tellp());
634 
635  if ((MaxLogSize() - pos) < requiredSpace)
636  {
637  CloseOfStream();
638  boost::filesystem::copy_file(m_logFilePath,
639  m_oldLogFilePath,
640  boost::filesystem::copy_option::overwrite_if_exists);
641  OpenOfStream(m_logFilePath, eFileOpenOptions::truncate_file);
642  }
643  }
649  {
650  m_logFormatter(m_ofStream,
651  logMessage.TimeStamp(),
652  logMessage.Message(),
653  GetLogMsgLevelAsString(logMessage.ErrorLevel()),
654  logMessage.File(),
655  logMessage.Function(),
656  logMessage.LineNo(),
657  logMessage.ThreadID());
658  m_ofStream.flush();
659  }
660 
661 private:
663  mutable std::mutex m_mutex;
664 #ifdef USE_DEFAULT_CONSTRUCTOR_
665 
666  std::string m_unknownLogMsgLevel;
668  std::unordered_map<eLogMessageLevel, std::string> m_logMsgLevelLookup;
669 #else
670 
671  std::string m_unknownLogMsgLevel{"?"};
673  std::unordered_map<eLogMessageLevel, std::string> m_logMsgLevelLookup{
675  {eLogMessageLevel::debug, "Debug"},
676  {eLogMessageLevel::info, "Info"},
677  {eLogMessageLevel::warning, "Warning"},
678  {eLogMessageLevel::error, "Error"},
679  {eLogMessageLevel::fatal, "Fatal"}};
680 #endif
681 
682  std::set<eLogMessageLevel> m_logMsgFilterSet{};
684  Formatter m_logFormatter{};
686  long m_maxLogSize{5 * BYTES_IN_MEBIBYTE};
688  std::ofstream m_ofStream{};
690  std::string m_softwareVersion{};
692  std::string m_logFilePath{};
694  std::string m_oldLogFilePath{};
698  std::unique_ptr<log_msg_queue> m_logMsgQueueThread{
699  new log_msg_queue(std::bind(&DebugLog<Formatter>::MessageDecoder, std::placeholders::_1),
701 };
702 
703 } // namespace log
704 } // namespace core_lib
705 
706 #endif // DEBUGLOG
Warning level defined for message.
~DebugLog()
Destructor.
Definition: DebugLog.h:341
const std::string & GetLogMsgLevelAsString(eLogMessageLevel logMessageLevel) const
Get message level as a string.
Definition: DebugLog.h:544
Error level defined for message.
bool MessageHandler(dl_private::LogQueueMessage &message)
Method to process message.
Definition: DebugLog.h:524
bool IsLogMsgLevelFilterSetNoMutex(eLogMessageLevel logMessageLevel) const
Is message level in filter set (no mutex).
Definition: DebugLog.h:555
void ClearLogMsgLevelFilters()
Clear all message levels from filter.
Definition: DebugLog.h:433
static int MessageDecoder(const dl_private::LogQueueMessage &)
Method to decode message ID.
Definition: DebugLog.h:515
The std namespace.
Definition: DebugLog.h:81
void AddLogMessage(const std::string &message)
Add message to the log file.
Definition: DebugLog.h:445
eMsgId
Static constant defining message ID for log messages.
Definition: DebugLog.h:156
std::size_t result_t
Typedef for result type.
Definition: DebugLog.h:90
void CheckLogFileSize(long requiredSpace)
Check size of current log file.
Definition: DebugLog.h:626
File containing declaration of MessageQueueThread class.
const std::string & Message() const
Get message string.
Definition: DebugLog.cpp:130
std::mutex m_mutex
Mutex to lock access.
Definition: DebugLog.h:663
void RemoveLogMsgLevelFilter(eLogMessageLevel logMessageLevel)
Remove level from filter.
Definition: DebugLog.h:418
eDefLogSize
Static constant defining number of bytes in a mebibyte.
Definition: DebugLog.h:147
The core_lib namespace.
Definition: AsioDefines.h:59
TYPENAME_DECL_ std::underlying_type< argument_t >::type enumType_t
Typedef for underlying type.
Definition: DebugLog.h:92
No level defined for message.
void AddLogMsgLevelFilter(eLogMessageLevel logMessageLevel)
Add level to filter.
Definition: DebugLog.h:398
DebugLog class.
Definition: DebugLog.h:276
bool IsLogMsgLevelInLookup(eLogMessageLevel logMessageLevel) const
Is message level in map.
Definition: DebugLog.h:535
void AddLogMessage(const std::string &message, const std::string &file, const std::string &function, int lineNo, eLogMessageLevel logMsgLevel)
Add message to the log file.
Definition: DebugLog.h:468
void OpenOfStream(const std::string &filePath, eFileOpenOptions fileOptions)
Open file stream.
Definition: DebugLog.h:582
eLogMessageLevel
Enumeration containing log message level options.
Definition: DebugLog.h:61
File containing declaration of DLL import/export control defines.
long MaxLogSize() const
Get the max log size in bytes.
Definition: DebugLog.h:499
Debug level defined for message.
void WriteMessageToLog(dl_private::LogQueueMessage &&logMessage)
Write log message to file stream.
Definition: DebugLog.h:648
bool IsLogMsgLevelFilterSet(eLogMessageLevel logMessageLevel) const
Is message level in filter set (with mutex).
Definition: DebugLog.h:564
DebugLog(const std::string &softwareVersion, const std::string &logFolderPath, const std::string &logName, long maxLogSize=5 *BYTES_IN_MEBIBYTE)
Initialisation constructor.
Definition: DebugLog.h:308
File containing platform specific definitions.
Fatal level defined for message.
void Instantiate(const std::string &softwareVersion, const std::string &logFolderPath, const std::string &logName, long maxLogSize=5 *BYTES_IN_MEBIBYTE)
Instantiate a previously default constructed DebugLog object.
Definition: DebugLog.h:363
result_t operator()(const argument_t &a) const
Function operator to perform the hash.
Definition: DebugLog.h:99
#define TYPENAME_DECL_
TYPENAME_DECL_ definition mapping to typename.
Definition: PlatformDefines.h:63
Default log line formater.
Definition: DebugLog.h:128
Option to truncate file when opened.
Definition: DebugLog.h:573
eFileOpenOptions
Enumeration containing file opening options.
Definition: DebugLog.h:570
Log Queue Message class.
Definition: DebugLog.h:168
void RegisterLogQueueMessageId()
Register the log queue message ID.
Definition: DebugLog.h:504
Message Queue Thread.
Definition: MessageQueueThread.h:68
Info level defined for message.
void CloseOfStream()
Close file stream.
Definition: DebugLog.h:608