diff --git a/Log.cpp b/Log.cpp index bb4f8231..27637c77 100644 --- a/Log.cpp +++ b/Log.cpp @@ -1,133 +1,155 @@ -#include -#include "Log.h" +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ -Log * g_Log = nullptr; +#include "Log.h" -static const char * g_LogLevelStr[eNumLogLevels] = -{ - "error", // eLogError - "warn", // eLogWarning - "info", // eLogInfo - "debug" // eLogDebug -}; -#ifndef _WIN32 -/** convert LogLevel enum to syslog priority level */ -static int ToSyslogLevel(LogLevel lvl) -{ - switch (lvl) { - case eLogError: - return LOG_ERR; - case eLogWarning: - return LOG_WARNING; - case eLogInfo: - return LOG_INFO; - case eLogDebug: - return LOG_DEBUG; - default: - // WTF? invalid log level? - return LOG_CRIT; - } -} -#endif +namespace i2p { +namespace log { + Log logger; + /** + * @enum Maps our loglevel to their symbolic name + */ + static const char * g_LogLevelStr[eNumLogLevels] = + { + "error", // eLogError + "warn", // eLogWarn + "info", // eLogInfo + "debug" // eLogDebug + }; -void LogMsg::Process() -{ #ifndef _WIN32 - if (log && log->SyslogEnabled()) { - // only log to syslog - syslog(ToSyslogLevel(level), "%s", s.str().c_str()); - return; - } + /** + * @brief Maps our log levels to syslog one + * @return syslog priority LOG_*, as defined in syslog.h + */ + static inline int GetSyslogPrio (enum LogLevel l) { + int priority = LOG_DEBUG; + switch (l) { + case eLogError : priority = LOG_ERR; break; + case eLogWarning : priority = LOG_WARNING; break; + case eLogInfo : priority = LOG_INFO; break; + case eLogDebug : priority = LOG_DEBUG; break; + default : priority = LOG_DEBUG; break; + } + return priority; + } #endif - auto stream = log ? log->GetLogStream () : nullptr; - auto& output = stream ? *stream : std::cout; - if (log) - output << log->GetTimestamp (); - else - output << boost::posix_time::second_clock::local_time().time_of_day (); - output << "/" << g_LogLevelStr[level] << " - "; - output << s.str(); -} -const std::string& Log::GetTimestamp () -{ -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) - auto ts = std::chrono::monotonic_clock::now (); -#else - auto ts = std::chrono::steady_clock::now (); -#endif - if (ts > m_LastTimestampUpdate + std::chrono::milliseconds (500)) // 0.5 second + Log::Log(): + m_Destination(eLogStdout), m_MinLevel(eLogInfo), + m_LogStream (nullptr), m_Logfile(""), m_IsReady(false) { - m_LastTimestampUpdate = ts; - m_Timestamp = boost::posix_time::to_simple_string (boost::posix_time::second_clock::local_time().time_of_day ()); - } - return m_Timestamp; -} - -void Log::Flush () -{ - if (m_LogStream) - m_LogStream->flush(); -} + } -void Log::SetLogFile (const std::string& fullFilePath, bool truncate) -{ - m_FullFilePath = fullFilePath; - auto mode = std::ofstream::out | std::ofstream::binary; - mode |= truncate ? std::ofstream::trunc : std::ofstream::app; - auto logFile = std::make_shared (fullFilePath, mode); - if (logFile->is_open ()) + Log::~Log () { - SetLogStream (logFile); - LogPrint(eLogInfo, "Log: will send messages to ", fullFilePath); - } -} + switch (m_Destination) { +#ifndef _WIN32 + case eLogSyslog : + closelog(); + break; +#endif + case eLogFile: + case eLogStream: + m_LogStream->flush(); + break; + default: + /* do nothing */ + break; + } + Process(); + } -void Log::ReopenLogFile () -{ - if (m_FullFilePath.length () > 0) - { - SetLogFile (m_FullFilePath, false); // don't truncate - LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen"); + void Log::SetLogLevel (const std::string& level) { + if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } + else { + LogPrint(eLogError, "Log: unknown loglevel: ", level); + return; + } + LogPrint(eLogInfo, "Log: min messages level set to ", level); + } + + const char * Log::TimeAsString(std::time_t t) { + if (t != m_LastTimestamp) { + strftime(m_LastDateTime, sizeof(m_LastDateTime), "%H:%M:%S", localtime(&t)); + m_LastTimestamp = t; + } + return m_LastDateTime; } -} + void Log::Process() { + std::unique_lock l(m_OutputLock); + while (1) { + auto msg = m_Queue.GetNextWithTimeout (1); + if (!msg) + break; + switch (m_Destination) { +#ifndef _WIN32 + case eLogSyslog: + syslog(GetSyslogPrio(msg->level), "%s", msg->text.c_str()); + break; +#endif + case eLogFile: + case eLogStream: + *m_LogStream << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + break; + default: + std::cout << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + break; + } // switch + } // while + } -void Log::SetLogLevel (const std::string& level) -{ - if (level == "error") { m_MinLevel = eLogError; } - else if (level == "warn") { m_MinLevel = eLogWarning; } - else if (level == "info") { m_MinLevel = eLogInfo; } - else if (level == "debug") { m_MinLevel = eLogDebug; } - else { - LogPrint(eLogError, "Log: Unknown loglevel: ", level); - return; - } - LogPrint(eLogInfo, "Log: min msg level set to ", level); -} + void Log::Append(std::shared_ptr & msg) { + m_Queue.Put(msg); + if (!m_IsReady) + return; + Process(); + } -void Log::SetLogStream (std::shared_ptr logStream) -{ - m_LogStream = logStream; -} + void Log::SendTo (const std::string& path) { + auto flags = std::ofstream::out | std::ofstream::app; + auto os = std::make_shared (path, flags); + if (os->is_open ()) { + m_Logfile = path; + m_Destination = eLogFile; + m_LogStream = os; + m_IsReady = true; + return; + } + LogPrint(eLogError, "Log: can't open file ", path); + } -void Log::StartSyslog(const std::string & ident, const int facility) -{ -#ifndef _WIN32 - m_Ident = ident; - openlog(m_Ident.c_str(), LOG_PID, facility); -#endif -} + void Log::SendTo (std::shared_ptr os) { + m_Destination = eLogStream; + m_IsReady = true; + m_LogStream = os; + } -void Log::StopSyslog() -{ #ifndef _WIN32 - closelog(); - m_Ident.clear(); + void Log::SendTo(const char *name, int facility) { + m_Destination = eLogSyslog; + m_LogStream = nullptr; + m_IsReady = true; + openlog(name, LOG_CONS | LOG_PID, facility); + } #endif -} -bool Log::SyslogEnabled() -{ - return m_Ident.size() > 0; -} + void Log::Reopen() { + if (m_Destination == eLogFile) + SendTo(m_Logfile); + } + + Log & Logger() { + return logger; + } +} // log +} // i2p diff --git a/Log.h b/Log.h index 76866590..ba755ae4 100644 --- a/Log.h +++ b/Log.h @@ -1,11 +1,19 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef LOG_H__ #define LOG_H__ +#include #include #include -#include #include -#include +#include #include #include #include "Queue.h" @@ -23,150 +31,151 @@ enum LogLevel eNumLogLevels }; -class Log; -struct LogMsg -{ - std::stringstream s; - Log * log; - LogLevel level; - - LogMsg (Log * l = nullptr, LogLevel lv = eLogInfo): log (l), level (lv) {}; - - void Process(); -}; - -class Log: public i2p::util::MsgQueue -{ - public: - - Log () { SetOnEmpty (std::bind (&Log::Flush, this)); }; - ~Log () {}; - - void SetLogFile (const std::string& fullFilePath, bool truncate = true); - void ReopenLogFile (); - void SetLogLevel (const std::string& level); - void SetLogStream (std::shared_ptr logStream); - std::shared_ptr GetLogStream () const { return m_LogStream; }; - const std::string& GetTimestamp (); - LogLevel GetLogLevel () { return m_MinLevel; }; - const std::string& GetFullFilePath () const { return m_FullFilePath; }; - /** start logging to syslog */ - void StartSyslog(const std::string & ident, const int facility); - /** stop logging to syslog */ - void StopSyslog(); - /** are we logging to syslog right now? */ - bool SyslogEnabled(); - private: - - void Flush (); - - private: - - std::string m_FullFilePath; // empty if stream - std::shared_ptr m_LogStream; - enum LogLevel m_MinLevel; - std::string m_Timestamp; -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) // gcc 4.6 - std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; -#else - std::chrono::steady_clock::time_point m_LastTimestampUpdate; +enum LogType { + eLogStdout = 0, + eLogStream, + eLogFile, +#ifndef _WIN32 + eLogSyslog, #endif - std::string m_Ident; }; -extern Log * g_Log; +namespace i2p { +namespace log { + struct LogMsg; /* forward declaration */ -inline void StartLog (const std::string& fullFilePath) -{ - if (!g_Log) - { - auto log = new Log (); - if (fullFilePath.length () > 0) - log->SetLogFile (fullFilePath); - g_Log = log; - } -} - -inline void StartLog (std::shared_ptr s) -{ - if (!g_Log) - { - auto log = new Log (); - if (s) - log->SetLogStream (s); - g_Log = log; - } -} - -inline void StopLog () -{ - if (g_Log) + class Log { - auto log = g_Log; - g_Log = nullptr; - log->Stop (); - delete log; - } -} - -inline void SetLogLevel (const std::string& level) -{ - if (g_Log) - g_Log->SetLogLevel(level); -} - -inline void ReopenLogFile () -{ - if (g_Log) - g_Log->ReopenLogFile (); -} - -inline bool IsLogToFile () -{ - return g_Log ? !g_Log->GetFullFilePath ().empty () : false; -} - -inline void StartSyslog() -{ - StartLog(""); -#ifndef _WIN32 - g_Log->StartSyslog("i2pd", LOG_USER); -#endif -} - -inline void StopSyslog() -{ - if(g_Log) - g_Log->StopSyslog(); -} - + private: + + enum LogType m_Destination; + enum LogLevel m_MinLevel; + std::shared_ptr m_LogStream; + std::string m_Logfile; + std::time_t m_LastTimestamp; + char m_LastDateTime[64]; + i2p::util::Queue > m_Queue; + volatile bool m_IsReady; + mutable std::mutex m_OutputLock; + + private: + + /** prevent making copies */ + Log (const Log &); + const Log& operator=(const Log&); + + /** + * @brief process stored messages in queue + */ + void Process (); + + /** + * @brief Makes formatted string from unix timestamp + * @param ts Second since epoch + * + * This function internally caches the result for last provided value + */ + const char * TimeAsString(std::time_t ts); + + public: + + Log (); + ~Log (); + + LogType GetLogType () { return m_Destination; }; + LogLevel GetLogLevel () { return m_MinLevel; }; + + /** + * @brief Sets minimal alloed level for log messages + * @param level String with wanted minimal msg level + */ + void SetLogLevel (const std::string& level); + + /** + * @brief Sets log destination to logfile + * @param path Path to logfile + */ + void SendTo (const std::string &path); + + /** + * @brief Sets log destination to given output stream + * @param os Output stream + */ + void SendTo (std::shared_ptr s); + + #ifndef _WIN32 + /** + * @brief Sets log destination to syslog + * @param name Wanted program name + * @param facility Wanted log category + */ + void SendTo (const char *name, int facility); + #endif + + /** + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message + */ + void Append(std::shared_ptr &); + + /** @brief Flushes the output log stream */ + void Flush(); + + /** @brief Reopen log file */ + void Reopen(); + }; + + /** + * @struct Log message container + * + * We creating it somewhere with LogPrint(), + * then put in MsgQueue for later processing. + */ + struct LogMsg { + std::time_t timestamp; + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ + + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + }; + + Log & Logger(); +} // log +} // i2p + +/** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg) +void LogPrint (std::stringstream& s, TValue arg) { s << arg; } - + +/** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg, TArgs... args) +void LogPrint (std::stringstream& s, TValue arg, TArgs... args) { LogPrint (s, arg); LogPrint (s, args...); } +/** + * @brief Create log message and send it to queue + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ template -void LogPrint (LogLevel level, TArgs... args) +void LogPrint (LogLevel level, TArgs... args) { - if (g_Log && level > g_Log->GetLogLevel ()) + i2p::log::Log &log = i2p::log::Logger(); + if (level > log.GetLogLevel ()) return; - LogMsg * msg = new LogMsg (g_Log, level); - LogPrint (msg->s, args...); - msg->s << std::endl; - if (g_Log) { - g_Log->Put (msg); - } else { - msg->Process (); - delete msg; - } + + // fold message to single string + std::stringstream ss(""); + LogPrint (ss, args ...); + + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + log.Append(msg); } -#endif +#endif // LOG_H__