Ring Daemon 16.0.0
Loading...
Searching...
No Matches
logger.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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#include <ciso646> // fix windows compiler bug
23
24#include "client/ring_signal.h"
25
26#include <fmt/core.h>
27#include <fmt/format.h>
28#include <fmt/compile.h>
29
30#ifdef _MSC_VER
31#include <sys_time.h>
32#else
33#include <sys/time.h>
34#endif
35
36#include <atomic>
37#include <condition_variable>
38#include <functional>
39#include <fstream>
40#include <string>
41#include <ios>
42#include <mutex>
43#include <thread>
44#include <array>
45
46#include "fileutils.h"
47#include "logger.h"
48
49#ifdef __linux__
50#include <unistd.h>
51#include <syslog.h>
52#include <sys/syscall.h>
53#endif // __linux__
54
55#ifdef __ANDROID__
56#ifndef APP_NAME
57#define APP_NAME "libjami"
58#endif /* APP_NAME */
59#endif
60
61#define END_COLOR "\033[0m"
62
63#ifndef _WIN32
64#define RED "\033[22;31m"
65#define YELLOW "\033[01;33m"
66#define CYAN "\033[22;36m"
67#else
68#define FOREGROUND_WHITE 0x000f
69#define RED FOREGROUND_RED + 0x0008
70#define YELLOW FOREGROUND_RED + FOREGROUND_GREEN + 0x0008
71#define CYAN FOREGROUND_BLUE + FOREGROUND_GREEN + 0x0008
72#define LIGHT_GREEN FOREGROUND_GREEN + 0x0008
73#endif // _WIN32
74
75#define LOGFILE "jami"
76
77namespace jami {
78
79static constexpr auto ENDL = '\n';
80
81#ifndef __GLIBC__
82static const char*
83check_error(int result, char* buffer)
84{
85 switch (result) {
86 case 0:
87 return buffer;
88
89 case ERANGE: /* should never happen */
90 return "unknown (too big to display)";
91
92 default:
93 return "unknown (invalid error number)";
94 }
95}
96
97static const char*
98check_error(char* result, char*)
99{
100 return result;
101}
102#endif
103
104void
106{
107#ifdef __GLIBC__
108 JAMI_ERR("%m");
109#else
110 char buf[1000];
111 JAMI_ERR("%s", check_error(strerror_r(errno, buf, sizeof(buf)), buf));
112#endif
113}
114
115// extract the last component of a pathname (extract a filename from its dirname)
116static const char*
117stripDirName(const char* path)
118{
119 if (path) {
120 const char* occur = strrchr(path, DIR_SEPARATOR_CH);
121 return occur ? occur + 1 : path;
122 } else return nullptr;
123}
124
125std::string
126formatHeader(const char* const file, int line)
127{
128#ifdef __linux__
129 auto tid = syscall(__NR_gettid) & 0xffff;
130#else
131 auto tid = std::this_thread::get_id();
132#endif // __linux__
133
134 unsigned int secs, milli;
135 struct timeval tv;
136 if (!gettimeofday(&tv, NULL)) {
137 secs = tv.tv_sec;
138 milli = tv.tv_usec / 1000; // suppose that milli < 1000
139 } else {
140 secs = time(NULL);
141 milli = 0;
142 }
143
144 if (file) {
145 return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
146 secs,
147 milli,
148 tid,
150 line);
151 } else {
152 return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}] "), secs, milli, tid);
153 }
154}
155
156std::string
157formatPrintfArgs(const char* format, va_list ap)
158{
159 std::string ret;
160 /* A good guess of what we might encounter. */
161 static constexpr size_t default_buf_size = 80;
162
163 ret.resize(default_buf_size);
164
165 /* Necessary if we don't have enough space in buf. */
166 va_list cp;
167 va_copy(cp, ap);
168
169 int size = vsnprintf(ret.data(), ret.size(), format, ap);
170
171 /* Not enough space? Well try again. */
172 if ((size_t) size >= ret.size()) {
173 ret.resize(size + 1);
174 vsnprintf((char*) ret.data(), ret.size(), format, cp);
175 }
176
177 ret.resize(size);
178
179 va_end(cp);
180
181 return ret;
182}
183
184struct Logger::Msg
185{
186 Msg() = delete;
187
188 Msg(int level, const char* file, int line, bool linefeed, std::string&& message)
189 : file_(stripDirName(file))
190 , line_(line)
191 , payload_(std::move(message))
192 , level_(level)
193 , linefeed_(linefeed)
194 {}
195
196 Msg(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap)
197 : file_(stripDirName(file))
198 , line_(line)
199 , payload_(formatPrintfArgs(fmt, ap))
200 , level_(level)
201 , linefeed_(linefeed)
202 {}
203
204 Msg(Msg&& other)
205 {
206 file_ = other.file_;
207 line_ = other.line_;
208 payload_ = std::move(other.payload_);
209 level_ = other.level_;
210 linefeed_ = other.linefeed_;
211 }
212
213 inline std::string header() const {
214 return formatHeader(file_, line_);
215 }
216
217 const char* file_;
218 unsigned line_;
219 std::string payload_;
222};
223
224class Logger::Handler
225{
226public:
227 virtual ~Handler() = default;
228
229 virtual void consume(Msg& msg) = 0;
230
231 void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
232 bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
233
234private:
235 std::atomic_bool enabled_ {false};
236};
237
238class ConsoleLog : public Logger::Handler
239{
240public:
242 {
243 // Intentional memory leak:
244 // Some thread can still be logging even during static destructors.
245 static ConsoleLog* self = new ConsoleLog();
246 return *self;
247 }
248
249#ifdef _WIN32
250 void printLogImpl(Logger::Msg& msg, bool with_color)
251 {
252 // If we are using Visual Studio, we can use OutputDebugString to print
253 // to the "Output" window. Otherwise, we just use fputs to stderr.
254 static std::function<void(const char* str)> fputsFunc = [](const char* str) {
255 fputs(str, stderr);
256 };
257 static std::function<void(const char* str)> outputDebugStringFunc = [](const char* str) {
259 };
260 static std::function<void()> putcFunc = []() {
261 putc(ENDL, stderr);
262 };
263 // These next two functions will be used to print the message and line ending.
265 static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
266
269 auto header = msg.header();
270 if (with_color) {
271 static WORD color_header = CYAN;
274
275 switch (msg.level_) {
276 case LOG_ERR:
278 break;
279
280 case LOG_WARNING:
282 break;
283 }
284
286 saved_attributes = consoleInfo.wAttributes;
288
289 printFunc(header.c_str());
290
293 } else {
294 printFunc(header.c_str());
295 }
296
297 printFunc(msg.payload_.c_str());
298
299 if (msg.linefeed_) {
300 endlFunc();
301 }
302
303 if (with_color) {
305 }
306 }
307#else
308 void printLogImpl(const Logger::Msg& msg, bool with_color)
309 {
310 auto header = msg.header();
311 if (with_color) {
312 const char* color_header = CYAN;
313 const char* color_prefix = "";
314
315 switch (msg.level_) {
316 case LOG_ERR:
318 break;
319
320 case LOG_WARNING:
322 break;
323 }
324
326 fwrite(header.c_str(), 1, header.size(), stderr);
329 } else {
330 fwrite(header.c_str(), 1, header.size(), stderr);
331 }
332
333 fputs(msg.payload_.c_str(), stderr);
334
335 if (with_color) {
337 }
338 if (msg.linefeed_) {
339 putc(ENDL, stderr);
340 }
341 }
342#endif /* _WIN32 */
343
344 void consume(Logger::Msg& msg) override
345 {
346 static bool with_color = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR")
347 || getenv("NO_COLOURS"));
348
350 }
351};
352
353void
354Logger::setConsoleLog(bool en)
355{
357#ifdef _WIN32
359 if (en) {
361 FILE *fpstdout = stdout, *fpstderr = stderr;
362 freopen_s(&fpstdout, "CONOUT$", "w", stdout);
363 freopen_s(&fpstderr, "CONOUT$", "w", stderr);
364 // Save the original state of the console window(in case AttachConsole worked).
367 original_attributes = consoleInfo.wAttributes;
371 }
372 } else {
373 // Restore the original state of the console window in case we attached.
375 FreeConsole();
376 }
377#endif
378}
379
380class SysLog : public Logger::Handler
381{
382public:
383 static SysLog& instance()
384 {
385 // Intentional memory leak:
386 // Some thread can still be logging even during static destructors.
387 static SysLog* self = new SysLog();
388 return *self;
389 }
390
392 {
393#ifdef _WIN32
395#else
396#ifndef __ANDROID__
398#endif
399#endif /* _WIN32 */
400 }
401
402 void consume(Logger::Msg& msg) override
403 {
404#ifdef __ANDROID__
405 __android_log_write(msg.level_, msg.file_, msg.payload_.c_str());
406#else
407 ::syslog(msg.level_, "%.*s", (int) msg.payload_.size(), msg.payload_.data());
408#endif
409 }
410};
411
412void
413Logger::setSysLog(bool en)
414{
416}
417
418class MonitorLog : public Logger::Handler
419{
420public:
422 {
423 // Intentional memory leak
424 // Some thread can still be logging even during static destructors.
425 static MonitorLog* self = new MonitorLog();
426 return *self;
427 }
428
429 void consume(Logger::Msg& msg) override
430 {
431 auto message = msg.header() + msg.payload_;
433 }
434};
435
436void
437Logger::setMonitorLog(bool en)
438{
440}
441
442class FileLog : public Logger::Handler
443{
444public:
446 {
447 // Intentional memory leak:
448 // Some thread can still be logging even during static destructors.
449 static FileLog* self = new FileLog();
450 return *self;
451 }
452
453 void setFile(const std::string& path)
454 {
455 if (thread_.joinable()) {
456 notify([this] { enable(false); });
457 thread_.join();
458 }
459
460 std::ofstream file;
461 if (not path.empty()) {
462 file.open(path, std::ofstream::out | std::ofstream::app);
463 enable(true);
464 } else {
465 enable(false);
466 return;
467 }
468
469 thread_ = std::thread([this, file = std::move(file)]() mutable {
470 std::vector<Logger::Msg> pendingQ_;
471 while (isEnable()) {
472 {
473 std::unique_lock lk(mtx_);
474 cv_.wait(lk, [&] { return not isEnable() or not currentQ_.empty(); });
475 if (not isEnable())
476 break;
477
478 std::swap(currentQ_, pendingQ_);
479 }
480
481 do_consume(file, pendingQ_);
482 pendingQ_.clear();
483 }
484 file.close();
485 });
486 }
487
489 {
490 notify([=] { enable(false); });
491 if (thread_.joinable())
492 thread_.join();
493 }
494
495 void consume(Logger::Msg& msg) override
496 {
497 notify([&, this] { currentQ_.emplace_back(std::move(msg)); });
498 }
499
500private:
501 template<typename T>
502 void notify(T func)
503 {
504 std::lock_guard lk(mtx_);
505 func();
506 cv_.notify_one();
507 }
508
509 void do_consume(std::ofstream& file, const std::vector<Logger::Msg>& messages)
510 {
511 for (const auto& msg : messages) {
512 file << msg.header() << msg.payload_;
513 if (msg.linefeed_)
514 file << ENDL;
515 }
516 file.flush();
517 }
518
519 std::vector<Logger::Msg> currentQ_;
520 std::mutex mtx_;
521 std::condition_variable cv_;
522 std::thread thread_;
523};
524
525void
526Logger::setFileLog(const std::string& path)
527{
529}
530
532Logger::log(int level, const char* file, int line, bool linefeed, const char* fmt, ...)
533{
534 va_list ap;
535
536 va_start(ap, fmt);
537
538 vlog(level, file, line, linefeed, fmt, ap);
539
540 va_end(ap);
541}
542
543template<typename T>
544void
546{
547 if (handler.isEnable()) {
548 handler.consume(msg);
549 }
550}
551
552static std::atomic_bool debugEnabled_ {false};
553
554void
555Logger::setDebugMode(bool enable)
556{
557 debugEnabled_.store(enable, std::memory_order_relaxed);
558}
559
560bool
561Logger::debugEnabled()
562{
563 return debugEnabled_.load(std::memory_order_relaxed);
564}
565
566void
567Logger::vlog(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap)
568{
569 if (level < LOG_WARNING and not debugEnabled_.load(std::memory_order_relaxed)) {
570 return;
571 }
572
573 if (not(ConsoleLog::instance().isEnable() or SysLog::instance().isEnable()
574 or MonitorLog::instance().isEnable() or FileLog::instance().isEnable())) {
575 return;
576 }
577
578 /* Timestamp is generated here. */
579 Msg msg(level, file, line, linefeed, fmt, ap);
580
584 log_to_if_enabled(FileLog::instance(), msg); // Takes ownership of msg if enabled
585}
586
587void
588Logger::write(int level, const char* file, int line, bool linefeed, std::string&& message)
589{
590 /* Timestamp is generated here. */
591 Msg msg(level, file, line, linefeed, std::move(message));
592
596 log_to_if_enabled(FileLog::instance(), msg); // Takes ownership of msg if enabled
597}
598
599void
600Logger::fini()
601{
602 // Force close on file and join thread
604
605#ifdef _WIN32
606 Logger::setConsoleLog(false);
607#endif /* _WIN32 */
608}
609
610} // namespace jami
void consume(Logger::Msg &msg) override
Definition logger.cpp:344
static ConsoleLog & instance()
Definition logger.cpp:241
void printLogImpl(const Logger::Msg &msg, bool with_color)
Definition logger.cpp:308
static FileLog & instance()
Definition logger.cpp:445
void consume(Logger::Msg &msg) override
Definition logger.cpp:495
void setFile(const std::string &path)
Definition logger.cpp:453
virtual void consume(Msg &msg)=0
virtual ~Handler()=default
void enable(bool en)
Definition logger.cpp:231
static LIBJAMI_PUBLIC void static LIBJAMI_PUBLIC void vlog(int level, const char *file, int line, bool linefeed, const char *fmt, va_list)
Printf fashion logging (using va_list parameters)
Definition logger.cpp:567
static MonitorLog & instance()
Definition logger.cpp:421
void consume(Logger::Msg &msg) override
Definition logger.cpp:429
static SysLog & instance()
Definition logger.cpp:383
void consume(Logger::Msg &msg) override
Definition logger.cpp:402
#define LIBJAMI_PUBLIC
Definition def.h:42
#define DIR_SEPARATOR_CH
Definition fileutils.h:34
#define END_COLOR
Definition logger.cpp:61
#define LOGFILE
Definition logger.cpp:75
#define RED
Definition logger.cpp:64
#define YELLOW
Definition logger.cpp:65
#define CYAN
Definition logger.cpp:66
#define JAMI_ERR(...)
Definition logger.h:218
static std::atomic_bool debugEnabled_
Definition logger.cpp:552
void emitSignal(Args... args)
Definition ring_signal.h:64
std::string formatPrintfArgs(const char *format, va_list ap)
Definition logger.cpp:157
static const char * stripDirName(const char *path)
Definition logger.cpp:117
void strErr()
Thread-safe function to print the stringified contents of errno.
Definition logger.cpp:105
static const char * check_error(int result, char *buffer)
Definition logger.cpp:83
static constexpr auto ENDL
Definition logger.cpp:79
void log_to_if_enabled(T &handler, Logger::Msg &msg)
Definition logger.cpp:545
std::string formatHeader(const char *const file, int line)
Definition logger.cpp:126
Msg(int level, const char *file, int line, bool linefeed, std::string &&message)
Definition logger.cpp:188
std::string header() const
Definition logger.cpp:213
Msg(int level, const char *file, int line, bool linefeed, const char *fmt, va_list ap)
Definition logger.cpp:196
Msg(Msg &&other)
Definition logger.cpp:204
std::string payload_
Definition logger.cpp:219
const char * file_
Definition logger.cpp:217
#define WINLOG_MAIL
Definition winsyslog.h:35
#define WINLOG_PID
Definition winsyslog.h:58
#define strerror_r(errno, buf, len)
Definition winsyslog.h:65
void syslog(int, const char *,...)
void openlog(const char *, int, int)