/ libi2pd / Log.cpp
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