Skip to content

Commit 969c3e0

Browse files
committed
feat: macro-based per-TU logging context (LOG_COMPONENT)
Adds a lightweight preprocessor layer so that every translation unit can declare its component label once at the top of the file: #define LOG_COMPONENT "WebAPI" #include "core/logger.h" Every log_* call in that TU then automatically carries a [WebAPI] prefix whenever the calling thread has no TLS component set. TLS context (set via log_set_thread_context) always takes priority, so all existing long-running threads (HLSWriter, MP4Writer, etc.) are unchanged. Implementation details ---------------------- - _log_message_ctx(level, component, stream, fmt, ...) is the new internal entry point, declared in logger.h with a printf format attribute. LOG_DISABLE_CONTEXT_MACROS suppresses the macro layer in logger.c itself so its own function bodies are not rewritten. - log_message_v() is refactored: shared logic extracted to the static do_log_internal(level, component, stream, fmt, va_list) helper, eliminating the previous code duplication between the normal and shutdown code paths. - _log_stream_name defaults to NULL via #ifndef guard; files/scopes that want to inject a stream can #undef / #define it around log calls without triggering -Wshadow on the common 'stream_name' function parameter. Web layer coverage (46 files) ------------------------------ All src/web/*.c files now declare a component before including the logger header: HTTP libuv_server, libuv_connection, libuv_file_serve, libuv_response, libuv_api_handlers, httpd_utils, request_response DetectionAPI api_handlers_detection{,_models,_results} RecordingsAPI api_handlers_recordings_*, api_handlers_retention, api_handlers_recording_tags, api_handlers_timeline, recordings_playback_state, batch_delete_progress StreamsAPI api_handlers_stream{s_get,s_modify,s_test,ing} SystemAPI api_handlers_system{,_go2rtc,_logs,_logs_tail} SettingsAPI api_handlers_settings HealthCheck api_handlers_health AuthAPI api_handlers_auth_backend_agnostic, api_handlers_totp UsersAPI api_handlers_users_backend_agnostic MotionAPI api_handlers_motion ZonesAPI api_handlers_zones PTZAPI api_handlers_ptz ONVIFAPI api_handlers_onvif_backend_agnostic go2rtcAPI api_handlers_go2rtc_proxy go2rtcProxy go2rtc_proxy_thread SetupAPI api_handlers_setup StorageAPI api_handlers_storage Thumbnail thumbnail_thread WebAPI api_handlers_ice_servers, api_handlers_common_utils Build: clean (zero errors, zero new -Wshadow warnings)
1 parent 5070303 commit 969c3e0

49 files changed

Lines changed: 196 additions & 72 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/core/logger.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,79 @@ int is_syslog_enabled(void);
145145
*/
146146
int is_logger_available(void);
147147

