logging: strip down and clean `log.h` and `log.c`
[babeltrace.git] / src / logging / log-api.c
CommitLineData
71436ae4
SM
1/*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright (c) 2016 wonder-mice
5 * Copyright (c) 2016-2023 Philippe Proulx <pproulx@efficios.com>
6 *
7 * This is very inspired by zf_log.c (see
8 * <https://github.com/wonder-mice/zf_log/>), but modified (mostly
9 * stripped down) for the use cases of Babeltrace.
10 */
11
12#include <ctype.h>
13#include <errno.h>
14#include <glib.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <string.h>
18#include <sys/time.h>
19#include <time.h>
20#include <unistd.h>
21
22#if defined(_WIN32) || defined(_WIN64)
23# include <windows.h>
24#endif
25
26#ifdef __linux__
27# include <sys/prctl.h>
28# include <sys/syscall.h>
29#endif
30
31#ifdef __MACH__
32# include <pthread.h>
33#endif
34
35#ifdef __GNU__
36# include <mach.h>
37#endif
38
39#include "common/assert.h"
40#include "common/common.h"
41#include "common/macros.h"
42#include "compat/time.h"
43
44#include "log-api.h"
45
46#ifdef __CYGWIN__
47extern unsigned long pthread_getsequence_np(pthread_t *);
48#endif
49
50/*
51 * Thread-local logging message buffer to put the next message and write
52 * it with a single system call.
53 */
54static __thread char msg_buf[4 * 4096];
55
56/*
57 * Returns the number of available bytes in `msg_buf` considering the
58 * writing position `at`.
59 */
60static inline
61size_t avail_msg_buf_bytes(char *at)
62{
63 return msg_buf + sizeof(msg_buf) - at;
64}
65
66/*
67 * Appends the character `ch` to `msg_buf`.
68 */
69static inline
70void append_char_to_msg_buf(char ** const at, const char ch)
71{
72 **at = ch;
73 (*at)++;
74}
75
76/*
77 * Appends a space character to `msg_buf`.
78 */
79static inline
80void append_sp_to_msg_buf(char ** const at)
81{
82 append_char_to_msg_buf(at, ' ');
83}
84
85/*
86 * Appends the null-terminated string `str` to `msg_buf`.
87 */
88static inline
89void append_str_to_msg_buf(char ** const at, const char * const str)
90{
91 const size_t len = strlen(str);
92
93 memcpy(*at, str, len);
94 *at += len;
95}
96
97/*
98 * Formats the unsigned integer `val` with sprintf() using `fmt` and
99 * appends the resulting string to `msg_buf`.
100 */
101static inline
102void append_uint_to_msg_buf(char ** const at, const char * const fmt,
103 const unsigned int val)
104{
105#pragma GCC diagnostic push
106#pragma GCC diagnostic ignored "-Wformat-nonliteral"
107 const int written_len = sprintf(*at, fmt, val);
108#pragma GCC diagnostic pop
109
110 BT_ASSERT_DBG(written_len > 0);
111 *at += (size_t) written_len;
112}
113
114/*
115 * Thread-local cache of seconds and milliseconds to formatted date/time
116 * string (null terminated).
117 *
118 * This is often useful because many log messages may happen during the
119 * same millisecond.
120 *
121 * Does not need any kind of protection since it's TLS.
122 */
123struct date_time_cache {
124 uint64_t s;
125 uint32_t ms;
126 char str[128];
127};
128
129static __thread struct date_time_cache date_time_cache = {0};
130
131static
132const char *date_time_cache_get(const struct timeval tv)
133{
134 const uint64_t s = (uint64_t) tv.tv_sec;
135 const uint32_t ms = (uint32_t) (tv.tv_usec / 1000);
136
137 if (date_time_cache.s != s || date_time_cache.ms != ms) {
138 /* Add to cache now */
139 struct tm tm;
140 const time_t time_s = (time_t) tv.tv_sec;
141
142 date_time_cache.s = (uint64_t) tv.tv_sec;
143 date_time_cache.ms = (uint32_t) (tv.tv_usec / 1000);
144 (void) bt_localtime_r(&time_s, &tm);
145 (void) sprintf(date_time_cache.str, "%02u-%02u %02u:%02u:%02u.%03u",
146 tm.tm_mon + 1, tm.tm_mday,
147 tm.tm_hour, tm.tm_min, tm.tm_sec, date_time_cache.ms);
148 }
149
150 return date_time_cache.str;
151}
152
153/*
154 * Appends a formatted date/time for `tv` to `msg_buf`.
155 */
156static inline
157void append_date_time_to_msg_buf(char ** const at, const struct timeval tv)
158{
159 const char *str = date_time_cache_get(tv);
160
161 append_str_to_msg_buf(at, str);
162}
163
164/*
165 * Appends the PID and TID to `msg_buf`.
166 */
167static inline
168void append_pid_tid_to_msg_buf(char ** const at)
169{
170 const unsigned int pid = (unsigned int) getpid();
171 unsigned int tid;
172
173 /* Look at this beautiful portability */
174#if defined(_WIN32) || defined(_WIN64)
175 tid = (unsigned int) GetCurrentThreadId();
176#elif defined(__CYGWIN__)
177 {
178 pthread_t thr = pthread_self();
179
180 tid = (unsigned int) pthread_getsequence_np(&thr);
181 }
182#elif defined(__sun__)
183 tid = (unsigned int) pthread_self();
184#elif defined(__linux__)
185 tid = (unsigned int) syscall(SYS_gettid);
186#elif defined(__APPLE__) && defined(__MACH__)
187 tid = (unsigned int) pthread_mach_thread_np(pthread_self());
188#elif defined(__GNU__)
189 {
190 mach_port_t mach_port = mach_thread_self();
191
192 mach_port_deallocate(mach_task_self(), mach_port);
193 tid = (unsigned int) mach_port;
194 }
195#else
196# error "Platform not supported"
197#endif
198
199 /* Append them now */
200 append_uint_to_msg_buf(at, "%u", pid);
201 append_sp_to_msg_buf(at);
202 append_uint_to_msg_buf(at, "%u", tid);
203}
204
205/*
206 * Writes the initial part of the log message to `msg_buf`, without the
207 * message and without resetting the terminal color).
208 */
209static
210void common_write_init(char ** const at, const char * const file_name,
211 const char * const func_name, const unsigned int line_no,
212 const enum bt_log_level lvl, const char * const tag)
213{
214 const char *color_p = "";
215 struct timeval tv;
216
217 /* Get time immediately */
218 gettimeofday(&tv, 0);
219
220 /* Write the terminal color code to use, if any */
221 switch (lvl) {
222 case BT_LOG_INFO:
223 color_p = bt_common_color_fg_blue();
224 break;
225 case BT_LOG_WARNING:
226 color_p = bt_common_color_fg_yellow();
227 break;
228 case BT_LOG_ERROR:
229 case BT_LOG_FATAL:
230 color_p = bt_common_color_fg_red();
231 break;
232 default:
233 break;
234 }
235
236 append_str_to_msg_buf(at, color_p);
237
238 /* Write date/time */
239 append_date_time_to_msg_buf(at, tv);
240 append_sp_to_msg_buf(at);
241
242 /* Write PID/TID */
243 append_pid_tid_to_msg_buf(at);
244 append_sp_to_msg_buf(at);
245
246 /* Write log level letter */
247 append_char_to_msg_buf(at, bt_log_get_letter_from_level(lvl));
248 append_sp_to_msg_buf(at);
249
250 /* Write tag */
251 if (tag) {
252 append_str_to_msg_buf(at, tag);
253 append_sp_to_msg_buf(at);
254 }
255
256 /* Write source location */
257 append_str_to_msg_buf(at, func_name);
258 append_char_to_msg_buf(at, '@');
259 append_str_to_msg_buf(at, file_name);
260 append_uint_to_msg_buf(at, ":%u", line_no);
261 append_sp_to_msg_buf(at);
262}
263
264/*
265 * Writes the final part of the log message to `msg_buf` (resets the
266 * terminal color and appends a newline), and then writes the whole log
267 * message to the standard error.
268 */
269static
270void common_write_fini(char ** const at)
271{
272 append_str_to_msg_buf(at, bt_common_color_reset());
273 append_char_to_msg_buf(at, '\n');
274 (void) write(STDERR_FILENO, msg_buf, *at - msg_buf);
275}
276
277void bt_log_write(const char * const file_name, const char * const func_name,
278 const unsigned int line_no, const enum bt_log_level lvl,
279 const char * const tag, const char * const msg)
280{
281 char *at = msg_buf;
282
283 common_write_init(&at, file_name, func_name, line_no, lvl, tag);
284 append_str_to_msg_buf(&at, msg);
285 common_write_fini(&at);
286}
287
288_BT_LOG_PRINTFLIKE(6, 0)
289static
290void write_vprintf(const char * const file_name, const char * const func_name,
291 const unsigned int line_no, const enum bt_log_level lvl,
292 const char * const tag, const char * const fmt, va_list args)
293{
294 char *at = msg_buf;
295 int written_len;
296
297 common_write_init(&at, file_name, func_name, line_no, lvl, tag);
298 written_len = vsnprintf(at, avail_msg_buf_bytes(at) - 16,
299 fmt, args);
300 if (written_len > 0) {
301 at += (size_t) written_len;
302 }
303
304 common_write_fini(&at);
305}
306
307void bt_log_write_printf(const char * const file_name,
308 const char * const func_name, const unsigned int line_no,
309 const enum bt_log_level lvl, const char * const tag,
310 const char * const fmt, ...)
311{
312 va_list va;
313
314 va_start(va, fmt);
315 write_vprintf(file_name, func_name, line_no, lvl, tag, fmt, va);
316 va_end(va);
317}
318
319/*
320 * Writes the initial part of the log message to `msg_buf`, including
321 * the initial message and `errno` string, without the message and
322 * without resetting the terminal color).
323 */
324static
325void common_write_errno_init(char ** const at, const char * const file_name,
326 const char * const func_name, const unsigned int line_no,
327 const enum bt_log_level lvl, const char * const tag,
328 const char * const init_msg)
329{
330 BT_ASSERT_DBG(errno != 0);
331 const char * const errno_msg = g_strerror(errno);
332
333 common_write_init(at, file_name, func_name, line_no, lvl, tag);
334 append_str_to_msg_buf(at, init_msg);
335 append_char_to_msg_buf(at, ':');
336 append_sp_to_msg_buf(at);
337 append_str_to_msg_buf(at, errno_msg);
338}
339
340void bt_log_write_errno(const char * const file_name,
341 const char * const func_name, const unsigned int line_no,
342 const enum bt_log_level lvl, const char * const tag,
343 const char * const init_msg, const char * const msg)
344{
345 char *at = msg_buf;
346
347 common_write_errno_init(&at, file_name, func_name, line_no, lvl,
348 tag, init_msg);
349 append_str_to_msg_buf(&at, msg);
350 common_write_fini(&at);
351}
352
353void bt_log_write_errno_printf(const char * const file_name,
354 const char * const func_name, const unsigned int line_no,
355 const enum bt_log_level lvl, const char * const tag,
356 const char * const init_msg, const char * const fmt, ...)
357{
358 char *at = msg_buf;
359 int written_len;
360 va_list va;
361
362 common_write_errno_init(&at, file_name, func_name, line_no, lvl,
363 tag, init_msg);
364 va_start(va, fmt);
365 written_len = vsnprintf(at, avail_msg_buf_bytes(at) - 16, fmt, va);
366 va_end(va);
367 if (written_len > 0) {
368 at += (size_t) written_len;
369 }
370
371 common_write_fini(&at);
372}
373
374/*
375 * Logs `mem_len` bytes of `mem_data` on a single line.
376 */
377static
378void write_mem_line(const char * const file_name, const char * const func_name,
379 const unsigned int line_no, const enum bt_log_level lvl,
380 const char * const tag, const uint8_t * const mem_data,
381 const size_t mem_len, const size_t max_mem_line_len)
382{
383 static const char * const hex_chars = "0123456789abcdef";
384 char *at = msg_buf;
385 size_t i;
386
387 common_write_init(&at, file_name, func_name, line_no, lvl, tag);
388
389 /* Write hexadecimal representation */
390 for (i = 0; i < mem_len; i++) {
391 const uint8_t byte = mem_data[i];
392
393 /* Write nibble */
394 append_char_to_msg_buf(&at, hex_chars[byte >> 4]);
395 append_char_to_msg_buf(&at, hex_chars[byte & 0xf]);
396
397 /* Add a space */
398 append_sp_to_msg_buf(&at);
399 }
400
401 /* Insert spaces to align the following ASCII representation */
402 for (i = 0; i < max_mem_line_len - mem_len; i++) {
403 append_sp_to_msg_buf(&at);
404 append_sp_to_msg_buf(&at);
405 append_sp_to_msg_buf(&at);
406 }
407
408 /* Insert a vertical line between the representations */
409 append_str_to_msg_buf(&at, "| ");
410
411 /* Write the ASCII representation */
412 for (i = 0; i < mem_len; i++) {
413 const uint8_t byte = mem_data[i];
414
415 if (isprint(byte)) {
416 append_char_to_msg_buf(&at, (char) byte);
417 } else {
418 /* Non-printable character */
419 append_char_to_msg_buf(&at, '.');
420 }
421 }
422
423 common_write_fini(&at);
424}
425
426/*
427 * Logs `mem_len` bytes of `mem_data` on one or more lines.
428 */
429static
430void write_mem_lines(const char * const file_name, const char * const func_name,
431 const unsigned int line_no, const enum bt_log_level lvl,
432 const char * const tag, const uint8_t * const mem_data,
433 const size_t mem_len)
434{
435 const uint8_t *mem_at = mem_data;
436 size_t rem_mem_len = mem_len;
437
438 if (!mem_data || mem_len == 0) {
439 /* Nothing to write */
440 goto end;
441 }
442
443 while (rem_mem_len > 0) {
444 static const size_t max_mem_line_len = 16;
445
446 /* Number of bytes to write on this line */
447 const size_t mem_line_len = rem_mem_len > max_mem_line_len ?
448 max_mem_line_len : rem_mem_len;
449
450 /* Log those bytes */
451 write_mem_line(file_name, func_name, line_no, lvl, tag,
452 mem_at, mem_line_len, max_mem_line_len);
453
454 /* Adjust for next iteration */
455 rem_mem_len -= mem_line_len;
456 mem_at += mem_line_len;
457 }
458
459end:
460 return;
461}
462
463void bt_log_write_mem(const char * const file_name, const char * const func_name,
464 const unsigned int line_no, const enum bt_log_level lvl,
465 const char * const tag, const void * const mem_data,
466 const size_t mem_len, const char * const msg)
467{
468 bt_log_write(file_name, func_name, line_no, lvl, tag, msg);
469 write_mem_lines(file_name, func_name, line_no, lvl, tag,
470 mem_data, mem_len);
471}
472
473void bt_log_write_mem_printf(const char * const file_name,
474 const char * const func_name, const unsigned int line_no,
475 const enum bt_log_level lvl, const char * const tag,
476 const void * const mem_data, const size_t mem_len,
477 const char * const fmt, ...)
478{
479 va_list va;
480
481 va_start(va, fmt);
482 write_vprintf(file_name, func_name, line_no, lvl, tag, fmt, va);
483 va_end(va);
484
485 write_mem_lines(file_name, func_name, line_no, lvl, tag,
486 mem_data, mem_len);
487}
This page took 0.050572 seconds and 4 git commands to generate.