Log.cpp
1 /* 2 * Copyright (c) 2013-2022, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <time.h> 10 11 #include "Log.h" 12 #include "util.h" 13 14 //for std::transform 15 #include <algorithm> 16 17 namespace i2p { 18 namespace log { 19 static Log logger; 20 /** 21 * @brief Maps our loglevel to their symbolic name 22 */ 23 static const char *g_LogLevelStr[eNumLogLevels] = 24 { 25 "none", // eLogNone 26 "critical", // eLogCritical 27 "error", // eLogError 28 "warn", // eLogWarning 29 "info", // eLogInfo 30 "debug" // eLogDebug 31 }; 32 33 /** 34 * @brief Colorize log output -- array of terminal control sequences 35 * @note Using ISO 6429 (ANSI) color sequences 36 */ 37 #ifdef _WIN32 38 static const char *LogMsgColors[] = { "", "", "", "", "", "", "" }; 39 #else /* UNIX */ 40 static const char *LogMsgColors[] = { 41 "\033[1;32m", /* none: green */ 42 "\033[1;41m", /* critical: red background */ 43 "\033[1;31m", /* error: red */ 44 "\033[1;33m", /* warning: yellow */ 45 "\033[1;36m", /* info: cyan */ 46 "\033[1;34m", /* debug: blue */ 47 "\033[0m" /* reset */ 48 }; 49 #endif 50 51 #ifndef _WIN32 52 /** 53 * @brief Maps our log levels to syslog one 54 * @return syslog priority LOG_*, as defined in syslog.h 55 */ 56 static inline int GetSyslogPrio (enum LogLevel l) { 57 int priority = LOG_DEBUG; 58 switch (l) { 59 case eLogNone : priority = LOG_CRIT; break; 60 case eLogCritical: priority = LOG_CRIT; break; 61 case eLogError : priority = LOG_ERR; break; 62 case eLogWarning : priority = LOG_WARNING; break; 63 case eLogInfo : priority = LOG_INFO; break; 64 case eLogDebug : priority = LOG_DEBUG; break; 65 default : priority = LOG_DEBUG; break; 66 } 67 return priority; 68 } 69 #endif 70 71 Log::Log(): 72 m_Destination(eLogStdout), m_MinLevel(eLogInfo), 73 m_LogStream (nullptr), m_Logfile(""), m_HasColors(true), m_TimeFormat("%H:%M:%S"), 74 m_IsRunning (false), m_Thread (nullptr) 75 { 76 } 77 78 Log::~Log () 79 { 80 delete m_Thread; 81 } 82 83 void Log::Start () 84 { 85 if (!m_IsRunning) 86 { 87 m_IsRunning = true; 88 m_Thread = new std::thread (std::bind (&Log::Run, this)); 89 } 90 } 91 92 void Log::Stop () 93 { 94 switch (m_Destination) 95 { 96 #ifndef _WIN32 97 case eLogSyslog : 98 closelog(); 99 break; 100 #endif 101 case eLogFile: 102 case eLogStream: 103 if (m_LogStream) m_LogStream->flush(); 104 break; 105 default: 106 /* do nothing */ 107 break; 108 } 109 m_IsRunning = false; 110 m_Queue.WakeUp (); 111 if (m_Thread) 112 { 113 m_Thread->join (); 114 delete m_Thread; 115 m_Thread = nullptr; 116 } 117 } 118 119 std::string str_tolower(std::string s) { 120 std::transform(s.begin(), s.end(), s.begin(), 121 // static_cast<int(*)(int)>(std::tolower) // wrong 122 // [](int c){ return std::tolower(c); } // wrong 123 // [](char c){ return std::tolower(c); } // wrong 124 [](unsigned char c){ return std::tolower(c); } // correct 125 ); 126 return s; 127 } 128 129 void Log::SetLogLevel (const std::string& level_) { 130 std::string level=str_tolower(level_); 131 if (level == "none") { m_MinLevel = eLogNone; } 132 else if (level == "critical") { m_MinLevel = eLogCritical; } 133 else if (level == "error") { m_MinLevel = eLogError; } 134 else if (level == "warn") { m_MinLevel = eLogWarning; } 135 else if (level == "info") { m_MinLevel = eLogInfo; } 136 else if (level == "debug") { m_MinLevel = eLogDebug; } 137 else { 138 LogPrint(eLogCritical, "Log: Unknown loglevel: ", level); 139 return; 140 } 141 LogPrint(eLogInfo, "Log: Logging level set to ", level); 142 } 143 144 const char * Log::TimeAsString(std::time_t t) { 145 struct tm caltime; 146 if (t != m_LastTimestamp) { 147 #ifdef _WIN32 148 localtime_s(&caltime, &t); 149 #else 150 localtime_r(&t, &caltime); 151 #endif 152 strftime(m_LastDateTime, sizeof(m_LastDateTime), m_TimeFormat.c_str(), &caltime); 153 m_LastTimestamp = t; 154 } 155 return m_LastDateTime; 156 } 157 158 /** 159 * @note This function better to be run in separate thread due to disk i/o. 160 * Unfortunately, with current startup process with late fork() this 161 * will give us nothing but pain. Maybe later. See in NetDb as example. 162 */ 163 void Log::Process(std::shared_ptr<LogMsg> msg) 164 { 165 if (!msg) return; 166 std::hash<std::thread::id> hasher; 167 unsigned short short_tid; 168 short_tid = (short) (hasher(msg->tid) % 1000); 169 switch (m_Destination) { 170 #ifndef _WIN32 171 case eLogSyslog: 172 syslog(GetSyslogPrio(msg->level), "[%03u] %s", short_tid, msg->text.c_str()); 173 break; 174 #endif 175 case eLogFile: 176 case eLogStream: 177 if (m_LogStream) 178 *m_LogStream << TimeAsString(msg->timestamp) 179 << "@" << short_tid 180 << "/" << g_LogLevelStr[msg->level] 181 << " - " << msg->text << std::endl; 182 break; 183 case eLogStdout: 184 default: 185 std::cout << TimeAsString(msg->timestamp) 186 << "@" << short_tid 187 << "/" << LogMsgColors[msg->level] << g_LogLevelStr[msg->level] << LogMsgColors[eNumLogLevels] 188 << " - " << msg->text << std::endl; 189 break; 190 } // switch 191 } 192 193 void Log::Run () 194 { 195 i2p::util::SetThreadName("Logging"); 196 197 Reopen (); 198 while (m_IsRunning) 199 { 200 std::shared_ptr<LogMsg> msg; 201 while ((msg = m_Queue.Get ())) 202 Process (msg); 203 if (m_LogStream) m_LogStream->flush(); 204 if (m_IsRunning) 205 m_Queue.Wait (); 206 } 207 } 208 209 void Log::Append(std::shared_ptr<i2p::log::LogMsg> & msg) 210 { 211 m_Queue.Put(msg); 212 } 213 214 void Log::SendTo (const std::string& path) 215 { 216 if (m_LogStream) m_LogStream = nullptr; // close previous 217 auto flags = std::ofstream::out | std::ofstream::app; 218 auto os = std::make_shared<std::ofstream> (path, flags); 219 if (os->is_open ()) 220 { 221 m_HasColors = false; 222 m_Logfile = path; 223 m_Destination = eLogFile; 224 m_LogStream = os; 225 return; 226 } 227 LogPrint(eLogCritical, "Log: Can't open file ", path); 228 } 229 230 void Log::SendTo (std::shared_ptr<std::ostream> os) { 231 m_HasColors = false; 232 m_Destination = eLogStream; 233 m_LogStream = os; 234 } 235 236 #ifndef _WIN32 237 void Log::SendTo(const char *name, int facility) { 238 if (m_MinLevel == eLogNone) return; 239 m_HasColors = false; 240 m_Destination = eLogSyslog; 241 m_LogStream = nullptr; 242 openlog(name, LOG_CONS | LOG_PID, facility); 243 } 244 #endif 245 246 void Log::Reopen() { 247 if (m_Destination == eLogFile) 248 SendTo(m_Logfile); 249 } 250 251 Log & Logger() { 252 return logger; 253 } 254 255 static ThrowFunction g_ThrowFunction; 256 ThrowFunction GetThrowFunction () { return g_ThrowFunction; } 257 void SetThrowFunction (ThrowFunction f) { g_ThrowFunction = f; } 258 259 } // log 260 } // i2p 261