Ring Daemon
Loading...
Searching...
No Matches
logger.cpp
Go to the documentation of this file.
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
74namespace jami {
75
76static constexpr auto ENDL = '\n';
77
78#ifndef __GLIBC__
79static const char*
80check_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
94static const char*
95check_error(char* result, char*)
96{
97 return result;
98}
99#endif
100
101void
103{
104#ifdef __GLIBC__
105 JAMI_ERR("%m");
106#else
107 char buf[1000];
108 JAMI_ERR("%s", check_error(strerror_r(errno, buf, sizeof(buf)), buf));
109#endif
110}
111
112// extract the last component of a pathname (extract a filename from its dirname)
113static constexpr std::string_view
114stripDirName(std::string_view path)
115{
116 if (!path.empty()) {
117 size_t pos = path.find_last_of("/\\");
118 if (pos != std::string_view::npos)
119 return path.substr(pos + 1);
120 }
121 return path;
122}
123
124static std::string
125formatHeader(std::string_view file, unsigned line)
126{
127#ifdef __linux__
128 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 if (!gettimeofday(&tv, NULL)) {
136 secs = tv.tv_sec;
137 milli = tv.tv_usec / 1000; // suppose that milli < 1000
138 } else {
139 secs = time(NULL);
140 milli = 0;
141 }
142
143 if (!file.empty()) {
144 return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
145 secs,
146 milli,
147 tid,
149 line);
150 } else {
151 return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}] "), secs, milli, tid);
152 }
153}
154
155static std::string
156formatPrintfArgs(const char* format, va_list ap)
157{
158 std::string ret;
159 /* A good guess of what we might encounter. */
160 static constexpr size_t default_buf_size = 80;
161
162 ret.resize(default_buf_size);
163
164 /* Necessary if we don't have enough space in buf. */
165 va_list cp;
166 va_copy(cp, ap);
167
168 int size = vsnprintf(ret.data(), ret.size(), format, ap);
169
170 /* Not enough space? Well try again. */
171 if ((size_t) size >= ret.size()) {
172 ret.resize(size + 1);
173 vsnprintf((char*) ret.data(), ret.size(), format, cp);
174 }
175
176 ret.resize(size);
177
178 va_end(cp);
179
180 return ret;
181}
182
183struct Logger::Msg
184{
185 Msg() = delete;
186
187 Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
188 : file_(stripDirName(file))
189 , line_(line)
190 , tag_(tag)
191 , payload_(std::move(message))
192 , level_(level)
193 , linefeed_(linefeed)
195 {}
196
197 Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, const char* fmt, va_list ap)
198 : file_(stripDirName(file))
199 , line_(line)
200 , tag_(tag)
201 , payload_(formatPrintfArgs(fmt, ap))
202 , level_(level)
203 , linefeed_(linefeed)
205 {}
206
207 Msg(Msg&& other) = default;
208 Msg& operator=(Msg&& other) = default;
209
210 std::string_view file_;
211 unsigned line_;
212 std::string_view tag_;
213 std::string payload_;
216 std::string header_;
217};
218
219class Logger::Handler
220{
221public:
222 virtual ~Handler() = default;
223
224 virtual void consume(const Msg& msg) = 0;
225
226 virtual void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
227 bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
228
229protected:
230 std::atomic_bool enabled_ {false};
231};
232
233class ConsoleLog final : public Logger::Handler
234{
235public:
236 ConsoleLog() = default;
237
238#ifdef _WIN32
239 void printLogImpl(const Logger::Msg& msg, bool with_color)
240 {
241 // If we are using Visual Studio, we can use OutputDebugString to print
242 // to the "Output" window. Otherwise, we just use fputs to stderr.
243 static std::function<void(const char* str)> fputsFunc = [](const char* str) {
244 fputs(str, stderr);
245 };
246 static std::function<void(const char* str)> outputDebugStringFunc = [](const char* str) {
248 };
249 static std::function<void()> putcFunc = []() {
250 putc(ENDL, stderr);
251 };
252 // These next two functions will be used to print the message and line ending.
254 static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
255
258 auto& header = msg.header_;
259 if (with_color) {
260 static WORD color_header = CYAN;
263
264 switch (msg.level_) {
265 case LOG_ERR:
267 break;
268
269 case LOG_WARNING:
271 break;
272 }
273
275 saved_attributes = consoleInfo.wAttributes;
277
278 printFunc(header.c_str());
279
282 } else {
283 printFunc(header.c_str());
284 }
285
286 if (!msg.tag_.empty())
287 printFunc(msg.tag_.data());
288 printFunc(msg.payload_.c_str());
289
290 if (msg.linefeed_) {
291 endlFunc();
292 }
293
294 if (with_color) {
296 }
297 }
298#else
299 void printLogImpl(const Logger::Msg& msg, bool with_color)
300 {
301 if (with_color) {
302 constexpr const char* color_header = CYAN;
303 const char* color_prefix = "";
304
305 switch (msg.level_) {
306 case LOG_ERR:
308 break;
309
310 case LOG_WARNING:
312 break;
313 }
314
316 fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
319 } else {
320 fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
321 }
322
323 if (!msg.tag_.empty())
324 fwrite(msg.tag_.data(), 1, msg.tag_.size(), stderr);
325 fputs(msg.payload_.c_str(), stderr);
326
327 if (with_color) {
329 }
330 if (msg.linefeed_) {
331 putc(ENDL, stderr);
332 }
333 }
334#endif /* _WIN32 */
335
336 bool withColor_ = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR") || getenv("NO_COLOURS"));
337
338 void consume(const Logger::Msg& msg) override { printLogImpl(msg, withColor_); }
339};
340
341class SysLog final : public Logger::Handler
342{
343public:
345 {
346#ifdef _WIN32
348#else
349#ifndef __ANDROID__
351#endif
352#endif /* _WIN32 */
353 }
354
355 void consume(const Logger::Msg& msg) override
356 {
357#ifdef __ANDROID__
358 __android_log_write(msg.level_, msg.file_.data(), msg.payload_.c_str());
359#else
360 ::syslog(msg.level_,
361 "%.*s%.*s",
362 (int) msg.tag_.size(),
363 msg.tag_.data(),
364 (int) msg.payload_.size(),
365 msg.payload_.data());
366#endif
367 }
368};
369
370class MonitorLog final : public Logger::Handler
371{
372public:
373 MonitorLog() = default;
374
375 void consume(const Logger::Msg& msg) override
376 {
377 auto message = msg.header_ + msg.payload_;
379 }
380};
381
382class FileLog final : public Logger::Handler
383{
384public:
385 FileLog() = default;
386
387 void setFile(const std::string& path)
388 {
389 std::lock_guard lk(mtx_);
390 if (file_.is_open())
391 file_.close();
392
393 if (not path.empty()) {
394 file_.open(path, std::ofstream::out | std::ofstream::app);
395 if (file_)
396 enable(true);
397 else
398 enable(false);
399 } else {
400 enable(false);
401 }
402 }
403
404 void consume(const Logger::Msg& msg) override
405 {
406 std::lock_guard lk(mtx_);
407 if (file_.is_open()) {
408 file_ << msg.header_ << msg.tag_ << msg.payload_;
409 if (msg.linefeed_)
410 file_ << ENDL;
411 file_.flush();
412 }
413 }
414
415private:
416 std::mutex mtx_;
417 std::ofstream file_;
418};
419
421{
422public:
424 {
425 static LogDispatcher* self = new LogDispatcher();
426 return *self;
427 }
428
429 void log(Logger::Msg&& msg)
430 {
431 {
432 std::lock_guard lk(mtx_);
433 if (!running_)
434 return;
435 if (!recycleQueue_.empty()) {
436 msgQueue_.splice(msgQueue_.end(), recycleQueue_, recycleQueue_.begin());
437 msgQueue_.back() = std::move(msg);
438 } else {
439 msgQueue_.emplace_back(std::move(msg));
440 }
441 }
442 cv_.notify_one();
443 }
444
445 void stop()
446 {
447 {
448 std::lock_guard lk(mtx_);
449 if (!running_)
450 return;
451 running_ = false;
452 }
453 cv_.notify_all();
454 if (thread_.joinable())
455 thread_.join();
456 recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
457 }
458
463
464 void enableFileLog(const std::string& path)
465 {
466 fileLog.setFile(path);
467 checkStatus();
468 }
469
471 {
473 checkStatus();
474#ifdef _WIN32
476 if (en) {
478 FILE *fpstdout = stdout, *fpstderr = stderr;
479 freopen_s(&fpstdout, "CONOUT$", "w", stdout);
480 freopen_s(&fpstderr, "CONOUT$", "w", stderr);
481 // Save the original state of the console window(in case AttachConsole worked).
484 original_attributes = consoleInfo.wAttributes;
487 }
488 } else {
489 // Restore the original state of the console window in case we attached.
491 FreeConsole();
492 }
493#endif
494 }
495
496 void enableSysLog(bool en)
497 {
499 checkStatus();
500 }
501
503 {
505 checkStatus();
506 }
507
509 {
511 }
512
513private:
514 LogDispatcher() = default;
515
516 void checkStatus()
517 {
518 bool en = isEnabled();
519 std::thread t;
520 {
521 std::lock_guard lk(mtx_);
522 if (en && !running_) {
523 running_ = true;
524 thread_ = std::thread(&LogDispatcher::loop, this);
525 } else if (!en && running_) {
526 running_ = false;
527 t = std::move(thread_);
528 }
529 }
530 if (t.joinable()) {
531 cv_.notify_all();
532 t.join();
533 std::lock_guard lk(mtx_);
534 recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
535 }
536 }
537
538 void loop()
539 {
540 std::unique_lock lk(mtx_);
541 while (running_) {
542 cv_.wait(lk, [this] { return not msgQueue_.empty() or not running_; });
543 auto local = std::move(msgQueue_);
544 lk.unlock();
545 for (auto& msg : local) {
546 if (sysLog.isEnable())
548 if (monitorLog.isEnable())
550 if (consoleLog.isEnable())
552 if (fileLog.isEnable())
554
555 msg.payload_ = {};
556 msg.header_ = {};
557 }
558 lk.lock();
559 if (recycleQueue_.size() < 128)
560 recycleQueue_.splice(recycleQueue_.end(), local);
561 }
562 }
563
564 std::mutex mtx_;
565 std::condition_variable cv_;
566 std::list<Logger::Msg> msgQueue_;
567 std::list<Logger::Msg> recycleQueue_;
568 bool running_ {false};
569 std::thread thread_;
570};
571
572void
573Logger::setConsoleLog(bool en)
574{
576}
577
578void
579Logger::setSysLog(bool en)
580{
582}
583
584void
585Logger::setMonitorLog(bool en)
586{
588}
589
590void
591Logger::setFileLog(const std::string& path)
592{
594}
595
597Logger::log(int level, const char* file, unsigned line, bool linefeed, const char* fmt, ...)
598{
599 va_list ap;
600 va_start(ap, fmt);
601 vlog(level, file, line, linefeed, fmt, ap);
602 va_end(ap);
603}
604
605static std::atomic_bool debugEnabled_ {false};
606
607void
608Logger::setDebugMode(bool enable)
609{
610 debugEnabled_.store(enable, std::memory_order_relaxed);
611}
612
613bool
614Logger::debugEnabled()
615{
616 return debugEnabled_.load(std::memory_order_relaxed);
617}
618
619void
620Logger::vlog(int level, const char* file, unsigned line, bool linefeed, const char* fmt, va_list ap)
621{
622 if (level < LOG_WARNING and not debugEnabled_.load(std::memory_order_relaxed)) {
623 return;
624 }
625
626 if (!LogDispatcher::instance().isEnabled()) {
627 return;
628 }
629
630 /* Timestamp is generated here. */
631 Msg msg(level, file, line, linefeed, {}, fmt, ap);
632 LogDispatcher::instance().log(std::move(msg));
633}
634
635void
636Logger::write(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
637{
638 if (!LogDispatcher::instance().isEnabled()) {
639 return;
640 }
641 /* Timestamp is generated here. */
642 Msg msg(level, file, line, linefeed, tag, std::move(message));
643 LogDispatcher::instance().log(std::move(msg));
644}
645
646void
647Logger::fini()
648{
649 // Force close on file and join thread
652
653#ifdef _WIN32
654 Logger::setConsoleLog(false);
655#endif /* _WIN32 */
656}
657
658} // namespace jami
ConsoleLog()=default
void consume(const Logger::Msg &msg) override
Definition logger.cpp:338
void printLogImpl(const Logger::Msg &msg, bool with_color)
Definition logger.cpp:299
void consume(const Logger::Msg &msg) override
Definition logger.cpp:404
FileLog()=default
void setFile(const std::string &path)
Definition logger.cpp:387
static LogDispatcher & instance()
Definition logger.cpp:423
void enableConsoleLog(bool en)
Definition logger.cpp:470
void enableSysLog(bool en)
Definition logger.cpp:496
ConsoleLog consoleLog
Definition logger.cpp:459
void enableMonitorLog(bool en)
Definition logger.cpp:502
void log(Logger::Msg &&msg)
Definition logger.cpp:429
MonitorLog monitorLog
Definition logger.cpp:461
void enableFileLog(const std::string &path)
Definition logger.cpp:464
std::atomic_bool enabled_
Definition logger.cpp:230
virtual ~Handler()=default
virtual void consume(const Msg &msg)=0
virtual void enable(bool en)
Definition logger.cpp:226
static LIBJAMI_PUBLIC void static LIBJAMI_PUBLIC void vlog(int level, const char *file, unsigned line, bool linefeed, const char *fmt, va_list)
Printf fashion logging (using va_list parameters)
Definition logger.cpp:620
MonitorLog()=default
void consume(const Logger::Msg &msg) override
Definition logger.cpp:375
void consume(const Logger::Msg &msg) override
Definition logger.cpp:355
#define LIBJAMI_PUBLIC
Definition def.h:42
#define END_COLOR
Definition logger.cpp:58
#define LOGFILE
Definition logger.cpp:72
#define RED
Definition logger.cpp:61
#define YELLOW
Definition logger.cpp:62
#define CYAN
Definition logger.cpp:63
#define JAMI_ERR(...)
Definition logger.h:230
static std::atomic_bool debugEnabled_
Definition logger.cpp:605
static std::string formatPrintfArgs(const char *format, va_list ap)
Definition logger.cpp:156
void emitSignal(Args... args)
Definition jami_signal.h:64
void strErr()
Thread-safe function to print the stringified contents of errno.
Definition logger.cpp:102
static const char * check_error(int result, char *buffer)
Definition logger.cpp:80
static constexpr auto ENDL
Definition logger.cpp:76
static constexpr std::string_view stripDirName(std::string_view path)
Definition logger.cpp:114
static std::string formatHeader(std::string_view file, unsigned line)
Definition logger.cpp:125
Msg(Msg &&other)=default
Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, const char *fmt, va_list ap)
Definition logger.cpp:197
std::string header_
Definition logger.cpp:216
Msg & operator=(Msg &&other)=default
Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string &&message)
Definition logger.cpp:187
std::string_view file_
Definition logger.cpp:210
std::string payload_
Definition logger.cpp:213
std::string_view tag_
Definition logger.cpp:212
#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)