LCOV - code coverage report
Current view: top level - src - logger.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 70.8 % 216 153
Test Date: 2026-06-13 09:18:46 Functions: 70.2 % 47 33

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : 
      18              : #include <cstdio>
      19              : #include <cstring>
      20              : #include <cerrno>
      21              : #include <ctime>
      22              : 
      23              : #include "client/jami_signal.h"
      24              : 
      25              : #include <fmt/core.h>
      26              : #include <fmt/format.h>
      27              : #include <fmt/compile.h>
      28              : 
      29              : #ifdef _MSC_VER
      30              : #include <sys_time.h>
      31              : #else
      32              : #include <sys/time.h>
      33              : #endif
      34              : 
      35              : #include <atomic>
      36              : #include <condition_variable>
      37              : #include <fstream>
      38              : #include <string>
      39              : #include <mutex>
      40              : #include <thread>
      41              : #include <list>
      42              : 
      43              : #include "logger.h"
      44              : 
      45              : #include <stdio.h>
      46              : #ifdef __linux__
      47              : #include <unistd.h>
      48              : #include <syslog.h>
      49              : #include <sys/syscall.h>
      50              : #endif // __linux__
      51              : 
      52              : #ifdef __ANDROID__
      53              : #ifndef APP_NAME
      54              : #define APP_NAME "libjami"
      55              : #endif /* APP_NAME */
      56              : #endif
      57              : 
      58              : #define END_COLOR "\033[0m"
      59              : 
      60              : #ifndef _WIN32
      61              : #define RED    "\033[22;31m"
      62              : #define YELLOW "\033[01;33m"
      63              : #define CYAN   "\033[22;36m"
      64              : #else
      65              : #define FOREGROUND_WHITE 0x000f
      66              : #define RED              FOREGROUND_RED + 0x0008
      67              : #define YELLOW           FOREGROUND_RED + FOREGROUND_GREEN + 0x0008
      68              : #define CYAN             FOREGROUND_BLUE + FOREGROUND_GREEN + 0x0008
      69              : #define LIGHT_GREEN      FOREGROUND_GREEN + 0x0008
      70              : #endif // _WIN32
      71              : 
      72              : #define LOGFILE "jami"
      73              : 
      74              : namespace jami {
      75              : 
      76              : static constexpr auto ENDL = '\n';
      77              : 
      78              : #ifndef __GLIBC__
      79              : static const char*
      80              : check_error(int result, char* buffer)
      81              : {
      82              :     switch (result) {
      83              :     case 0:
      84              :         return buffer;
      85              : 
      86              :     case ERANGE: /* should never happen */
      87              :         return "unknown (too big to display)";
      88              : 
      89              :     default:
      90              :         return "unknown (invalid error number)";
      91              :     }
      92              : }
      93              : 
      94              : static const char*
      95              : check_error(char* result, char*)
      96              : {
      97              :     return result;
      98              : }
      99              : #endif
     100              : 
     101              : void
     102            0 : strErr()
     103              : {
     104              : #ifdef __GLIBC__
     105            0 :     JAMI_ERROR("{}", strerror(errno));
     106              : #else
     107              :     char buf[1000];
     108              :     JAMI_ERROR("{}", check_error(strerror_r(errno, buf, sizeof(buf)), buf));
     109              : #endif
     110            0 : }
     111              : 
     112              : // extract the last component of a pathname (extract a filename from its dirname)
     113              : static constexpr std::string_view
     114       608765 : stripDirName(std::string_view path)
     115              : {
     116       608765 :     if (!path.empty()) {
     117       608724 :         size_t pos = path.find_last_of("/\\");
     118       608552 :         if (pos != std::string_view::npos)
     119       218031 :             return path.substr(pos + 1);
     120              :     }
     121       390515 :     return path;
     122              : }
     123              : 
     124              : static std::string
     125       304037 : formatHeader(std::string_view file, unsigned line)
     126              : {
     127              : #ifdef __linux__
     128       304037 :     auto tid = syscall(__NR_gettid) & 0xffff;
     129              : #else
     130              :     auto tid = std::this_thread::get_id();
     131              : #endif // __linux__
     132              : 
     133              :     unsigned int secs, milli;
     134              :     struct timeval tv;
     135       304681 :     if (!gettimeofday(&tv, NULL)) {
     136       304677 :         secs = tv.tv_sec;
     137       304677 :         milli = tv.tv_usec / 1000; // suppose that milli < 1000
     138              :     } else {
     139            0 :         secs = time(NULL);
     140            0 :         milli = 0;
     141              :     }
     142              : 
     143       304677 :     if (!file.empty()) {
     144       913347 :         return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
     145              :                            secs,
     146              :                            milli,
     147              :                            tid,
     148       608570 :                            stripDirName(file),
     149       303952 :                            line);
     150              :     } else {
     151            0 :         return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}] "), secs, milli, tid);
     152              :     }
     153              : }
     154              : 
     155              : static std::string
     156            0 : formatPrintfArgs(const char* format, va_list ap)
     157              : {
     158            0 :     std::string ret;
     159              :     /* A good guess of what we might encounter. */
     160              :     static constexpr size_t default_buf_size = 80;
     161              : 
     162            0 :     ret.resize(default_buf_size);
     163              : 
     164              :     /* Necessary if we don't have enough space in buf. */
     165              :     va_list cp;
     166            0 :     va_copy(cp, ap);
     167              : 
     168            0 :     int size = vsnprintf(ret.data(), ret.size(), format, ap);
     169              : 
     170              :     /* Not enough space?  Well try again. */
     171            0 :     if ((size_t) size >= ret.size()) {
     172            0 :         ret.resize(size + 1);
     173            0 :         vsnprintf((char*) ret.data(), ret.size(), format, cp);
     174              :     }
     175              : 
     176            0 :     ret.resize(size);
     177              : 
     178            0 :     va_end(cp);
     179              : 
     180            0 :     return ret;
     181            0 : }
     182              : 
     183              : namespace Logger {
     184              : 
     185              : struct Msg
     186              : {
     187              :     Msg() = delete;
     188              : 
     189       304400 :     Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
     190       304400 :         : file_(stripDirName(file))
     191       304459 :         , line_(line)
     192       608918 :         , tag_(tag)
     193       304184 :         , payload_(std::move(message))
     194       303953 :         , level_(level)
     195       303953 :         , linefeed_(linefeed)
     196       303953 :         , header_(formatHeader(file_, line_))
     197       303955 :     {}
     198              : 
     199              :     Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, const char* fmt, va_list ap)
     200              :         : file_(stripDirName(file))
     201              :         , line_(line)
     202              :         , tag_(tag)
     203              :         , payload_(formatPrintfArgs(fmt, ap))
     204              :         , level_(level)
     205              :         , linefeed_(linefeed)
     206              :         , header_(formatHeader(file_, line_))
     207              :     {}
     208              : 
     209          704 :     Msg(Msg&& other) = default;
     210       303920 :     Msg& operator=(Msg&& other) = default;
     211              : 
     212              :     std::string_view file_;
     213              :     unsigned line_;
     214              :     std::string tag_;
     215              :     std::string payload_;
     216              :     int level_;
     217              :     bool linefeed_;
     218              :     std::string header_;
     219              : };
     220              : 
     221              : class Handler
     222              : {
     223              : public:
     224            0 :     virtual ~Handler() = default;
     225              : 
     226              :     virtual void consume(const Msg& msg) = 0;
     227              : 
     228          921 :     virtual void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
     229      1522548 :     bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
     230              : 
     231              : protected:
     232              :     std::atomic_bool enabled_ {false};
     233              : };
     234              : 
     235              : class ConsoleLog final : public Handler
     236              : {
     237              : public:
     238           40 :     ConsoleLog() = default;
     239              : 
     240              : #ifdef _WIN32
     241              :     void printLogImpl(const Msg& msg, bool with_color)
     242              :     {
     243              :         // If we are using Visual Studio, we can use OutputDebugString to print
     244              :         // to the "Output" window. Otherwise, we just use fputs to stderr.
     245              :         static std::function<void(const char* str)> fputsFunc = [](const char* str) {
     246              :             fputs(str, stderr);
     247              :         };
     248              :         static std::function<void(const char* str)> outputDebugStringFunc = [](const char* str) {
     249              :             OutputDebugStringA(str);
     250              :         };
     251              :         static std::function<void()> putcFunc = []() {
     252              :             putc(ENDL, stderr);
     253              :         };
     254              :         // These next two functions will be used to print the message and line ending.
     255              :         static auto printFunc = IsDebuggerPresent() ? outputDebugStringFunc : fputsFunc;
     256              :         static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
     257              : 
     258              :         WORD saved_attributes;
     259              :         static HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
     260              :         auto& header = msg.header_;
     261              :         if (with_color) {
     262              :             static WORD color_header = CYAN;
     263              :             WORD color_prefix = LIGHT_GREEN;
     264              :             CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
     265              : 
     266              :             switch (msg.level_) {
     267              :             case LOG_ERR:
     268              :                 color_prefix = RED;
     269              :                 break;
     270              : 
     271              :             case LOG_WARNING:
     272              :                 color_prefix = YELLOW;
     273              :                 break;
     274              :             }
     275              : 
     276              :             GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
     277              :             saved_attributes = consoleInfo.wAttributes;
     278              :             SetConsoleTextAttribute(hConsole, color_header);
     279              : 
     280              :             printFunc(header.c_str());
     281              : 
     282              :             SetConsoleTextAttribute(hConsole, saved_attributes);
     283              :             SetConsoleTextAttribute(hConsole, color_prefix);
     284              :         } else {
     285              :             printFunc(header.c_str());
     286              :         }
     287              : 
     288              :         if (!msg.tag_.empty())
     289              :             printFunc(msg.tag_.data());
     290              :         printFunc(msg.payload_.c_str());
     291              : 
     292              :         if (msg.linefeed_) {
     293              :             endlFunc();
     294              :         }
     295              : 
     296              :         if (with_color) {
     297              :             SetConsoleTextAttribute(hConsole, saved_attributes);
     298              :         }
     299              :     }
     300              : #else
     301       304624 :     void printLogImpl(const Msg& msg, bool with_color)
     302              :     {
     303       304624 :         if (with_color) {
     304       304624 :             constexpr const char* color_header = CYAN;
     305       304624 :             const char* color_prefix = "";
     306              : 
     307       304624 :             switch (msg.level_) {
     308         6231 :             case LOG_ERR:
     309         6231 :                 color_prefix = RED;
     310         6231 :                 break;
     311              : 
     312        34604 :             case LOG_WARNING:
     313        34604 :                 color_prefix = YELLOW;
     314        34604 :                 break;
     315              :             }
     316              : 
     317       304624 :             fputs(color_header, stderr);
     318       304624 :             fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
     319       304624 :             fputs(END_COLOR, stderr);
     320       304624 :             fputs(color_prefix, stderr);
     321              :         } else {
     322            0 :             fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
     323              :         }
     324              : 
     325       304624 :         if (!msg.tag_.empty())
     326        52342 :             fwrite(msg.tag_.data(), 1, msg.tag_.size(), stderr);
     327       304624 :         fputs(msg.payload_.c_str(), stderr);
     328              : 
     329       304624 :         if (with_color) {
     330       304624 :             fputs(END_COLOR, stderr);
     331              :         }
     332       304624 :         if (msg.linefeed_) {
     333       304621 :             putc(ENDL, stderr);
     334              :         }
     335       304624 :     }
     336              : #endif /* _WIN32 */
     337              : 
     338              :     bool withColor_ = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR") || getenv("NO_COLOURS"));
     339              : 
     340       304624 :     void consume(const Msg& msg) override { printLogImpl(msg, withColor_); }
     341              : };
     342              : 
     343              : class SysLog final : public Handler
     344              : {
     345              : public:
     346           40 :     SysLog()
     347           40 :     {
     348              : #ifdef _WIN32
     349              :         ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL);
     350              : #else
     351              : #ifndef __ANDROID__
     352           40 :         ::openlog(LOGFILE, LOG_NDELAY, LOG_USER);
     353              : #endif
     354              : #endif /* _WIN32 */
     355           40 :     }
     356              : 
     357            0 :     void consume(const Msg& msg) override
     358              :     {
     359              : #ifdef __ANDROID__
     360              :         __android_log_write(msg.level_, msg.file_.data(), msg.payload_.c_str());
     361              : #else
     362            0 :         ::syslog(msg.level_,
     363              :                  "%.*s%.*s",
     364            0 :                  (int) msg.tag_.size(),
     365              :                  msg.tag_.data(),
     366            0 :                  (int) msg.payload_.size(),
     367              :                  msg.payload_.data());
     368              : #endif
     369            0 :     }
     370              : };
     371              : 
     372              : class MonitorLog final : public Handler
     373              : {
     374              : public:
     375           40 :     MonitorLog() = default;
     376              : 
     377            0 :     void consume(const Msg& msg) override
     378              :     {
     379            0 :         auto message = msg.header_ + msg.payload_;
     380            0 :         emitSignal<libjami::ConfigurationSignal::MessageSend>(message);
     381            0 :     }
     382              : };
     383              : 
     384              : class FileLog final : public Handler
     385              : {
     386              : public:
     387           40 :     FileLog() = default;
     388              : 
     389          307 :     void setFile(const std::string& path)
     390              :     {
     391          307 :         std::lock_guard lk(mtx_);
     392          307 :         if (file_.is_open())
     393            0 :             file_.close();
     394              : 
     395          307 :         if (not path.empty()) {
     396            0 :             file_.open(path, std::ofstream::out | std::ofstream::app);
     397            0 :             if (file_)
     398            0 :                 enable(true);
     399              :             else
     400            0 :                 enable(false);
     401              :         } else {
     402          307 :             enable(false);
     403              :         }
     404          307 :     }
     405              : 
     406            0 :     void consume(const Msg& msg) override
     407              :     {
     408            0 :         std::lock_guard lk(mtx_);
     409            0 :         if (file_.is_open()) {
     410            0 :             file_ << msg.header_ << msg.tag_ << msg.payload_;
     411            0 :             if (msg.linefeed_)
     412            0 :                 file_ << ENDL;
     413            0 :             file_.flush();
     414              :         }
     415            0 :     }
     416              : 
     417              : private:
     418              :     std::mutex mtx_;
     419              :     std::ofstream file_;
     420              : };
     421              : 
     422              : class LogDispatcher final
     423              : {
     424              : public:
     425       609321 :     static LogDispatcher& instance()
     426              :     {
     427       609321 :         static LogDispatcher* self = new LogDispatcher();
     428       609350 :         return *self;
     429              :     }
     430              : 
     431       304073 :     void log(Msg&& msg)
     432              :     {
     433              :         {
     434       304073 :             std::lock_guard lk(mtx_);
     435       304697 :             if (!running_)
     436           73 :                 return;
     437       304624 :             if (!recycleQueue_.empty()) {
     438       303920 :                 msgQueue_.splice(msgQueue_.end(), recycleQueue_, recycleQueue_.begin());
     439       303920 :                 msgQueue_.back() = std::move(msg);
     440              :             } else {
     441          704 :                 msgQueue_.emplace_back(std::move(msg));
     442              :             }
     443       304697 :         }
     444       304621 :         cv_.notify_one();
     445              :     }
     446              : 
     447          307 :     void stop()
     448              :     {
     449              :         {
     450          307 :             std::lock_guard lk(mtx_);
     451          307 :             if (!running_)
     452            0 :                 return;
     453          307 :             running_ = false;
     454          307 :         }
     455          307 :         cv_.notify_all();
     456          307 :         if (thread_.joinable())
     457          307 :             thread_.join();
     458          307 :         recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
     459              :     }
     460              : 
     461              :     ConsoleLog consoleLog;
     462              :     SysLog sysLog;
     463              :     MonitorLog monitorLog;
     464              :     FileLog fileLog;
     465              : 
     466          307 :     void enableFileLog(const std::string& path)
     467              :     {
     468          307 :         fileLog.setFile(path);
     469          307 :         checkStatus();
     470          307 :     }
     471              : 
     472          307 :     void enableConsoleLog(bool en)
     473              :     {
     474          307 :         consoleLog.enable(en);
     475          307 :         checkStatus();
     476              : #ifdef _WIN32
     477              :         static WORD original_attributes;
     478              :         if (en) {
     479              :             if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
     480              :                 FILE *fpstdout = stdout, *fpstderr = stderr;
     481              :                 freopen_s(&fpstdout, "CONOUT$", "w", stdout);
     482              :                 freopen_s(&fpstderr, "CONOUT$", "w", stderr);
     483              :                 // Save the original state of the console window(in case AttachConsole worked).
     484              :                 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
     485              :                 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo);
     486              :                 original_attributes = consoleInfo.wAttributes;
     487              :                 SetConsoleCP(CP_UTF8);
     488              :                 SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
     489              :             }
     490              :         } else {
     491              :             // Restore the original state of the console window in case we attached.
     492              :             SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), original_attributes);
     493              :             FreeConsole();
     494              :         }
     495              : #endif
     496          307 :     }
     497              : 
     498          307 :     void enableSysLog(bool en)
     499              :     {
     500          307 :         sysLog.enable(en);
     501          307 :         checkStatus();
     502          307 :     }
     503              : 
     504            0 :     void enableMonitorLog(bool en)
     505              :     {
     506            0 :         monitorLog.enable(en);
     507            0 :         checkStatus();
     508            0 :     }
     509              : 
     510       305400 :     bool isEnabled()
     511              :     {
     512       305400 :         return consoleLog.isEnable() || sysLog.isEnable() || monitorLog.isEnable() || fileLog.isEnable();
     513              :     }
     514              : 
     515              : private:
     516           40 :     LogDispatcher() = default;
     517              : 
     518          921 :     void checkStatus()
     519              :     {
     520          921 :         bool en = isEnabled();
     521          921 :         std::thread t;
     522              :         {
     523          921 :             std::lock_guard lk(mtx_);
     524          921 :             if (en && !running_) {
     525          307 :                 running_ = true;
     526          307 :                 thread_ = std::thread(&LogDispatcher::loop, this);
     527          614 :             } else if (!en && running_) {
     528            0 :                 running_ = false;
     529            0 :                 t = std::move(thread_);
     530              :             }
     531          921 :         }
     532          921 :         if (t.joinable()) {
     533            0 :             cv_.notify_all();
     534            0 :             t.join();
     535            0 :             std::lock_guard lk(mtx_);
     536            0 :             recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
     537            0 :         }
     538          921 :     }
     539              : 
     540          307 :     void loop()
     541              :     {
     542          307 :         std::unique_lock lk(mtx_);
     543       253181 :         while (running_) {
     544       682548 :             cv_.wait(lk, [this] { return not msgQueue_.empty() or not running_; });
     545       252874 :             auto local = std::move(msgQueue_);
     546       252874 :             lk.unlock();
     547       557498 :             for (auto& msg : local) {
     548       304624 :                 if (sysLog.isEnable())
     549            0 :                     sysLog.consume(msg);
     550       304624 :                 if (monitorLog.isEnable())
     551            0 :                     monitorLog.consume(msg);
     552       304624 :                 if (consoleLog.isEnable())
     553       304624 :                     consoleLog.consume(msg);
     554       304624 :                 if (fileLog.isEnable())
     555            0 :                     fileLog.consume(msg);
     556              : 
     557       304624 :                 msg.payload_ = {};
     558       304624 :                 msg.header_ = {};
     559              :             }
     560       252874 :             lk.lock();
     561       252874 :             if (recycleQueue_.size() < 128)
     562       252772 :                 recycleQueue_.splice(recycleQueue_.end(), local);
     563       252874 :         }
     564          307 :     }
     565              : 
     566              :     std::mutex mtx_;
     567              :     std::condition_variable cv_;
     568              :     std::list<Msg> msgQueue_;
     569              :     std::list<Msg> recycleQueue_;
     570              :     bool running_ {false};
     571              :     std::thread thread_;
     572              : };
     573              : 
     574              : void
     575          307 : setConsoleLog(bool en)
     576              : {
     577          307 :     LogDispatcher::instance().enableConsoleLog(en);
     578          307 : }
     579              : 
     580              : void
     581          307 : setSysLog(bool en)
     582              : {
     583          307 :     LogDispatcher::instance().enableSysLog(en);
     584          307 : }
     585              : 
     586              : void
     587            0 : setMonitorLog(bool en)
     588              : {
     589            0 :     LogDispatcher::instance().enableMonitorLog(en);
     590            0 : }
     591              : 
     592              : void
     593            0 : setFileLog(const std::string& path)
     594              : {
     595            0 :     LogDispatcher::instance().enableFileLog(path);
     596            0 : }
     597              : 
     598              : static std::atomic_bool debugEnabled_ {false};
     599              : 
     600              : void
     601          307 : setDebugMode(bool enable)
     602              : {
     603          307 :     debugEnabled_.store(enable, std::memory_order_relaxed);
     604          307 : }
     605              : 
     606              : bool
     607        88657 : debugEnabled()
     608              : {
     609        88657 :     return debugEnabled_.load(std::memory_order_relaxed);
     610              : }
     611              : 
     612              : void
     613       304449 : write(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
     614              : {
     615       304449 :     if (!LogDispatcher::instance().isEnabled()) {
     616            3 :         return;
     617              :     }
     618              :     /* Timestamp is generated here. */
     619       304353 :     Msg msg(level, file, line, linefeed, tag, std::move(message));
     620       303979 :     LogDispatcher::instance().log(std::move(msg));
     621       304674 : }
     622              : 
     623              : void
     624          307 : fini()
     625              : {
     626              :     // Force close on file and join thread
     627          307 :     LogDispatcher::instance().enableFileLog({});
     628          307 :     LogDispatcher::instance().stop();
     629              : 
     630              : #ifdef _WIN32
     631              :     setConsoleLog(false);
     632              : #endif /* _WIN32 */
     633          307 : }
     634              : 
     635              : } // namespace Logger
     636              : } // namespace jami
        

Generated by: LCOV version 2.0-1