148+
/* -----------------------------------------------------------------------
149+
* Compile-time per-translation-unit context macros
150+
*
151+
* A source file may set a component label BEFORE including this header:
152+
*
153+
* #define LOG_COMPONENT "WebAPI"
154+
* #include "core/logger.h"
155+
*
156+
* Every log_* call in that TU will then carry a [WebAPI] prefix whenever
157+
* the calling thread has no TLS component set. TLS set via
158+
* log_set_thread_context() always takes priority over LOG_COMPONENT.
159+
*
160+
* A per-scope stream name can be injected by redefining _log_stream_name
161+
* around the relevant log calls, e.g. inside a function that handles a
162+
* specific stream:
163+
*
164+
* #undef _log_stream_name
165+
* #define _log_stream_name stream_name // local variable
166+
* log_info("handling request");
167+
* #undef _log_stream_name
168+
* #define _log_stream_name ((const char *)NULL) // restore default
169+
*
170+
* This keeps _log_stream_name as a pure macro so that -Wshadow warnings
171+
* are never triggered by the ubiquitous 'stream_name' parameter name.
172+
* ----------------------------------------------------------------------- */
173+
174+
/**
175+
* Internal variant called by the log_* macros below.
176+
*
177+
* Prefers TLS context (set via log_set_thread_context); falls back to
178+
* the explicit (component, stream) arguments when TLS is unset.
179+
*
180+
* Not intended to be called directly — use the log_* macros instead.
181+
*/
182+
void _log_message_ctx(log_level_t level, const char *component, const char *stream,
183+
const char *format, ...)
184+
__attribute__((format(printf, 4, 5)));
185+
186+
/** Default component: NULL (no prefix). Override per-file with
187+
* #define LOG_COMPONENT "MySubsystem" before #include "core/logger.h". */
188+
#ifndef LOG_COMPONENT
189+
# define LOG_COMPONENT ((const char *)NULL)
190+
#endif
191+
192+
/** Default stream: NULL (no stream prefix).
193+
* Override per-scope: #undef _log_stream_name / #define _log_stream_name var */
194+
#ifndef _log_stream_name
195+
# define _log_stream_name ((const char *)NULL)
196+
#endif
197+
198+
/* Redefine the public log_* names to call _log_message_ctx so that every
199+
* call site automatically picks up LOG_COMPONENT and _log_stream_name.
200+
*
201+
* Define LOG_DISABLE_CONTEXT_MACROS before including this header to opt
202+
* out (logger.c uses this to protect its own function definitions). */
203+
#ifndef LOG_DISABLE_CONTEXT_MACROS
204+
# undef log_error
205+
# define log_error(...) _log_message_ctx(LOG_LEVEL_ERROR, LOG_COMPONENT, \
206+
_log_stream_name, __VA_ARGS__)
207+
# undef log_warn
208+
# define log_warn(...) _log_message_ctx(LOG_LEVEL_WARN, LOG_COMPONENT, \
209+
_log_stream_name, __VA_ARGS__)
210+
# undef log_info
211+
# define log_info(...) _log_message_ctx(LOG_LEVEL_INFO, LOG_COMPONENT, \
212+
_log_stream_name, __VA_ARGS__)
213+
# undef log_debug
214+
# define log_debug(...) _log_message_ctx(LOG_LEVEL_DEBUG, LOG_COMPONENT, \
215+
_log_stream_name, __VA_ARGS__)
216+
# undef log_message
217+
# define log_message(lvl, ...) _log_message_ctx((lvl), LOG_COMPONENT, \
218+
_log_stream_name, __VA_ARGS__)
219+
#endif /* LOG_DISABLE_CONTEXT_MACROS */
220+
148221
/* -----------------------------------------------------------------------
149222
* Per-thread logging context
150223
*

src/core/logger.c

Lines changed: 76 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/* Prevent logger.h's macro layer from redefining our own function bodies. */
2+
#define LOG_DISABLE_CONTEXT_MACROS
3+
14
#include <stdio.h>
25
#include <stdlib.h>
36
#include <string.h>
@@ -350,142 +353,143 @@ const char *sanitize_for_logging(const char *str, size_t max_len) {
350353
return sanitized;
351354
}
352355

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).
359378
va_list args_copy;
360379
va_copy(args_copy, args);
361380

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.
364383
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
367384
char message[4096];
368385
vsnprintf(message, sizeof(message), format, args_copy); // NOLINT(clang-analyzer-valist.Uninitialized)
369386
va_end(args_copy);
370387

371388
time_t now;
372389
struct tm tm_buf;
373-
const struct tm *tm_info;
374390
char timestamp[32];
375391
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+
388398
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);
390401
fflush(console);
391402
return;
392403
}
393404

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.
396406
if (level > logger.log_level) {
397407
va_end(args_copy);
398408
return;
399409
}
400410

401411
time_t now;
402412
struct tm tm_buf;
403-
const struct tm *tm_info;
404413
char timestamp[32];
405414
char iso_timestamp[32];
406415

