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_ERR("%m");
106 : #else
107 : char buf[1000];
108 : JAMI_ERR("%s", 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 553793 : stripDirName(std::string_view path)
115 : {
116 553793 : if (!path.empty()) {
117 553726 : size_t pos = path.find_last_of("/\\");
118 553874 : if (pos != std::string_view::npos)
119 196563 : return path.substr(pos + 1);
120 : }
121 357309 : return path;
122 : }
123 :
124 : static std::string
125 276600 : formatHeader(std::string_view file, unsigned line)
126 : {
127 : #ifdef __linux__
128 276600 : 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 277107 : if (!gettimeofday(&tv, NULL)) {
136 277096 : secs = tv.tv_sec;
137 277096 : milli = tv.tv_usec / 1000; // suppose that milli < 1000
138 : } else {
139 0 : secs = time(NULL);
140 0 : milli = 0;
141 : }
142 :
143 277096 : if (!file.empty()) {
144 831024 : return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
145 : secs,
146 : milli,
147 : tid,
148 553688 : stripDirName(file),
149 276647 : 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 23270 : formatPrintfArgs(const char* format, va_list ap)
157 : {
158 23270 : std::string ret;
159 : /* A good guess of what we might encounter. */
160 : static constexpr size_t default_buf_size = 80;
161 :
162 23266 : ret.resize(default_buf_size);
163 :
164 : /* Necessary if we don't have enough space in buf. */
165 : va_list cp;
166 23255 : va_copy(cp, ap);
167 :
168 23255 : int size = vsnprintf(ret.data(), ret.size(), format, ap);
169 :
170 : /* Not enough space? Well try again. */
171 23252 : if ((size_t) size >= ret.size()) {
172 1538 : ret.resize(size + 1);
173 1538 : vsnprintf((char*) ret.data(), ret.size(), format, cp);
174 : }
175 :
176 23273 : ret.resize(size);
177 :
178 23268 : va_end(cp);
179 :
180 46536 : return ret;
181 0 : }
182 :
183 : struct Logger::Msg
184 : {
185 : Msg() = delete;
186 :
187 253630 : Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
188 253630 : : file_(stripDirName(file))
189 253675 : , line_(line)
190 253675 : , tag_(tag)
191 253675 : , payload_(std::move(message))
192 253285 : , level_(level)
193 253285 : , linefeed_(linefeed)
194 253285 : , header_(formatHeader(file_, line_))
195 253419 : {}
196 :
197 23263 : Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, const char* fmt, va_list ap)
198 23263 : : file_(stripDirName(file))
199 23270 : , line_(line)
200 23270 : , tag_(tag)
201 23270 : , payload_(formatPrintfArgs(fmt, ap))
202 23268 : , level_(level)
203 23268 : , linefeed_(linefeed)
204 23268 : , header_(formatHeader(file_, line_))
205 23259 : {}
206 :
207 850 : Msg(Msg&& other) = default;
208 276201 : Msg& operator=(Msg&& other) = default;
209 :
210 : std::string_view file_;
211 : unsigned line_;
212 : std::string_view tag_;
213 : std::string payload_;
214 : int level_;
215 : bool linefeed_;
216 : std::string header_;
217 : };
218 :
219 : class Logger::Handler
220 : {
221 : public:
222 0 : virtual ~Handler() = default;
223 :
224 : virtual void consume(const Msg& msg) = 0;
225 :
226 918 : virtual void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
227 1385645 : bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
228 :
229 : protected:
230 : std::atomic_bool enabled_ {false};
231 : };
232 :
233 : class ConsoleLog final : public Logger::Handler
234 : {
235 : public:
236 40 : 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) {
247 : OutputDebugStringA(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.
253 : static auto printFunc = IsDebuggerPresent() ? outputDebugStringFunc : fputsFunc;
254 : static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
255 :
256 : WORD saved_attributes;
257 : static HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
258 : auto& header = msg.header_;
259 : if (with_color) {
260 : static WORD color_header = CYAN;
261 : WORD color_prefix = LIGHT_GREEN;
262 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
263 :
264 : switch (msg.level_) {
265 : case LOG_ERR:
266 : color_prefix = RED;
267 : break;
268 :
269 : case LOG_WARNING:
270 : color_prefix = YELLOW;
271 : break;
272 : }
273 :
274 : GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
275 : saved_attributes = consoleInfo.wAttributes;
276 : SetConsoleTextAttribute(hConsole, color_header);
277 :
278 : printFunc(header.c_str());
279 :
280 : SetConsoleTextAttribute(hConsole, saved_attributes);
281 : SetConsoleTextAttribute(hConsole, color_prefix);
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) {
295 : SetConsoleTextAttribute(hConsole, saved_attributes);
296 : }
297 : }
298 : #else
299 277051 : void printLogImpl(const Logger::Msg& msg, bool with_color)
300 : {
301 277051 : if (with_color) {
302 277051 : constexpr const char* color_header = CYAN;
303 277051 : const char* color_prefix = "";
304 :
305 277051 : switch (msg.level_) {
306 6443 : case LOG_ERR:
307 6443 : color_prefix = RED;
308 6443 : break;
309 :
310 31978 : case LOG_WARNING:
311 31978 : color_prefix = YELLOW;
312 31978 : break;
313 : }
314 :
315 277051 : fputs(color_header, stderr);
316 277051 : fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
317 277051 : fputs(END_COLOR, stderr);
318 277051 : fputs(color_prefix, stderr);
319 : } else {
320 0 : fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
321 : }
322 :
323 277051 : if (!msg.tag_.empty())
324 46640 : fwrite(msg.tag_.data(), 1, msg.tag_.size(), stderr);
325 277051 : fputs(msg.payload_.c_str(), stderr);
326 :
327 277051 : if (with_color) {
328 277051 : fputs(END_COLOR, stderr);
329 : }
330 277051 : if (msg.linefeed_) {
331 277047 : putc(ENDL, stderr);
332 : }
333 277051 : }
334 : #endif /* _WIN32 */
335 :
336 : bool withColor_ = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR") || getenv("NO_COLOURS"));
337 :
338 277051 : void consume(const Logger::Msg& msg) override { printLogImpl(msg, withColor_); }
339 : };
340 :
341 : class SysLog final : public Logger::Handler
342 : {
343 : public:
344 40 : SysLog()
345 40 : {
346 : #ifdef _WIN32
347 : ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL);
348 : #else
349 : #ifndef __ANDROID__
350 40 : ::openlog(LOGFILE, LOG_NDELAY, LOG_USER);
351 : #endif
352 : #endif /* _WIN32 */
353 40 : }
354 :
355 0 : 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 0 : ::syslog(msg.level_,
361 : "%.*s%.*s",
362 0 : (int) msg.tag_.size(),
363 : msg.tag_.data(),
364 0 : (int) msg.payload_.size(),
365 : msg.payload_.data());
366 : #endif
367 0 : }
368 : };
369 :
370 : class MonitorLog final : public Logger::Handler
371 : {
372 : public:
373 40 : MonitorLog() = default;
374 :
375 0 : void consume(const Logger::Msg& msg) override
376 : {
377 0 : auto message = msg.header_ + msg.payload_;
378 0 : emitSignal<libjami::ConfigurationSignal::MessageSend>(message);
379 0 : }
380 : };
381 :
382 : class FileLog final : public Logger::Handler
383 : {
384 : public:
385 40 : FileLog() = default;
386 :
387 306 : void setFile(const std::string& path)
388 : {
389 306 : std::lock_guard lk(mtx_);
390 306 : if (file_.is_open())
391 0 : file_.close();
392 :
393 306 : if (not path.empty()) {
394 0 : file_.open(path, std::ofstream::out | std::ofstream::app);
395 0 : if (file_)
396 0 : enable(true);
397 : else
398 0 : enable(false);
399 : } else {
400 306 : enable(false);
401 : }
402 306 : }
403 :
404 0 : void consume(const Logger::Msg& msg) override
405 : {
406 0 : std::lock_guard lk(mtx_);
407 0 : if (file_.is_open()) {
408 0 : file_ << msg.header_ << msg.tag_ << msg.payload_;
409 0 : if (msg.linefeed_)
410 0 : file_ << ENDL;
411 0 : file_.flush();
412 : }
413 0 : }
414 :
415 : private:
416 : std::mutex mtx_;
417 : std::ofstream file_;
418 : };
419 :
420 : class LogDispatcher final
421 : {
422 : public:
423 554662 : static LogDispatcher& instance()
424 : {
425 554662 : static LogDispatcher* self = new LogDispatcher();
426 554645 : return *self;
427 : }
428 :
429 276601 : void log(Logger::Msg&& msg)
430 : {
431 : {
432 276601 : std::lock_guard lk(mtx_);
433 277122 : if (!running_)
434 71 : return;
435 277051 : if (!recycleQueue_.empty()) {
436 276201 : msgQueue_.splice(msgQueue_.end(), recycleQueue_, recycleQueue_.begin());
437 276201 : msgQueue_.back() = std::move(msg);
438 : } else {
439 850 : msgQueue_.emplace_back(std::move(msg));
440 : }
441 277122 : }
442 277051 : cv_.notify_one();
443 : }
444 :
445 306 : void stop()
446 : {
447 : {
448 306 : std::lock_guard lk(mtx_);
449 306 : if (!running_)
450 0 : return;
451 306 : running_ = false;
452 306 : }
453 306 : cv_.notify_all();
454 306 : if (thread_.joinable())
455 306 : thread_.join();
456 306 : recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
457 : }
458 :
459 : ConsoleLog consoleLog;
460 : SysLog sysLog;
461 : MonitorLog monitorLog;
462 : FileLog fileLog;
463 :
464 306 : void enableFileLog(const std::string& path)
465 : {
466 306 : fileLog.setFile(path);
467 306 : checkStatus();
468 306 : }
469 :
470 306 : void enableConsoleLog(bool en)
471 : {
472 306 : consoleLog.enable(en);
473 306 : checkStatus();
474 : #ifdef _WIN32
475 : static WORD original_attributes;
476 : if (en) {
477 : if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
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).
482 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
483 : GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo);
484 : original_attributes = consoleInfo.wAttributes;
485 : SetConsoleCP(CP_UTF8);
486 : SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
487 : }
488 : } else {
489 : // Restore the original state of the console window in case we attached.
490 : SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), original_attributes);
491 : FreeConsole();
492 : }
493 : #endif
494 306 : }
495 :
496 306 : void enableSysLog(bool en)
497 : {
498 306 : sysLog.enable(en);
499 306 : checkStatus();
500 306 : }
501 :
502 0 : void enableMonitorLog(bool en)
503 : {
504 0 : monitorLog.enable(en);
505 0 : checkStatus();
506 0 : }
507 :
508 277913 : bool isEnabled()
509 : {
510 277913 : return consoleLog.isEnable() || sysLog.isEnable() || monitorLog.isEnable() || fileLog.isEnable();
511 : }
512 :
513 : private:
514 40 : LogDispatcher() = default;
515 :
516 918 : void checkStatus()
517 : {
518 918 : bool en = isEnabled();
519 918 : std::thread t;
520 : {
521 918 : std::lock_guard lk(mtx_);
522 918 : if (en && !running_) {
523 306 : running_ = true;
524 306 : thread_ = std::thread(&LogDispatcher::loop, this);
525 612 : } else if (!en && running_) {
526 0 : running_ = false;
527 0 : t = std::move(thread_);
528 : }
529 918 : }
530 918 : if (t.joinable()) {
531 0 : cv_.notify_all();
532 0 : t.join();
533 0 : std::lock_guard lk(mtx_);
534 0 : recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
535 0 : }
536 918 : }
537 :
538 306 : void loop()
539 : {
540 306 : std::unique_lock lk(mtx_);
541 229858 : while (running_) {
542 618385 : cv_.wait(lk, [this] { return not msgQueue_.empty() or not running_; });
543 229552 : auto local = std::move(msgQueue_);
544 229552 : lk.unlock();
545 506603 : for (auto& msg : local) {
546 277051 : if (sysLog.isEnable())
547 0 : sysLog.consume(msg);
548 277051 : if (monitorLog.isEnable())
549 0 : monitorLog.consume(msg);
550 277051 : if (consoleLog.isEnable())
551 277051 : consoleLog.consume(msg);
552 277051 : if (fileLog.isEnable())
553 0 : fileLog.consume(msg);
554 :
555 277051 : msg.payload_ = {};
556 277051 : msg.header_ = {};
557 : }
558 229552 : lk.lock();
559 229552 : if (recycleQueue_.size() < 128)
560 229523 : recycleQueue_.splice(recycleQueue_.end(), local);
561 229552 : }
562 306 : }
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 :
572 : void
573 306 : Logger::setConsoleLog(bool en)
574 : {
575 306 : LogDispatcher::instance().enableConsoleLog(en);
576 306 : }
577 :
578 : void
579 306 : Logger::setSysLog(bool en)
580 : {
581 306 : LogDispatcher::instance().enableSysLog(en);
582 306 : }
583 :
584 : void
585 0 : Logger::setMonitorLog(bool en)
586 : {
587 0 : LogDispatcher::instance().enableMonitorLog(en);
588 0 : }
589 :
590 : void
591 0 : Logger::setFileLog(const std::string& path)
592 : {
593 0 : LogDispatcher::instance().enableFileLog(path);
594 0 : }
595 :
596 : LIBJAMI_PUBLIC void
597 23269 : Logger::log(int level, const char* file, unsigned line, bool linefeed, const char* fmt, ...)
598 : {
599 : va_list ap;
600 23269 : va_start(ap, fmt);
601 23269 : vlog(level, file, line, linefeed, fmt, ap);
602 23276 : va_end(ap);
603 23276 : }
604 :
605 : static std::atomic_bool debugEnabled_ {false};
606 :
607 : void
608 306 : Logger::setDebugMode(bool enable)
609 : {
610 306 : debugEnabled_.store(enable, std::memory_order_relaxed);
611 306 : }
612 :
613 : bool
614 77823 : Logger::debugEnabled()
615 : {
616 77823 : return debugEnabled_.load(std::memory_order_relaxed);
617 : }
618 :
619 : void
620 23270 : Logger::vlog(int level, const char* file, unsigned line, bool linefeed, const char* fmt, va_list ap)
621 : {
622 23270 : if (level < LOG_WARNING and not debugEnabled_.load(std::memory_order_relaxed)) {
623 1 : return;
624 : }
625 :
626 23270 : if (!LogDispatcher::instance().isEnabled()) {
627 1 : return;
628 : }
629 :
630 : /* Timestamp is generated here. */
631 23268 : Msg msg(level, file, line, linefeed, {}, fmt, ap);
632 23260 : LogDispatcher::instance().log(std::move(msg));
633 23276 : }
634 :
635 : void
636 253739 : Logger::write(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
637 : {
638 253739 : if (!LogDispatcher::instance().isEnabled()) {
639 2 : return;
640 : }
641 : /* Timestamp is generated here. */
642 253660 : Msg msg(level, file, line, linefeed, tag, std::move(message));
643 253408 : LogDispatcher::instance().log(std::move(msg));
644 253833 : }
645 :
646 : void
647 306 : Logger::fini()
648 : {
649 : // Force close on file and join thread
650 306 : LogDispatcher::instance().enableFileLog({});
651 306 : LogDispatcher::instance().stop();
652 :
653 : #ifdef _WIN32
654 : Logger::setConsoleLog(false);
655 : #endif /* _WIN32 */
656 306 : }
657 :
658 : } // namespace jami
|