diff --git a/src/jami/jami.h b/src/jami/jami.h index 7e773d6c3847a645212723a3e8cdbbc3e1ba132d..78c5be0ceb30c7a6c4470d107ae72095e770ee4f 100644 --- a/src/jami/jami.h +++ b/src/jami/jami.h @@ -68,6 +68,13 @@ DRING_PUBLIC bool start(const std::string& config_file = {}) noexcept; */ DRING_PUBLIC void fini() noexcept; +/** + * Control log handlers. + * + * @param whom Log handler to control + */ +DRING_PUBLIC void logging(const char* whom, const char* action) noexcept; + /* External Callback Dynamic Utilities * * The library provides to users a way to be acknowledged diff --git a/src/logger.cpp b/src/logger.cpp index 0981e54c3e83b82c28b81fc1f20c0371e401ce64..e4e6d3dfc983f2d4e00e0718854748ff3c4210b9 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -34,6 +34,9 @@ #endif #include <atomic> +#include <condition_variable> +#include <functional> +#include <fstream> #include <string> #include <sstream> #include <iomanip> @@ -42,11 +45,12 @@ #include <thread> #include <array> +#include "fileutils.h" #include "logger.h" #ifdef __linux__ -#include <syslog.h> #include <unistd.h> +#include <syslog.h> #include <sys/syscall.h> #endif // __linux__ @@ -85,26 +89,15 @@ #define LOGFILE "jami" -#ifdef RING_UWP -static constexpr auto ENDL = ""; -#else -static constexpr auto ENDL = "\n"; -#endif - -static int consoleLog; -static std::atomic_bool monitorLog; -static int debugMode; -static std::mutex logMutex; +static constexpr auto ENDL = '\n'; // extract the last component of a pathname (extract a filename from its dirname) static const char* stripDirName(const char* path) { -#ifdef _MSC_VER - return strrchr(path, '\\') ? strrchr(path, '\\') + 1 : path; -#else - return strrchr(path, '/') ? strrchr(path, '/') + 1 : path; -#endif + const char* occur = strrchr(path, DIR_SEPARATOR_CH); + + return occur ? occur + 1 : path; } static std::string @@ -148,46 +141,6 @@ contextHeader(const char* const file, int line) return out.str(); } -void -setConsoleLog(int c) -{ - if (c) - ::closelog(); - else { -#ifdef _WIN32 - ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL); -#else - ::openlog(LOGFILE, LOG_NDELAY, LOG_USER); -#endif /* _WIN32 */ - } - - consoleLog = c; -} - -void -setDebugMode(int d) -{ - debugMode = d; -} - -void -setMonitorLog(bool m) -{ - monitorLog.store(m); -} - -int -getDebugMode(void) -{ - return debugMode; -} - -bool -getMonitorLog(void) -{ - return monitorLog.load(); -} - static const char* check_error(int result, char* buffer) { @@ -222,55 +175,94 @@ strErr(void) namespace jami { -void -Logger::log(int level, const char* file, int line, bool linefeed, const char* const format, ...) +struct BufDeleter { -#if defined(TARGET_OS_IOS) && TARGET_OS_IOS - if (!debugMode && !monitorLog.load()) - return; -#endif - if (!debugMode && !monitorLog.load() && level == LOG_DEBUG) - return; + void operator()(char* ptr) + { + if (ptr) { + free(ptr); + } + } +}; - va_list ap; - va_start(ap, format); - va_list cp; +struct Logger::Msg +{ + Msg() = delete; - bool withMonitor = monitorLog.load(); + Msg(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap) + : header_(contextHeader(file, line)) + , level_(level) + , linefeed_(linefeed) + { + /* A good guess of what we might encounter. */ + static constexpr size_t default_buf_size = 80; - if (withMonitor) + char* buf = (char*) malloc(default_buf_size); + int buf_size = default_buf_size; + va_list cp; + + /* Necessary if we don't have enough space in buf. */ va_copy(cp, ap); -#ifdef __ANDROID__ - __android_log_vprint(level, APP_NAME, format, ap); -#else - Logger::vlog(level, file, line, linefeed, format, ap); -#endif - if (withMonitor) { - std::array<char, 4096> tmp; - vsnprintf(tmp.data(), tmp.size(), format, cp); - jami::emitSignal<DRing::ConfigurationSignal::MessageSend>(contextHeader(file, line) - + tmp.data()); + int size = vsnprintf(buf, buf_size, fmt, ap); + + /* Not enough space? Well try again. */ + if (size >= buf_size) { + buf_size = size + 1; + buf = (char*) realloc(buf, buf_size); + vsnprintf(buf, buf_size, fmt, cp); + } + + payload_.reset(buf); + + va_end(cp); } - va_end(ap); -} -void -Logger::vlog( - const int level, const char* file, int line, bool linefeed, const char* format, va_list ap) + Msg(Msg&& other) + { + payload_ = std::move(other.payload_); + header_ = std::move(other.header_); + level_ = other.level_; + linefeed_ = other.linefeed_; + } + + std::unique_ptr<char, BufDeleter> payload_; + std::string header_; + int level_; + bool linefeed_; +}; + +class Logger::Handler { -#if defined(TARGET_OS_IOS) && TARGET_OS_IOS - if (!debugMode) - return; -#endif - if (!debugMode && level == LOG_DEBUG) - return; +public: + virtual ~Handler() = default; + + virtual void consume(Msg& msg) = 0; + + void enable(bool en) { enabled_.store(en); } + bool isEnable() { return enabled_.load(); } - // syslog is supposed to thread-safe, but not all implementations (Android?) - // follow strictly POSIX rules... so we lock our mutex in any cases. - std::lock_guard<std::mutex> lk {logMutex}; +private: + std::atomic<bool> enabled_; +}; + +class ConsoleLog : public jami::Logger::Handler +{ +public: + static ConsoleLog& instance() + { + // This is an intentional memory leak!!! + // + // Some thread can still be logging after DRing::fini and even + // during the static destructors called by libstdc++. Thus, we + // allocate the logger on the heap and never free it. + static ConsoleLog* self = new ConsoleLog(); + + return *self; + } - if (consoleLog or monitorLog.load()) { + virtual void consume(jami::Logger::Msg& msg) override + { #ifndef _WIN32 const char* color_header = CYAN; const char* color_prefix = ""; @@ -284,7 +276,7 @@ Logger::vlog( WORD saved_attributes; #endif - switch (level) { + switch (msg.level_) { case LOG_ERR: color_prefix = RED; break; @@ -301,7 +293,7 @@ Logger::vlog( saved_attributes = consoleInfo.wAttributes; SetConsoleTextAttribute(hConsole, color_header); #endif - fputs(contextHeader(file, line).c_str(), stderr); + fputs(msg.header_.c_str(), stderr); #ifndef _WIN32 fputs(END_COLOR, stderr); fputs(color_prefix, stderr); @@ -309,19 +301,269 @@ Logger::vlog( SetConsoleTextAttribute(hConsole, saved_attributes); SetConsoleTextAttribute(hConsole, color_prefix); #endif - vfprintf(stderr, format, ap); - if (linefeed) - fputs(ENDL, stderr); + fputs(msg.payload_.get(), stderr); + + if (msg.linefeed_) { + putc(ENDL, stderr); + } #ifndef _WIN32 fputs(END_COLOR, stderr); #elif !defined(RING_UWP) SetConsoleTextAttribute(hConsole, saved_attributes); #endif - } else { - ::vsyslog(level, format, ap); } +}; + +void +Logger::setConsoleLog(bool en) +{ + ConsoleLog::instance().enable(en); +} + +class SysLog : public jami::Logger::Handler +{ +public: + static SysLog& instance() + { + // This is an intentional memory leak!!! + // + // Some thread can still be logging after DRing::fini and even + // during the static destructors called by libstdc++. Thus, we + // allocate the logger on the heap and never free it. + static SysLog* self = new SysLog(); + + return *self; + } + + SysLog() + { +#ifdef _WIN32 + ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL); +#else + ::openlog(LOGFILE, LOG_NDELAY, LOG_USER); +#endif /* _WIN32 */ + } + + virtual void consume(jami::Logger::Msg& msg) override + { + // syslog is supposed to thread-safe, but not all implementations (Android?) + // follow strictly POSIX rules... so we lock our mutex in any cases. + std::lock_guard<std::mutex> lk {mtx_}; +#ifdef __ANDROID__ + __android_log_print(msg.level_, APP_NAME, "%s%s", msg.header_.c_str(), msg.payload_.get()); +#else + ::syslog(msg.level_, "%s", msg.payload_.get()); +#endif + } + +private: + std::mutex mtx_; +}; + +void +Logger::setSysLog(bool en) +{ + SysLog::instance().enable(en); +} + +class MonitorLog : public jami::Logger::Handler +{ +public: + static MonitorLog& instance() + { + // This is an intentional memory leak!!! + // + // Some thread can still be logging after DRing::fini and even + // during the static destructors called by libstdc++. Thus, we + // allocate the logger on the heap and never free it. + static MonitorLog* self = new MonitorLog(); + + return *self; + } + + virtual void consume(jami::Logger::Msg& msg) override + { + /* + * TODO - Maybe change the MessageSend sigature to avoid copying + * of message payload? + */ + auto tmp = msg.header_ + std::string(msg.payload_.get()); + + jami::emitSignal<DRing::ConfigurationSignal::MessageSend>(tmp); + } +}; + +void +Logger::setMonitorLog(bool en) +{ + MonitorLog::instance().enable(en); +} + +class FileLog : public jami::Logger::Handler +{ +public: + static FileLog& instance() + { + // This is an intentional memory leak!!! + // + // Some thread can still be logging after DRing::fini and even + // during the static destructors called by libstdc++. Thus, we + // allocate the logger on the heap and never free it. + static FileLog* self = new FileLog(); + + return *self; + } + + void setFile(const std::string& path) + { + if (thread_.joinable()) { + notify([this] { enable(false); }); + thread_.join(); + } + + if (file_.is_open()) { + file_.close(); + } + + if (not path.empty()) { + file_.open(path, std::ofstream::out | std::ofstream::app); + enable(true); + } else { + enable(false); + return; + } + + thread_ = std::thread([this] { + while (isEnable()) { + + { + std::unique_lock lk(mtx_); + + cv_.wait(lk, [&] { return not isEnable() or not currentQ_.empty(); }); + + if (not isEnable()) { + break; + } + + std::swap(currentQ_, pendingQ_); + } + + do_consume(pendingQ_); + pendingQ_.clear(); + } + }); + } + + ~FileLog() + { + notify([=] { enable(false); }); + + if (thread_.joinable()) { + thread_.join(); + } + } + + virtual void consume(jami::Logger::Msg& msg) override + { + notify([&, this] { currentQ_.push_back(std::move(msg)); }); + } + +private: + template<typename T> + void notify(T func) + { + std::unique_lock lk(mtx_); + func(); + cv_.notify_one(); + } + + void do_consume(const std::vector<jami::Logger::Msg>& messages) + { + for (const auto& msg : messages) { + file_ << msg.header_ << msg.payload_.get(); + + if (msg.linefeed_) { + file_ << ENDL; + } + } + + file_.flush(); + } + + std::vector<jami::Logger::Msg> currentQ_; + std::vector<jami::Logger::Msg> pendingQ_; + std::mutex mtx_; + std::condition_variable cv_; + + std::ofstream file_; + std::thread thread_; +}; + +void +Logger::setFileLog(const std::string& path) +{ + FileLog::instance().setFile(path); +} + +void +Logger::log(int level, const char* file, int line, bool linefeed, const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + vlog(level, file, line, linefeed, fmt, ap); + + va_end(ap); +} + +template<typename T> +void log_to_if_enabled(T& handler, Logger::Msg& msg) +{ + if (handler.isEnable()) { + handler.consume(msg); + } +} + +static std::atomic_bool debugEnabled{false}; + +void +Logger::setDebugMode(bool enable) +{ + debugEnabled.store(enable); +} + +void +Logger::vlog(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap) +{ + if (not debugEnabled.load() and + level < LOG_WARNING) { + return; + } + + if (not(ConsoleLog::instance().isEnable() or + SysLog::instance().isEnable() or + MonitorLog::instance().isEnable() or + FileLog::instance().isEnable())) { + return; + } + + /* Timestamp is generated here. */ + Msg msg(level, file, line, linefeed, fmt, ap); + + log_to_if_enabled(ConsoleLog::instance(), msg); + log_to_if_enabled(SysLog::instance(), msg); + log_to_if_enabled(MonitorLog::instance(), msg); + log_to_if_enabled(FileLog::instance(), msg); // Takes ownership of msg if enabled +} + +void +Logger::fini() +{ + // Force close on file and join thread + FileLog::instance().setFile(nullptr); } } // namespace jami diff --git a/src/logger.h b/src/logger.h index 0c4e93093636403dc734e137a27c0c5535fd464c..d9c02fe90919ea9abfa70b824cc9ac58a020253e 100644 --- a/src/logger.h +++ b/src/logger.h @@ -25,6 +25,7 @@ #include <cinttypes> // for PRIx64 #include <cstdarg> +#include <atomic> #include <sstream> #include <string> #include "string_utils.h" // to_string @@ -33,24 +34,6 @@ extern "C" { #endif -/** - * Allow writing on the console - */ -void setConsoleLog(int c); - -/** - * When debug mode is not set, logging will not print anything - */ -void setDebugMode(int d); - -/** - * Return the current mode - */ -int getDebugMode(void); - -void setMonitorLog(bool m); -bool getMonitorLog(void); - /** * Thread-safe function to print the stringified contents of errno */ @@ -98,6 +81,10 @@ namespace jami { class Logger { public: + + class Handler; + struct Msg; + Logger(int level, const char* file, int line, bool linefeed) : level_ {level} , file_ {file} @@ -129,8 +116,16 @@ public: /// /// Printf fashion logging (using va_list parameters) /// - static void vlog( - const int level, const char* file, int line, bool linefeed, const char* format, va_list); + static void vlog(int level, const char* file, int line, bool linefeed, const char* fmt, va_list); + + static void setConsoleLog(bool enable); + static void setSysLog(bool enable); + static void setMonitorLog(bool enable); + static void setFileLog(const std::string& path); + + static void setDebugMode(bool enable); + + static void fini(); /// /// Stream fashion logging. @@ -143,6 +138,7 @@ public: } private: + int level_; ///< LOG_XXXX values const char* const file_; ///< contextual filename (printed as header) const int line_; ///< contextual line number (printed as header) diff --git a/src/manager.cpp b/src/manager.cpp index c5fc12dbc34ce51f2a166077afa2e5a615582cf2..28353b9120c458c8239e7e4e4f397f258f0852c4 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -933,7 +933,7 @@ Manager::finish() noexcept void Manager::monitor(bool continuous) { - setMonitorLog(true); + Logger::setMonitorLog(true); JAMI_DBG("############## START MONITORING ##############"); JAMI_DBG("Using PJSIP version %s for %s", pj_get_version(), PJ_OS_NAME); JAMI_DBG("Using GnuTLS version %s", gnutls_check_version(nullptr)); @@ -953,7 +953,7 @@ Manager::monitor(bool continuous) if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account)) acc->monitor(); JAMI_DBG("############## END MONITORING ##############"); - setMonitorLog(continuous); + Logger::setMonitorLog(continuous); } bool diff --git a/src/media/libav_utils.cpp b/src/media/libav_utils.cpp index 58c04443490ae79e067ef37b37f4c6f3d74f01a4..32c3e688a4c6cb56c4b900eeb50169772393775d 100644 --- a/src/media/libav_utils.cpp +++ b/src/media/libav_utils.cpp @@ -188,8 +188,7 @@ init_once() av_lockmgr_register(avcodecManageMutex); #endif - if (getDebugMode()) - setAvLogLevel(); + setAvLogLevel(); #ifdef __ANDROID__ // android doesn't like stdout and stderr :( diff --git a/src/ring_api.cpp b/src/ring_api.cpp index e47b2ba1c1c424159d1eb6578718d7f451ff739a..8b1567dfcbc8f8a60060400c60c36c99030fe151 100644 --- a/src/ring_api.cpp +++ b/src/ring_api.cpp @@ -44,8 +44,16 @@ namespace DRing { bool init(enum InitFlag flags) noexcept { - ::setDebugMode(flags & DRING_FLAG_DEBUG); - ::setConsoleLog(flags & DRING_FLAG_CONSOLE_LOG); + jami::Logger::setDebugMode(DRING_FLAG_DEBUG == (flags & DRING_FLAG_DEBUG)); + + jami::Logger::setSysLog(true); + jami::Logger::setConsoleLog(DRING_FLAG_CONSOLE_LOG == (flags & DRING_FLAG_CONSOLE_LOG)); + + const char* log_file = getenv("JAMI_LOG_FILE"); + + if (log_file) { + jami::Logger::setFileLog(log_file); + } // Following function create a local static variable inside // This var must have the same live as Manager. @@ -77,6 +85,23 @@ void fini() noexcept { jami::Manager::instance().finish(); + jami::Logger::fini(); } } // namespace DRing + +void +logging(const std::string& whom, const std::string& action) noexcept +{ + if ("syslog" == whom) { + jami::Logger::setSysLog(not action.empty()); + } else if ("console" == whom) { + jami::Logger::setConsoleLog(not action.empty()); + } else if ("monitor" == whom) { + jami::Logger::setMonitorLog(not action.empty()); + } else if ("file" == whom) { + jami::Logger::setFileLog(action); + } else { + JAMI_ERR("Bad log handler %s", whom.c_str()); + } +} diff --git a/src/winsyslog.c b/src/winsyslog.c index a9ec4984b93a1500fd8474e307aba55fd699fd59..65e8f11cb93df669fc75b8e3433dc3b56aafdb80 100644 --- a/src/winsyslog.c +++ b/src/winsyslog.c @@ -71,18 +71,22 @@ void closelog(void) free(loghdr); } -/* Emulator for GNU vsyslog() routine +/* Emulator for GNU syslog() routine * Accepts: priority * format * arglist */ // TODO: use a real EventIdentifier with a Message Text file ? // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363679%28v=vs.85%29.aspx - void vsyslog(int level, const char* format, va_list arglist) +void syslog(int level, const char* format, ...) { CONST CHAR *arr[1]; char tmp[1024]; + va_list arglist; + + va_start(arglist, format); + vsnprintf(tmp, 1024, format, arglist); #ifndef RING_UWP @@ -96,6 +100,8 @@ void closelog(void) puts(getLastErrorText(errText, 1024)); } #endif + + va_end(arglist); } /* Emulator for BSD openlog() routine diff --git a/src/winsyslog.h b/src/winsyslog.h index b72565e6472b85ca83dbd24bbeae827c743964bf..ba756e598c078e587709b27fa2a7c54e038e1a6d 100644 --- a/src/winsyslog.h +++ b/src/winsyslog.h @@ -70,7 +70,7 @@ extern "C" { extern void closelog(void); extern void openlog(const char*, int, int); -extern void vsyslog(int, const char*, va_list); +extern void syslog(int, const char*, ...); #ifdef __cplusplus }