407-
// Get current time
408416
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);
413420

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
418421
char message[4096];
419422
vsnprintf(message, sizeof(message), format, args_copy); // NOLINT(clang-analyzer-valist.Uninitialized)
420423
va_end(args_copy);
421424

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).
425425
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);
434427

435-
// Double-check shutdown flag before acquiring mutex
428+
// Double-check shutdown before acquiring mutex.
436429
if (logger.shutdown) {
437430
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);
439433
fflush(console);
440434
return;
441435
}
442436

443437
pthread_mutex_lock(&logger.mutex);
444438

445-
// Write to log file if available
446439
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);
448442
fflush(logger.log_file);
449443
}
450444

451-
// Always write to console (tee behavior)
452-
// Use stderr for errors, stdout for other levels
453445
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);
455448
fflush(console);
456449

457-
// Write to syslog if enabled
458450
if (logger.syslog_enabled) {
459-
// Map our log levels to syslog priorities
460451
int syslog_priority;
461452
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;
477458
}
478459
syslog(syslog_priority, "%s", message);
479460
}
480461

481462
pthread_mutex_unlock(&logger.mutex);
482463

483-
// Write to JSON log file if the function is available (weak symbol, no-op when not linked)
484464
if (write_json_log) {
485465
write_json_log(level, iso_timestamp, message);
486466
}
487467
}
488468

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+
489493
// Get the string representation of a log level
490494
const char *get_log_level_string(log_level_t level) {
491495
if (level >= LOG_LEVEL_ERROR && level <= LOG_LEVEL_DEBUG) {

src/web/api_handlers_auth_backend_agnostic.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "web/request_response.h"
1818
#include "web/httpd_utils.h"
1919
#include "web/api_handlers_totp.h"
20+
#define LOG_COMPONENT "AuthAPI"
2021
#include "core/logger.h"
2122
#include "core/config.h"
2223
#include "database/db_auth.h"

src/web/api_handlers_common_utils.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <cjson/cJSON.h>
1010
#include "web/api_handlers_common.h"
11+
#define LOG_COMPONENT "WebAPI"
1112
#include "core/logger.h"
1213
#include "web/request_response.h"
1314

src/web/api_handlers_detection.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "web/api_handlers.h"
1313
#include "web/request_response.h"
14+
#define LOG_COMPONENT "DetectionAPI"
1415
#include "core/logger.h"
1516
#include "core/config.h"
1617
#include "video/detection.h"

src/web/api_handlers_detection_models.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "web/api_handlers.h"
1212
#include "web/request_response.h"
13+
#define LOG_COMPONENT "DetectionAPI"
1314
#include "core/logger.h"
1415
#include "core/config.h"
1516
#include "video/detection.h"

src/web/api_handlers_detection_results.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <cjson/cJSON.h>
1414
#include "web/api_handlers_detection.h"
1515
#include "web/api_handlers_common.h"
16+
#define LOG_COMPONENT "DetectionAPI"
1617
#include "core/logger.h"
1718
#include "core/config.h"
1819
#include "core/mqtt_client.h"

src/web/api_handlers_go2rtc_proxy.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "web/api_handlers_go2rtc_proxy.h"
1919
#include "web/request_response.h"
2020
#include "core/config.h"
21+
#define LOG_COMPONENT "go2rtcAPI"
2122
#include "core/logger.h"
2223
#include "utils/memory.h"
2324

src/web/api_handlers_health.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "web/api_handlers_health.h"
2626
#include "web/http_server.h"
2727
#include "web/request_response.h"
28+
#define LOG_COMPONENT "HealthCheck"
2829
#include "core/logger.h"
2930
#include "core/config.h"
3031

src/web/api_handlers_ice_servers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "web/api_handlers.h"
1212
#include "web/request_response.h"
1313
#include "core/config.h"
14+
#define LOG_COMPONENT "WebAPI"
1415
#include "core/logger.h"
1516

1617
/**

0 commit comments

Comments
 (0)