|
| 1 | +/* Prevent logger.h's macro layer from redefining our own function bodies. */ |
| 2 | +#define LOG_DISABLE_CONTEXT_MACROS |
| 3 | + |
1 | 4 | #include <stdio.h> |
2 | 5 | #include <stdlib.h> |
3 | 6 | #include <string.h> |
@@ -350,142 +353,143 @@ const char *sanitize_for_logging(const char *str, size_t max_len) { |
350 | 353 | return sanitized; |
351 | 354 | } |
352 | 355 |
|
353 | | -// Log a message at the specified level with va_list |
354 | | -void log_message_v(log_level_t level, const char *format, va_list args) { |
355 | | - // Copy va_list before use so each code path gets a fresh, initialized copy. |
356 | | - // A va_list parameter is only valid for a single traversal; copying it here |
357 | | - // satisfies static-analysis tools (clang-analyzer-valist.Uninitialized) and |
358 | | - // makes the intent explicit. |
| 356 | +// Internal helper: build "[component] [stream] " prefix into buf (max bufsz bytes). |
| 357 | +static void build_ctx_prefix(char *buf, size_t bufsz, |
| 358 | + const char *component, const char *stream) { |
| 359 | + if (component && component[0]) { |
| 360 | + if (stream && stream[0]) { |
| 361 | + snprintf(buf, bufsz, "[%s] [%s] ", component, stream); |
| 362 | + } else { |
| 363 | + snprintf(buf, bufsz, "[%s] ", component); |
| 364 | + } |
| 365 | + } else { |
| 366 | + buf[0] = '\0'; |
| 367 | + } |
| 368 | +} |
| 369 | + |
| 370 | +// Core logging implementation shared by log_message_v and _log_message_ctx. |
| 371 | +// component and stream are already resolved by the caller; NULL or "" means |
| 372 | +// no prefix for that field. |
| 373 | +static void do_log_internal(log_level_t level, |
| 374 | + const char *component, const char *stream, |
| 375 | + const char *format, va_list args) { |
| 376 | + // Copy va_list: a va_list may only be traversed once; copying satisfies |
| 377 | + // static-analysis tools (clang-analyzer-valist.Uninitialized). |
359 | 378 | va_list args_copy; |
360 | 379 | va_copy(args_copy, args); |
361 | 380 |
|
362 | | - // CRITICAL: Check if logger is shutting down or destroyed |
363 | | - // If so, just write to console without mutex to avoid use-after-destroy |
| 381 | + // CRITICAL: Check if logger is shutting down or destroyed. |
| 382 | + // Write directly to console without the mutex to avoid use-after-destroy. |
364 | 383 | if (logger.shutdown) { |
365 | | - // Logger is shutting down or destroyed - use fallback console logging only |
366 | | - // This is safe because we don't use the mutex |
367 | 384 | char message[4096]; |
368 | 385 | vsnprintf(message, sizeof(message), format, args_copy); // NOLINT(clang-analyzer-valist.Uninitialized) |
369 | 386 | va_end(args_copy); |
370 | 387 |
|
371 | 388 | time_t now; |
372 | 389 | struct tm tm_buf; |
373 | | - const struct tm *tm_info; |
374 | 390 | char timestamp[32]; |
375 | 391 | time(&now); |
376 | | - tm_info = localtime_r(&now, &tm_buf); |
377 | | - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); |
378 | | - |
379 | | - char shutdown_prefix[224] = {0}; |
380 | | - if (tls_log_component[0] != '\0') { |
381 | | - if (tls_log_stream[0] != '\0') { |
382 | | - snprintf(shutdown_prefix, sizeof(shutdown_prefix), "[%s] [%s] ", |
383 | | - tls_log_component, tls_log_stream); |
384 | | - } else { |
385 | | - snprintf(shutdown_prefix, sizeof(shutdown_prefix), "[%s] ", tls_log_component); |
386 | | - } |
387 | | - } |
| 392 | + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", |
| 393 | + localtime_r(&now, &tm_buf)); |
| 394 | + |
| 395 | + char ctx_prefix[224] = {0}; |
| 396 | + build_ctx_prefix(ctx_prefix, sizeof(ctx_prefix), component, stream); |
| 397 | + |
388 | 398 | FILE *console = (level == LOG_LEVEL_ERROR) ? stderr : stdout; |
389 | | - fprintf(console, "[%s] [%s] %s%s\n", timestamp, log_level_strings[level], shutdown_prefix, message); |
| 399 | + fprintf(console, "[%s] [%s] %s%s\n", |
| 400 | + timestamp, log_level_strings[level], ctx_prefix, message); |
390 | 401 | fflush(console); |
391 | 402 | return; |
392 | 403 | } |
393 | 404 |
|
394 | | - // Only log messages at or below the configured log level |
395 | | - // For example, if log_level is INFO (2), we log ERROR (0), WARN (1), and INFO (2), but not DEBUG (3) |
| 405 | + // Only log messages at or below the configured log level. |
396 | 406 | if (level > logger.log_level) { |
397 | 407 | va_end(args_copy); |
398 | 408 | return; |
399 | 409 | } |
400 | 410 |
|
401 | 411 | time_t now; |
402 | 412 | struct tm tm_buf; |
403 | | - const struct tm *tm_info; |
404 | 413 | char timestamp[32]; |
405 | 414 | char iso_timestamp[32]; |
406 | 415 |
|
407 | | - // Get current time |
408 | 416 | time(&now); |
409 | | - tm_info = localtime_r(&now, &tm_buf); |
410 | | - |
411 | | - // Format timestamp for text log |
412 | | - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); |
| 417 | + localtime_r(&now, &tm_buf); |
| 418 | + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_buf); |
| 419 | + strftime(iso_timestamp, sizeof(iso_timestamp), "%Y-%m-%dT%H:%M:%S", &tm_buf); |
413 | 420 |
|
414 | | - // Format ISO timestamp for JSON log |
415 | | - strftime(iso_timestamp, sizeof(iso_timestamp), "%Y-%m-%dT%H:%M:%S", tm_info); |
416 | | - |
417 | | - // Format the log message |
418 | 421 | char message[4096]; |
419 | 422 | vsnprintf(message, sizeof(message), format, args_copy); // NOLINT(clang-analyzer-valist.Uninitialized) |
420 | 423 | va_end(args_copy); |
421 | 424 |
|
422 | | - // Build optional [component] [stream] prefix from per-thread context. |
423 | | - // If the thread has not called log_set_thread_context() both strings are |
424 | | - // empty and ctx_prefix is "" (no prefix — fully backward compatible). |
425 | 425 | char ctx_prefix[224] = {0}; |
426 | | - if (tls_log_component[0] != '\0') { |
427 | | - if (tls_log_stream[0] != '\0') { |
428 | | - snprintf(ctx_prefix, sizeof(ctx_prefix), "[%s] [%s] ", |
429 | | - tls_log_component, tls_log_stream); |
430 | | - } else { |
431 | | - snprintf(ctx_prefix, sizeof(ctx_prefix), "[%s] ", tls_log_component); |
432 | | - } |
433 | | - } |
| 426 | + build_ctx_prefix(ctx_prefix, sizeof(ctx_prefix), component, stream); |
434 | 427 |
|
435 | | - // Double-check shutdown flag before acquiring mutex |
| 428 | + // Double-check shutdown before acquiring mutex. |
436 | 429 | if (logger.shutdown) { |
437 | 430 | FILE *console = (level == LOG_LEVEL_ERROR) ? stderr : stdout; |
438 | | - fprintf(console, "[%s] [%s] %s%s\n", timestamp, log_level_strings[level], ctx_prefix, message); |
| 431 | + fprintf(console, "[%s] [%s] %s%s\n", |
| 432 | + timestamp, log_level_strings[level], ctx_prefix, message); |
439 | 433 | fflush(console); |
440 | 434 | return; |
441 | 435 | } |
442 | 436 |
|
443 | 437 | pthread_mutex_lock(&logger.mutex); |
444 | 438 |
|
445 | | - // Write to log file if available |
446 | 439 | if (logger.log_file && logger.log_file != stdout && logger.log_file != stderr) { |
447 | | - fprintf(logger.log_file, "[%s] [%s] %s%s\n", timestamp, log_level_strings[level], ctx_prefix, message); |
| 440 | + fprintf(logger.log_file, "[%s] [%s] %s%s\n", |
| 441 | + timestamp, log_level_strings[level], ctx_prefix, message); |
448 | 442 | fflush(logger.log_file); |
449 | 443 | } |
450 | 444 |
|
451 | | - // Always write to console (tee behavior) |
452 | | - // Use stderr for errors, stdout for other levels |
453 | 445 | FILE *console = (level == LOG_LEVEL_ERROR) ? stderr : stdout; |
454 | | - fprintf(console, "[%s] [%s] %s%s\n", timestamp, log_level_strings[level], ctx_prefix, message); |
| 446 | + fprintf(console, "[%s] [%s] %s%s\n", |
| 447 | + timestamp, log_level_strings[level], ctx_prefix, message); |
455 | 448 | fflush(console); |
456 | 449 |
|
457 | | - // Write to syslog if enabled |
458 | 450 | if (logger.syslog_enabled) { |
459 | | - // Map our log levels to syslog priorities |
460 | 451 | int syslog_priority; |
461 | 452 | switch (level) { |
462 | | - case LOG_LEVEL_ERROR: |
463 | | - syslog_priority = LOG_ERR; |
464 | | - break; |
465 | | - case LOG_LEVEL_WARN: |
466 | | - syslog_priority = LOG_WARNING; |
467 | | - break; |
468 | | - case LOG_LEVEL_INFO: |
469 | | - syslog_priority = LOG_INFO; |
470 | | - break; |
471 | | - case LOG_LEVEL_DEBUG: |
472 | | - syslog_priority = LOG_DEBUG; |
473 | | - break; |
474 | | - default: |
475 | | - syslog_priority = LOG_INFO; |
476 | | - break; |
| 453 | + case LOG_LEVEL_ERROR: syslog_priority = LOG_ERR; break; |
| 454 | + case LOG_LEVEL_WARN: syslog_priority = LOG_WARNING; break; |
| 455 | + case LOG_LEVEL_INFO: syslog_priority = LOG_INFO; break; |
| 456 | + case LOG_LEVEL_DEBUG: syslog_priority = LOG_DEBUG; break; |
| 457 | + default: syslog_priority = LOG_INFO; break; |
477 | 458 | } |
478 | 459 | syslog(syslog_priority, "%s", message); |
479 | 460 | } |
480 | 461 |
|
481 | 462 | pthread_mutex_unlock(&logger.mutex); |
482 | 463 |
|
483 | | - // Write to JSON log file if the function is available (weak symbol, no-op when not linked) |
484 | 464 | if (write_json_log) { |
485 | 465 | write_json_log(level, iso_timestamp, message); |
486 | 466 | } |
487 | 467 | } |
488 | 468 |
|
| 469 | +// Log a message at the specified level with va_list. |
| 470 | +// Reads component/stream from the calling thread's TLS context. |
| 471 | +void log_message_v(log_level_t level, const char *format, va_list args) { |
| 472 | + do_log_internal(level, tls_log_component, tls_log_stream, format, args); |
| 473 | +} |
| 474 | + |
| 475 | +// Internal variant called by the log_* macros. |
| 476 | +// Prefers TLS context; falls back to (component, stream) when TLS is unset. |
| 477 | +void _log_message_ctx(log_level_t level, const char *component, const char *stream, |
| 478 | + const char *format, ...) { |
| 479 | + // TLS takes priority: it represents the most specific runtime context |
| 480 | + // (set explicitly for a thread or operation via log_set_thread_context). |
| 481 | + // The compile-time LOG_COMPONENT / _log_stream_name macros act as |
| 482 | + // fallbacks for threads that have not set a TLS context. |
| 483 | + const char *eff_comp = (tls_log_component[0]) ? tls_log_component |
| 484 | + : (component && component[0]) ? component : ""; |
| 485 | + const char *eff_stream = (tls_log_stream[0]) ? tls_log_stream |
| 486 | + : (stream && stream[0]) ? stream : ""; |
| 487 | + va_list args; |
| 488 | + va_start(args, format); |
| 489 | + do_log_internal(level, eff_comp, eff_stream, format, args); |
| 490 | + va_end(args); |
| 491 | +} |
| 492 | + |
489 | 493 | // Get the string representation of a log level |
490 | 494 | const char *get_log_level_string(log_level_t level) { |
491 | 495 | if (level >= LOG_LEVEL_ERROR && level <= LOG_LEVEL_DEBUG) { |
|
0 commit comments