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 <functional>
38 : #include <fstream>
39 : #include <string>
40 : #include <ios>
41 : #include <mutex>
42 : #include <thread>
43 : #include <array>
44 : #include <list>
45 :
46 : #include "fileutils.h"
47 : #include "logger.h"
48 :
49 : #include <stdio.h>
50 : #ifdef __linux__
51 : #include <unistd.h>
52 : #include <syslog.h>
53 : #include <sys/syscall.h>
54 : #endif // __linux__
55 :
56 : #ifdef __ANDROID__
57 : #ifndef APP_NAME
58 : #define APP_NAME "libjami"
59 : #endif /* APP_NAME */
60 : #endif
61 :
62 : #define END_COLOR "\033[0m"
63 :
64 : #ifndef _WIN32
65 : #define RED "\033[22;31m"
66 : #define YELLOW "\033[01;33m"
67 : #define CYAN "\033[22;36m"
68 : #else
69 : #define FOREGROUND_WHITE 0x000f
70 : #define RED FOREGROUND_RED + 0x0008
71 : #define YELLOW FOREGROUND_RED + FOREGROUND_GREEN + 0x0008
72 : #define CYAN FOREGROUND_BLUE + FOREGROUND_GREEN + 0x0008
73 : #define LIGHT_GREEN FOREGROUND_GREEN + 0x0008
74 : #endif // _WIN32
75 :
76 : #define LOGFILE "jami"
77 :
78 : namespace jami {
79 :
80 : static constexpr auto ENDL = '\n';
81 :
82 : #ifndef __GLIBC__
83 : static const char*
84 : check_error(int result, char* buffer)
85 : {
86 : switch (result) {
87 : case 0:
88 : return buffer;
89 :
90 : case ERANGE: /* should never happen */
91 : return "unknown (too big to display)";
92 :
93 : default:
94 : return "unknown (invalid error number)";
95 : }
96 : }
97 :
98 : static const char*
99 : check_error(char* result, char*)
100 : {
101 : return result;
102 : }
103 : #endif
104 :
105 : void
106 0 : strErr()
107 : {
108 : #ifdef __GLIBC__
109 0 : JAMI_ERR("%m");
110 : #else
111 : char buf[1000];
112 : JAMI_ERR("%s", check_error(strerror_r(errno, buf, sizeof(buf)), buf));
113 : #endif
114 0 : }
115 :
116 : // extract the last component of a pathname (extract a filename from its dirname)
117 : static constexpr std::string_view
118 599743 : stripDirName(std::string_view path)
119 : {
120 599743 : if (!path.empty()) {
121 599699 : size_t pos = path.find_last_of("/\\");
122 599898 : if (pos != std::string_view::npos)
123 194802 : return path.substr(pos + 1);
124 : }
125 405091 : return path;
126 : }
127 :
128 : std::string
129 299568 : formatHeader(std::string_view file, unsigned line)
130 : {
131 : #ifdef __linux__
132 299568 : auto tid = syscall(__NR_gettid) & 0xffff;
133 : #else
134 : auto tid = std::this_thread::get_id();
135 : #endif // __linux__
136 :
137 : unsigned int secs, milli;
138 : struct timeval tv;
139 300101 : if (!gettimeofday(&tv, NULL)) {
140 300086 : secs = tv.tv_sec;
141 300086 : milli = tv.tv_usec / 1000; // suppose that milli < 1000
142 : } else {
143 0 : secs = time(NULL);
144 0 : milli = 0;
145 : }
146 :
147 300086 : if (!file.empty()) {
148 899981 : return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}|{: <24s}:{: <4d}] "),
149 : secs,
150 : milli,
151 : tid,
152 599476 : stripDirName(file),
153 299441 : line);
154 : } else {
155 0 : return fmt::format(FMT_COMPILE("[{: >3d}.{:0<3d}|{: >4}] "), secs, milli, tid);
156 : }
157 : }
158 :
159 : std::string
160 22325 : formatPrintfArgs(const char* format, va_list ap)
161 : {
162 22325 : std::string ret;
163 : /* A good guess of what we might encounter. */
164 : static constexpr size_t default_buf_size = 80;
165 :
166 22318 : ret.resize(default_buf_size);
167 :
168 : /* Necessary if we don't have enough space in buf. */
169 : va_list cp;
170 22320 : va_copy(cp, ap);
171 :
172 22320 : int size = vsnprintf(ret.data(), ret.size(), format, ap);
173 :
174 : /* Not enough space? Well try again. */
175 22314 : if ((size_t) size >= ret.size()) {
176 1440 : ret.resize(size + 1);
177 1440 : vsnprintf((char*) ret.data(), ret.size(), format, cp);
178 : }
179 :
180 22338 : ret.resize(size);
181 :
182 22322 : va_end(cp);
183 :
184 44644 : return ret;
185 0 : }
186 :
187 : struct Logger::Msg
188 : {
189 : Msg() = delete;
190 :
191 277546 : Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
192 277546 : : file_(stripDirName(file))
193 277612 : , line_(line)
194 277612 : , tag_(tag)
195 277612 : , payload_(std::move(message))
196 277192 : , level_(level)
197 277192 : , linefeed_(linefeed)
198 277192 : , header_(formatHeader(file_, line_))
199 277202 : {}
200 :
201 22327 : Msg(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, const char* fmt, va_list ap)
202 22327 : : file_(stripDirName(file))
203 22326 : , line_(line)
204 22326 : , tag_(tag)
205 22326 : , payload_(formatPrintfArgs(fmt, ap))
206 22322 : , level_(level)
207 22322 : , linefeed_(linefeed)
208 22322 : , header_(formatHeader(file_, line_))
209 22317 : {}
210 :
211 702 : Msg(Msg&& other) = default;
212 299345 : Msg& operator=(Msg&& other) = default;
213 :
214 : std::string_view file_;
215 : unsigned line_;
216 : std::string_view tag_;
217 : std::string payload_;
218 : int level_;
219 : bool linefeed_;
220 : std::string header_;
221 : };
222 :
223 : class Logger::Handler
224 : {
225 : public:
226 0 : virtual ~Handler() = default;
227 :
228 : virtual void consume(const Msg& msg) = 0;
229 :
230 906 : virtual void enable(bool en) { enabled_.store(en, std::memory_order_relaxed); }
231 1500455 : bool isEnable() { return enabled_.load(std::memory_order_relaxed); }
232 :
233 : protected:
234 : std::atomic_bool enabled_ {false};
235 : };
236 :
237 : class ConsoleLog final : public Logger::Handler
238 : {
239 : public:
240 38 : ConsoleLog() = default;
241 :
242 : #ifdef _WIN32
243 : void printLogImpl(const Logger::Msg& msg, bool with_color)
244 : {
245 : // If we are using Visual Studio, we can use OutputDebugString to print
246 : // to the "Output" window. Otherwise, we just use fputs to stderr.
247 : static std::function<void(const char* str)> fputsFunc = [](const char* str) {
248 : fputs(str, stderr);
249 : };
250 : static std::function<void(const char* str)> outputDebugStringFunc = [](const char* str) {
251 : OutputDebugStringA(str);
252 : };
253 : static std::function<void()> putcFunc = []() {
254 : putc(ENDL, stderr);
255 : };
256 : // These next two functions will be used to print the message and line ending.
257 : static auto printFunc = IsDebuggerPresent() ? outputDebugStringFunc : fputsFunc;
258 : static auto endlFunc = IsDebuggerPresent() ? []() { OutputDebugStringA("\n"); } : putcFunc;
259 :
260 : WORD saved_attributes;
261 : static HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
262 : auto& header = msg.header_;
263 : if (with_color) {
264 : static WORD color_header = CYAN;
265 : WORD color_prefix = LIGHT_GREEN;
266 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
267 :
268 : switch (msg.level_) {
269 : case LOG_ERR:
270 : color_prefix = RED;
271 : break;
272 :
273 : case LOG_WARNING:
274 : color_prefix = YELLOW;
275 : break;
276 : }
277 :
278 : GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
279 : saved_attributes = consoleInfo.wAttributes;
280 : SetConsoleTextAttribute(hConsole, color_header);
281 :
282 : printFunc(header.c_str());
283 :
284 : SetConsoleTextAttribute(hConsole, saved_attributes);
285 : SetConsoleTextAttribute(hConsole, color_prefix);
286 : } else {
287 : printFunc(header.c_str());
288 : }
289 :
290 : if (!msg.tag_.empty())
291 : printFunc(msg.tag_.data());
292 : printFunc(msg.payload_.c_str());
293 :
294 : if (msg.linefeed_) {
295 : endlFunc();
296 : }
297 :
298 : if (with_color) {
299 : SetConsoleTextAttribute(hConsole, saved_attributes);
300 : }
301 : }
302 : #else
303 300045 : void printLogImpl(const Logger::Msg& msg, bool with_color)
304 : {
305 300045 : if (with_color) {
306 300045 : constexpr const char* color_header = CYAN;
307 300045 : const char* color_prefix = "";
308 :
309 300045 : switch (msg.level_) {
310 6297 : case LOG_ERR:
311 6297 : color_prefix = RED;
312 6297 : break;
313 :
314 32470 : case LOG_WARNING:
315 32470 : color_prefix = YELLOW;
316 32470 : break;
317 : }
318 :
319 300045 : fputs(color_header, stderr);
320 300045 : fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
321 300045 : fputs(END_COLOR, stderr);
322 300045 : fputs(color_prefix, stderr);
323 : } else {
324 0 : fwrite(msg.header_.c_str(), 1, msg.header_.size(), stderr);
325 : }
326 :
327 300045 : if (!msg.tag_.empty())
328 71533 : fwrite(msg.tag_.data(), 1, msg.tag_.size(), stderr);
329 300045 : fputs(msg.payload_.c_str(), stderr);
330 :
331 300045 : if (with_color) {
332 300045 : fputs(END_COLOR, stderr);
333 : }
334 300045 : if (msg.linefeed_) {
335 300041 : putc(ENDL, stderr);
336 : }
337 300045 : }
338 : #endif /* _WIN32 */
339 :
340 : bool withColor_ = !(getenv("NO_COLOR") || getenv("NO_COLORS") || getenv("NO_COLOUR") || getenv("NO_COLOURS"));
341 :
342 300045 : void consume(const Logger::Msg& msg) override { printLogImpl(msg, withColor_); }
343 : };
344 :
345 : class SysLog final : public Logger::Handler
346 : {
347 : public:
348 38 : SysLog()
349 38 : {
350 : #ifdef _WIN32
351 : ::openlog(LOGFILE, WINLOG_PID, WINLOG_MAIL);
352 : #else
353 : #ifndef __ANDROID__
354 38 : ::openlog(LOGFILE, LOG_NDELAY, LOG_USER);
355 : #endif
356 : #endif /* _WIN32 */
357 38 : }
358 :
359 0 : void consume(const Logger::Msg& msg) override
360 : {
361 : #ifdef __ANDROID__
362 : __android_log_write(msg.level_, msg.file_.data(), msg.payload_.c_str());
363 : #else
364 0 : ::syslog(msg.level_,
365 : "%.*s%.*s",
366 0 : (int) msg.tag_.size(),
367 : msg.tag_.data(),
368 0 : (int) msg.payload_.size(),
369 : msg.payload_.data());
370 : #endif
371 0 : }
372 : };
373 :
374 : class MonitorLog final : public Logger::Handler
375 : {
376 : public:
377 38 : MonitorLog() = default;
378 :
379 0 : void consume(const Logger::Msg& msg) override
380 : {
381 0 : auto message = msg.header_ + msg.payload_;
382 0 : emitSignal<libjami::ConfigurationSignal::MessageSend>(message);
383 0 : }
384 : };
385 :
386 : class FileLog final : public Logger::Handler
387 : {
388 : public:
389 38 : FileLog() = default;
390 :
391 302 : void setFile(const std::string& path)
392 : {
393 302 : std::lock_guard lk(mtx_);
394 302 : if (file_.is_open())
395 0 : file_.close();
396 :
397 302 : if (not path.empty()) {
398 0 : file_.open(path, std::ofstream::out | std::ofstream::app);
399 0 : if (file_)
400 0 : enable(true);
401 : else
402 0 : enable(false);
403 : } else {
404 302 : enable(false);
405 : }
406 302 : }
407 :
408 0 : void consume(const Logger::Msg& msg) override
409 : {
410 0 : std::lock_guard lk(mtx_);
411 0 : if (file_.is_open()) {
412 0 : file_ << msg.header_ << msg.tag_ << msg.payload_;
413 0 : if (msg.linefeed_)
414 0 : file_ << ENDL;
415 0 : file_.flush();
416 : }
417 0 : }
418 :
419 : private:
420 : std::mutex mtx_;
421 : std::ofstream file_;
422 : };
423 :
424 : class LogDispatcher final
425 : {
426 : public:
427 600452 : static LogDispatcher& instance()
428 : {
429 600452 : static LogDispatcher* self = new LogDispatcher();
430 600428 : return *self;
431 : }
432 :
433 299467 : void log(Logger::Msg&& msg)
434 : {
435 : {
436 299467 : std::lock_guard lk(mtx_);
437 300118 : if (!running_)
438 71 : return;
439 300047 : if (!recycleQueue_.empty()) {
440 299345 : msgQueue_.splice(msgQueue_.end(), recycleQueue_, recycleQueue_.begin());
441 299345 : msgQueue_.back() = std::move(msg);
442 : } else {
443 702 : msgQueue_.emplace_back(std::move(msg));
444 : }
445 300118 : }
446 300042 : cv_.notify_one();
447 : }
448 :
449 302 : void stop()
450 : {
451 : {
452 302 : std::lock_guard lk(mtx_);
453 302 : if (!running_)
454 0 : return;
455 302 : running_ = false;
456 302 : }
457 302 : cv_.notify_all();
458 302 : if (thread_.joinable())
459 302 : thread_.join();
460 302 : recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
461 : }
462 :
463 : ConsoleLog consoleLog;
464 : SysLog sysLog;
465 : MonitorLog monitorLog;
466 : FileLog fileLog;
467 :
468 302 : void enableFileLog(const std::string& path)
469 : {
470 302 : fileLog.setFile(path);
471 302 : checkStatus();
472 302 : }
473 :
474 302 : void enableConsoleLog(bool en)
475 : {
476 302 : consoleLog.enable(en);
477 302 : checkStatus();
478 : #ifdef _WIN32
479 : static WORD original_attributes;
480 : if (en) {
481 : if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
482 : FILE *fpstdout = stdout, *fpstderr = stderr;
483 : freopen_s(&fpstdout, "CONOUT$", "w", stdout);
484 : freopen_s(&fpstderr, "CONOUT$", "w", stderr);
485 : // Save the original state of the console window(in case AttachConsole worked).
486 : CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
487 : GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo);
488 : original_attributes = consoleInfo.wAttributes;
489 : SetConsoleCP(CP_UTF8);
490 : SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
491 : }
492 : } else {
493 : // Restore the original state of the console window in case we attached.
494 : SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), original_attributes);
495 : FreeConsole();
496 : }
497 : #endif
498 302 : }
499 :
500 302 : void enableSysLog(bool en)
501 : {
502 302 : sysLog.enable(en);
503 302 : checkStatus();
504 302 : }
505 :
506 0 : void enableMonitorLog(bool en)
507 : {
508 0 : monitorLog.enable(en);
509 0 : checkStatus();
510 0 : }
511 :
512 300860 : bool isEnabled()
513 : {
514 300860 : return consoleLog.isEnable() || sysLog.isEnable() || monitorLog.isEnable() || fileLog.isEnable();
515 : }
516 :
517 : private:
518 38 : LogDispatcher() = default;
519 :
520 906 : void checkStatus()
521 : {
522 906 : bool en = isEnabled();
523 906 : std::thread t;
524 : {
525 906 : std::lock_guard lk(mtx_);
526 906 : if (en && !running_) {
527 302 : running_ = true;
528 302 : thread_ = std::thread(&LogDispatcher::loop, this);
529 604 : } else if (!en && running_) {
530 0 : running_ = false;
531 0 : t = std::move(thread_);
532 : }
533 906 : }
534 906 : if (t.joinable()) {
535 0 : cv_.notify_all();
536 0 : t.join();
537 0 : std::lock_guard lk(mtx_);
538 0 : recycleQueue_.splice(recycleQueue_.end(), std::move(msgQueue_));
539 0 : }
540 906 : }
541 :
542 302 : void loop()
543 : {
544 302 : std::unique_lock lk(mtx_);
545 251157 : while (running_) {
546 675333 : cv_.wait(lk, [this] { return not msgQueue_.empty() or not running_; });
547 250855 : auto local = std::move(msgQueue_);
548 250855 : lk.unlock();
549 550900 : for (auto& msg : local) {
550 300045 : if (sysLog.isEnable())
551 0 : sysLog.consume(msg);
552 300045 : if (monitorLog.isEnable())
553 0 : monitorLog.consume(msg);
554 300045 : if (consoleLog.isEnable())
555 300045 : consoleLog.consume(msg);
556 300045 : if (fileLog.isEnable())
557 0 : fileLog.consume(msg);
558 :
559 300045 : msg.payload_ = {};
560 300045 : msg.header_ = {};
561 : }
562 250855 : lk.lock();
563 250855 : if (recycleQueue_.size() < 128)
564 250849 : recycleQueue_.splice(recycleQueue_.end(), local);
565 250855 : }
566 302 : }
567 :
568 : std::mutex mtx_;
569 : std::condition_variable cv_;
570 : std::list<Logger::Msg> msgQueue_;
571 : std::list<Logger::Msg> recycleQueue_;
572 : bool running_ {false};
573 : std::thread thread_;
574 : };
575 :
576 : void
577 302 : Logger::setConsoleLog(bool en)
578 : {
579 302 : LogDispatcher::instance().enableConsoleLog(en);
580 302 : }
581 :
582 : void
583 302 : Logger::setSysLog(bool en)
584 : {
585 302 : LogDispatcher::instance().enableSysLog(en);
586 302 : }
587 :
588 : void
589 0 : Logger::setMonitorLog(bool en)
590 : {
591 0 : LogDispatcher::instance().enableMonitorLog(en);
592 0 : }
593 :
594 : void
595 0 : Logger::setFileLog(const std::string& path)
596 : {
597 0 : LogDispatcher::instance().enableFileLog(path);
598 0 : }
599 :
600 : LIBJAMI_PUBLIC void
601 22336 : Logger::log(int level, const char* file, unsigned line, bool linefeed, const char* fmt, ...)
602 : {
603 : va_list ap;
604 22336 : va_start(ap, fmt);
605 22336 : vlog(level, file, line, linefeed, fmt, ap);
606 22336 : va_end(ap);
607 22336 : }
608 :
609 : static std::atomic_bool debugEnabled_ {false};
610 :
611 : void
612 302 : Logger::setDebugMode(bool enable)
613 : {
614 302 : debugEnabled_.store(enable, std::memory_order_relaxed);
615 302 : }
616 :
617 : bool
618 77576 : Logger::debugEnabled()
619 : {
620 77576 : return debugEnabled_.load(std::memory_order_relaxed);
621 : }
622 :
623 : void
624 22335 : Logger::vlog(int level, const char* file, unsigned line, bool linefeed, const char* fmt, va_list ap)
625 : {
626 22335 : if (level < LOG_WARNING and not debugEnabled_.load(std::memory_order_relaxed)) {
627 1 : return;
628 : }
629 :
630 22335 : if (!LogDispatcher::instance().isEnabled()) {
631 1 : return;
632 : }
633 :
634 : /* Timestamp is generated here. */
635 22322 : Msg msg(level, file, line, linefeed, {}, fmt, ap);
636 22323 : LogDispatcher::instance().log(std::move(msg));
637 22340 : }
638 :
639 : void
640 277654 : Logger::write(int level, std::string_view file, unsigned line, bool linefeed, std::string_view tag, std::string&& message)
641 : {
642 277654 : if (!LogDispatcher::instance().isEnabled()) {
643 0 : return;
644 : }
645 : /* Timestamp is generated here. */
646 277559 : Msg msg(level, file, line, linefeed, tag, std::move(message));
647 277203 : LogDispatcher::instance().log(std::move(msg));
648 277766 : }
649 :
650 : void
651 302 : Logger::fini()
652 : {
653 : // Force close on file and join thread
654 302 : LogDispatcher::instance().enableFileLog({});
655 302 : LogDispatcher::instance().stop();
656 :
657 : #ifdef _WIN32
658 : Logger::setConsoleLog(false);
659 : #endif /* _WIN32 */
660 302 : }
661 :
662 : } // namespace jami
|