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
|