Line data Source code
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 :
77 : namespace jami {
78 :
79 : static constexpr auto ENDL = '\n';
80 :
81 : #ifndef __GLIBC__
82 : static const char*
83 : check_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 :
97 : static const char*
98 : check_error(char* result, char*)
99 : {
100 : return result;
101 : }
102 : #endif
103 :
104 : void
105 0 : strErr()
106 : {
107 : #ifdef __GLIBC__
108 0 : 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 0 : }
114 :
115 : // extract the last component of a pathname (extract a filename from its dirname)
116 : static const char*
117 496150 : stripDirName(const char* path)
118 : {
119 496150 : if (path) {
120 390989 : const char* occur = strrchr(path, DIR_SEPARATOR_CH);
121 390989 : return occur ? occur + 1 : path;
122 : } else
123 105161 : return nullptr;
124 : }
125 :
126 : std::string
127 300685 : formatHeader(const char* const file, int line)
128 : {
129 : #ifdef __linux__
130 300685 : auto tid = syscall(__NR_gettid) & 0xffff;
131 : #else
132 : auto tid = std::this_thread::get_id();
133 : #endif // __linux__
134 :
135 : unsigned int secs, milli;
136 : struct timeval tv;
137 300867 : if (!gettimeofday(&tv, NULL)) {
138 300852 : secs = tv.tv_sec;
139 300852 : milli = tv.tv_usec / 1000; // suppose that milli < 1000
140 : } else {
141 0 : secs = time(NULL);
142 0 : milli = 0;
143 : }
144 :
145 300852 : if (file) {
146 390988 : return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
147 : secs,
148 : milli,
149 : tid,
150 390814 : stripDirName(file),
151 195214 : line);
152 : } else {
153 420908 : return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}] "), secs, milli, tid);
154 : }
155 : }
156 :
157 : std::string
158 34588 : formatPrintfArgs(const char* format, va_list ap)
159 : {
160 34588 : std::string ret;
161 : /* A good guess of what we might encounter. */
162 : static constexpr size_t default_buf_size = 80;
163 :
164 34590 : ret.resize(default_buf_size);
165 :
166 : /* Necessary if we don't have enough space in buf. */
167 : va_list cp;
168 34597 : va_copy(cp, ap);
169 :
170 34597 : int size = vsnprintf(ret.data(), ret.size(), format, ap);
171 :
172 : /* Not enough space? Well try again. */
173 34598 : if ((size_t) size >= ret.size()) {
174 6245 : ret.resize(size + 1);
175 6245 : vsnprintf((char*) ret.data(), ret.size(), format, cp);
176 : }
177 :
178 34608 : ret.resize(size);
179 :
180 34598 : va_end(cp);
181 :
182 69196 : return ret;
183 0 : }
184 :
185 : struct Logger::Msg
186 : {
187 : Msg() = delete;
188 :
189 266099 : Msg(int level, const char* file, int line, bool linefeed, std::string&& message)
190 266099 : : file_(stripDirName(file))
191 266115 : , line_(line)
192 266115 : , payload_(std::move(message))
193 266111 : , level_(level)
194 266111 : , linefeed_(linefeed)
195 266111 : {}
196 :
197 34584 : Msg(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap)
198 34584 : : file_(stripDirName(file))
199 34589 : , line_(line)
200 34589 : , payload_(formatPrintfArgs(fmt, ap))
201 34595 : , level_(level)
202 34595 : , linefeed_(linefeed)
203 34595 : {}
204 :
205 0 : Msg(Msg&& other)
206 0 : {
207 0 : file_ = other.file_;
208 0 : line_ = other.line_;
209 0 : payload_ = std::move(other.payload_);
210 0 : level_ = other.level_;
211 0 : linefeed_ = other.linefeed_;
212 0 : }
213 :
214 300622 : inline std::string header() const { return formatHeader(file_, line_); }
215 :
216 : const char* file_;
217 : unsigned line_;
218 : std::string payload_;
219 : int level_;
220 : bool linefeed_;
221 : };
222 :
223 : class Logger::Handler
224 : {
225 : public:
226 0 : virtual ~Handler() = default;
227 :
228 : virtual void consume(Msg& msg) = 0;
229 :
230 921 : void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
231 1236721 : bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
232 :
233 : private:
234 : std::atomic_bool enabled_ {false};
235 : };
236 :
237 : class ConsoleLog : public Logger::Handler
238 : {
239 : public:
240 335573 : static ConsoleLog& instance()
241 : {
242 : // Intentional memory leak:
243 : // Some thread can still be logging even during static destructors.
244 335573 : static ConsoleLog* self = new ConsoleLog();
245 335529 : return *self;
246 : }
247 :
248 : #ifdef _WIN32
249 : void printLogImpl(Logger::Msg& msg, bool with_color)
250 : {
251 : // If we are using Visual Studio, we can use OutputDebugString to print
252 : // to the "Output" window. Otherwise, we just use fputs to stderr.
253 : static std::function<void(const char* str)> fputsFunc = [](const char* str) {
254 : fputs(str, stderr);
255 : };
256 : static std::function<void(const char* str)> outputDebugStringFunc = [](const char* str) {
257 : OutputDebugStringA(str);
258 : };
259 : static std::function<void()> putcFunc = []() {
260 : putc(ENDL, stderr);
261 : };
262 : // These next two functions will be used to print the message and line ending.
263 : static auto printFunc = IsDebuggerPresent() ? outputDebugStringFunc : fputsFunc;
264 : static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
265 :
266 : WORD saved_attributes;
267 : static HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
268 : auto header = msg.header();
269 : if (with_color) {
270 : static WORD color_header = CYAN;
271 : WORD color_prefix = LIGHT_GREEN;
272 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
273 :
274 : switch (msg.level_) {
275 : case LOG_ERR:
276 : color_prefix = RED;
277 : break;
278 :
279 : case LOG_WARNING:
280 : color_prefix = YELLOW;
281 : break;
282 : }
283 :
284 : GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
285 : saved_attributes = consoleInfo.wAttributes;
286 : SetConsoleTextAttribute(hConsole, color_header);
287 :
288 : printFunc(header.c_str());
289 :
290 : SetConsoleTextAttribute(hConsole, saved_attributes);
291 : SetConsoleTextAttribute(hConsole, color_prefix);
292 : } else {
293 : printFunc(header.c_str());
294 : }
295 :
296 : printFunc(msg.payload_.c_str());
297 :
298 : if (msg.linefeed_) {
299 : endlFunc();
300 : }
301 :
302 : if (with_color) {
303 : SetConsoleTextAttribute(hConsole, saved_attributes);
304 : }
305 : }
306 : #else
307 300626 : void printLogImpl(const Logger::Msg& msg, bool with_color)
308 : {
309 300626 : auto header = msg.header();
310 300387 : if (with_color) {
311 300387 : const char* color_header = CYAN;
312 300387 : const char* color_prefix = "";
313 :
314 300387 : switch (msg.level_) {
315 6282 : case LOG_ERR:
316 6282 : color_prefix = RED;
317 6282 : break;
318 :
319 32471 : case LOG_WARNING:
320 32471 : color_prefix = YELLOW;
321 32471 : break;
322 : }
323 :
324 300387 : fputs(color_header, stderr);
325 300840 : fwrite(header.c_str(), 1, header.size(), stderr);
326 300870 : fputs(END_COLOR, stderr);
327 300873 : fputs(color_prefix, stderr);
328 : } else {
329 0 : fwrite(header.c_str(), 1, header.size(), stderr);
330 : }
331 :
332 300873 : fputs(msg.payload_.c_str(), stderr);
333 :
334 300879 : if (with_color) {
335 300879 : fputs(END_COLOR, stderr);
336 : }
337 300879 : if (msg.linefeed_) {
338 300875 : putc(ENDL, stderr);
339 : }
340 300874 : }
341 : #endif /* _WIN32 */
342 :
343 300599 : void consume(Logger::Msg& msg) override
344 : {
345 76 : static bool with_color = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR")
346 300675 : || getenv("NO_COLOURS"));
347 :
348 300599 : printLogImpl(msg, with_color);
349 300872 : }
350 : };
351 :
352 : void
353 307 : Logger::setConsoleLog(bool en)
354 : {
355 307 : ConsoleLog::instance().enable(en);
356 : #ifdef _WIN32
357 : static WORD original_attributes;
358 : if (en) {
359 : if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
360 : FILE *fpstdout = stdout, *fpstderr = stderr;
361 : freopen_s(&fpstdout, "CONOUT$", "w", stdout);
362 : freopen_s(&fpstderr, "CONOUT$", "w", stderr);
363 : // Save the original state of the console window(in case AttachConsole worked).
364 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
365 : GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo);
366 : original_attributes = consoleInfo.wAttributes;
367 : SetConsoleCP(CP_UTF8);
368 : SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
369 : }
370 : } else {
371 : // Restore the original state of the console window in case we attached.
372 : SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), original_attributes);
373 : FreeConsole();
374 : }
375 : #endif
376 307 : }
377 :
378 : class SysLog : public Logger::Handler
379 : {
380 : public:
381 301184 : static SysLog& instance()
382 : {
383 : // Intentional memory leak:
384 : // Some thread can still be logging even during static destructors.
385 301184 : static SysLog* self = new SysLog();
386 301184 : return *self;
387 : }
388 :
389 39 : SysLog()
390 39 : {
391 : #ifdef _WIN32
392 : ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL);
393 : #else
394 : #ifndef __ANDROID__
395 39 : ::openlog(LOGFILE, LOG_NDELAY, LOG_USER);
396 : #endif
397 : #endif /* _WIN32 */
398 39 : }
399 :
400 0 : void consume(Logger::Msg& msg) override
401 : {
402 : #ifdef __ANDROID__
403 : __android_log_write(msg.level_, msg.file_, msg.payload_.c_str());
404 : #else
405 0 : ::syslog(msg.level_, "%.*s", (int) msg.payload_.size(), msg.payload_.data());
406 : #endif
407 0 : }
408 : };
409 :
410 : void
411 307 : Logger::setSysLog(bool en)
412 : {
413 307 : SysLog::instance().enable(en);
414 307 : }
415 :
416 : class MonitorLog : public Logger::Handler
417 : {
418 : public:
419 300870 : static MonitorLog& instance()
420 : {
421 : // Intentional memory leak
422 : // Some thread can still be logging even during static destructors.
423 300870 : static MonitorLog* self = new MonitorLog();
424 300871 : return *self;
425 : }
426 :
427 0 : void consume(Logger::Msg& msg) override
428 : {
429 0 : auto message = msg.header() + msg.payload_;
430 0 : emitSignal<libjami::ConfigurationSignal::MessageSend>(message);
431 0 : }
432 : };
433 :
434 : void
435 0 : Logger::setMonitorLog(bool en)
436 : {
437 0 : MonitorLog::instance().enable(en);
438 0 : }
439 :
440 : class FileLog : public Logger::Handler
441 : {
442 : public:
443 301177 : static FileLog& instance()
444 : {
445 : // Intentional memory leak:
446 : // Some thread can still be logging even during static destructors.
447 301177 : static FileLog* self = new FileLog();
448 301176 : return *self;
449 : }
450 :
451 307 : void setFile(const std::string& path)
452 : {
453 307 : if (thread_.joinable()) {
454 0 : notify([this] { enable(false); });
455 0 : thread_.join();
456 : }
457 :
458 307 : std::ofstream file;
459 307 : if (not path.empty()) {
460 0 : file.open(path, std::ofstream::out | std::ofstream::app);
461 0 : enable(true);
462 : } else {
463 307 : enable(false);
464 307 : return;
465 : }
466 :
467 0 : thread_ = std::thread([this, file = std::move(file)]() mutable {
468 0 : std::vector<Logger::Msg> pendingQ_;
469 0 : while (isEnable()) {
470 : {
471 0 : std::unique_lock lk(mtx_);
472 0 : cv_.wait(lk, [&] { return not isEnable() or not currentQ_.empty(); });
473 0 : if (not isEnable())
474 0 : break;
475 :
476 0 : std::swap(currentQ_, pendingQ_);
477 0 : }
478 :
479 0 : do_consume(file, pendingQ_);
480 0 : pendingQ_.clear();
481 : }
482 0 : file.close();
483 0 : });
484 307 : }
485 :
486 0 : ~FileLog()
487 0 : {
488 0 : notify([=] { enable(false); });
489 0 : if (thread_.joinable())
490 0 : thread_.join();
491 0 : }
492 :
493 0 : void consume(Logger::Msg& msg) override
494 : {
495 0 : notify([&, this] { currentQ_.emplace_back(std::move(msg)); });
496 0 : }
497 :
498 : private:
499 : template<typename T>
500 0 : void notify(T func)
501 : {
502 0 : std::lock_guard lk(mtx_);
503 0 : func();
504 0 : cv_.notify_one();
505 0 : }
506 :
507 0 : void do_consume(std::ofstream& file, const std::vector<Logger::Msg>& messages)
508 : {
509 0 : for (const auto& msg : messages) {
510 0 : file << msg.header() << msg.payload_;
511 0 : if (msg.linefeed_)
512 0 : file << ENDL;
513 : }
514 0 : file.flush();
515 0 : }
516 :
517 : std::vector<Logger::Msg> currentQ_;
518 : std::mutex mtx_;
519 : std::condition_variable cv_;
520 : std::thread thread_;
521 : };
522 :
523 : void
524 0 : Logger::setFileLog(const std::string& path)
525 : {
526 0 : FileLog::instance().setFile(path);
527 0 : }
528 :
529 : LIBJAMI_PUBLIC void
530 34602 : Logger::log(int level, const char* file, int line, bool linefeed, const char* fmt, ...)
531 : {
532 : va_list ap;
533 :
534 34602 : va_start(ap, fmt);
535 :
536 34602 : vlog(level, file, line, linefeed, fmt, ap);
537 :
538 34610 : va_end(ap);
539 34610 : }
540 :
541 : template<typename T>
542 : void
543 1203253 : log_to_if_enabled(T& handler, Logger::Msg& msg)
544 : {
545 1203253 : if (handler.isEnable()) {
546 300611 : handler.consume(msg);
547 : }
548 1203484 : }
549 :
550 : static std::atomic_bool debugEnabled_ {false};
551 :
552 : void
553 307 : Logger::setDebugMode(bool enable)
554 : {
555 307 : debugEnabled_.store(enable, std::memory_order_relaxed);
556 307 : }
557 :
558 : bool
559 67693 : Logger::debugEnabled()
560 : {
561 67693 : return debugEnabled_.load(std::memory_order_relaxed);
562 : }
563 :
564 : void
565 34600 : Logger::vlog(int level, const char* file, int line, bool linefeed, const char* fmt, va_list ap)
566 : {
567 34600 : if (level < LOG_WARNING and not debugEnabled_.load(std::memory_order_relaxed)) {
568 1 : return;
569 : }
570 :
571 34601 : if (not(ConsoleLog::instance().isEnable() or SysLog::instance().isEnable() or MonitorLog::instance().isEnable()
572 6 : or FileLog::instance().isEnable())) {
573 1 : return;
574 : }
575 :
576 : /* Timestamp is generated here. */
577 34582 : Msg msg(level, file, line, linefeed, fmt, ap);
578 :
579 34595 : log_to_if_enabled(ConsoleLog::instance(), msg);
580 34609 : log_to_if_enabled(SysLog::instance(), msg);
581 34609 : log_to_if_enabled(MonitorLog::instance(), msg);
582 34609 : log_to_if_enabled(FileLog::instance(), msg); // Takes ownership of msg if enabled
583 34608 : }
584 :
585 : void
586 266180 : Logger::write(int level, const char* file, int line, bool linefeed, std::string&& message)
587 : {
588 : /* Timestamp is generated here. */
589 266180 : Msg msg(level, file, line, linefeed, std::move(message));
590 :
591 266102 : log_to_if_enabled(ConsoleLog::instance(), msg);
592 266267 : log_to_if_enabled(SysLog::instance(), msg);
593 266265 : log_to_if_enabled(MonitorLog::instance(), msg);
594 266263 : log_to_if_enabled(FileLog::instance(), msg); // Takes ownership of msg if enabled
595 266260 : }
596 :
597 : void
598 307 : Logger::fini()
599 : {
600 : // Force close on file and join thread
601 307 : FileLog::instance().setFile({});
602 :
603 : #ifdef _WIN32
604 : Logger::setConsoleLog(false);
605 : #endif /* _WIN32 */
606 307 : }
607 :
608 : } // namespace jami
|