Csilk 0.2.1
A lightweight, high-performance C HTTP web framework
Loading...
Searching...
No Matches
logger.c File Reference

Thread-safe structured logger with JSON and human-readable output, file rotation, and ANSI color support. More...

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <uv.h>
#include "csilk/csilk.h"
#include "csilk/reflection/reflect.h"
Include dependency graph for logger.c:

Data Structures

struct  csilk_log_entry_t
 Structured log entry type used for JSON-formatted log output. More...
 
struct  csilk_logger_t
 Internal logger singleton — holds configuration, file pointer, mutex, and current file size. More...
 

Macros

#define LOG_ENTRY_MAP(X)
 

Functions

static void rotate_log_files (void)
 Rotate the current log file by renaming it with a ".1" suffix.
 
static int log_text (csilk_log_level_t lv, const char *file, int line, const char *func, const char *msg, int msg_len)
 Format and write a human-readable plain-text log line.
 
static cJSON * build_json_entry (csilk_log_level_t lv, const char *file, int line, const char *func, const char *msg, int msg_len)
 Build a cJSON object from log entry fields using the reflection engine.
 
static int log_json (csilk_log_level_t lv, const char *file, int line, const char *func, cJSON *extra, const char *msg, int msg_len)
 Format and write a structured JSON log line with optional extra fields.
 
int csilk_log_init (csilk_log_config_t config)
 Initialize (or reinitialize) the global logger with the given configuration.
 
void _csilk_log_internal (csilk_log_level_t lv, const char *file, int line, const char *func, const char *fmt,...)
 Internal: format and emit a log message to the global logger.
 
void _csilk_log_structured (csilk_log_level_t lv, const char *file, int line, const char *func, cJSON *extra, const char *fmt,...)
 Internal: emit a structured JSON log entry with extra key-value fields.
 
int csilk_log_is_json (void)
 Check whether the global logger is configured for structured JSON output.
 
void csilk_log_set_request_id (const char *request_id)
 Set the Request ID for the current thread.
 
cJSON * csilk_log_make_kv (const char *key,...)
 Build a key-value cJSON object for structured logging.
 
void csilk_log_close (void)
 Close the global logger and release resources.
 

Variables

static csilk_logger_t g_logger = {{0}, NULL, 0, {0}, 0}
 
static _Thread_local char tl_request_id [37] = {0}
 
static const char * level_names [] = {"TRACE", "DEBUG", "INFO ", "WARN ", "ERROR", "FATAL"}
 
static const char * level_colors []
 

Detailed Description

Thread-safe structured logger with JSON and human-readable output, file rotation, and ANSI color support.

=== Design ===

The logger is a global singleton (g_logger) protected by a mutex for thread-safe access. Two output modes are available:

Text mode (default): [2024-01-15 10:30:00] INFO [file.c:42] function(): <request_id> message ANSI color codes are added for each level when use_colors is enabled.

JSON mode: {"time_epoch":1705312200,"level":"INFO","request_id":"...", "file":"file.c","line":42,"func":"function","msg":"..."} Uses the csilk reflection engine (CSILK_REGISTER_REFLECT) for automatic struct-to-JSON serialization, avoiding manual JSON string building.

=== Thread Safety ===

All public log macros (CSILK_LOG_I, CSILK_LOG_E, etc.) acquire g_logger.mutex before writing. The thread-local request ID (tl_request_id) allows each thread to track its own request context without contention.

=== File Rotation ===

When max_file_size is set and the current file exceeds it, the logger renames <path> to <path>.1 (single-backup rotation) and opens a new file. Rotation happens inline during log write, protected by the mutex.

=== Log Levels ===

TRACE (0) - Most verbose, for debugging internals DEBUG (1) - Detailed information for developers INFO (2) - Normal operational messages (default) WARN (3) - Unexpected but handled situations ERROR (4) - Errors that don't stop the server FATAL (5) - Critical errors causing shutdown

The level filter is checked inside each log macro call before any formatting or I/O occurs, so disabled levels have near-zero overhead.


Data Structure Documentation

◆ csilk_log_entry_t

struct csilk_log_entry_t

Structured log entry type used for JSON-formatted log output.

All fields are fixed-size character arrays to avoid dynamic allocation. The type is registered with the reflection engine for automatic JSON serialization via csilk_json_marshal().

Data Fields
char file[64]

source filename

char func[64]

function name

char level[8]

TRACE/DEBUG/INFO/WARN/ERROR/FATAL

int32_t line

source line number

char msg[1024]

log message

char request_id[37]

unique request id

int64_t time_epoch

unix timestamp

◆ csilk_logger_t

struct csilk_logger_t

Internal logger singleton — holds configuration, file pointer, mutex, and current file size.

Collaboration diagram for csilk_logger_t:
Data Fields
csilk_log_config_t config

Logger configuration.

size_t current_size

Current log file size.

FILE * fp

Output file pointer.

int initialized

Whether logger is initialized.

uv_mutex_t mutex

Mutex for thread-safe logging.

Macro Definition Documentation

◆ LOG_ENTRY_MAP

#define LOG_ENTRY_MAP (   X)
Value:
X(csilk_log_entry_t, time_epoch, CSILK_TYPE_INT64, sizeof(int64_t), 0, false, NULL) \
X(csilk_log_entry_t, level, CSILK_TYPE_STRING, 8, 0, false, NULL) \
X(csilk_log_entry_t, request_id, CSILK_TYPE_STRING, 37, 0, false, NULL) \
X(csilk_log_entry_t, file, CSILK_TYPE_STRING, 64, 0, false, NULL) \
X(csilk_log_entry_t, line, CSILK_TYPE_INT32, sizeof(int32_t), 0, false, NULL) \
X(csilk_log_entry_t, func, CSILK_TYPE_STRING, 64, 0, false, NULL) \
X(csilk_log_entry_t, msg, CSILK_TYPE_STRING, 1024, 0, false, NULL)
Structured log entry type used for JSON-formatted log output.
Definition logger.c:66
@ CSILK_TYPE_INT64
Definition reflect.h:36
@ CSILK_TYPE_INT32
Definition reflect.h:34
@ CSILK_TYPE_STRING
Definition reflect.h:42

Function Documentation

◆ _csilk_log_internal()

void _csilk_log_internal ( csilk_log_level_t  lv,
const char *  file,
int  line,
const char *  func,
const char *  fmt,
  ... 
)

Internal: format and emit a log message to the global logger.

Internal log function (use macros instead).

Formats the variadic message via vsnprintf, acquires the logger mutex, checks file rotation (if file logging and max_file_size exceeded), and writes the entry as either JSON or plain text depending on configuration.

Parameters
lvLog severity level (filtered against g_logger.config.level).
fileSource file name (provided by CSILK_LOG_* macro).
lineSource line number (provided by CSILK_LOG_* macro).
funcFunction name (provided by CSILK_LOG_* macro).
fmtprintf-style format string.
...Variadic arguments for the format string.
Note
Use the CSILK_LOG_* macros (CSILK_LOG_I, CSILK_LOG_E, etc.) instead of calling this function directly. The macros automatically supply FILE, LINE, and func.

◆ _csilk_log_structured()

void _csilk_log_structured ( csilk_log_level_t  lv,
const char *  file,
int  line,
const char *  func,
cJSON *  extra,
const char *  fmt,
  ... 
)

Internal: emit a structured JSON log entry with extra key-value fields.

Log a structured JSON message with extra key-value fields.

Like _csilk_log_internal() but accepts an additional cJSON object of extra fields. In JSON mode, the extra fields are merged into the output. In text mode, the extra fields are discarded (cJSON_Delete is called).

Parameters
lvLog severity level.
fileSource file name.
lineSource line number.
funcFunction name.
extracJSON object of extra fields to include (ownership taken).
fmtprintf-style format string.
...Variadic arguments.
Note
Use the CSILK_LOG_KV macro instead of calling this directly.

◆ build_json_entry()

static cJSON * build_json_entry ( csilk_log_level_t  lv,
const char *  file,
int  line,
const char *  func,
const char *  msg,
int  msg_len 
)
static

Build a cJSON object from log entry fields using the reflection engine.

Populates a csilk_log_entry_t struct with the provided metadata, then serializes it to JSON via csilk_json_marshal() and parses the result back into a cJSON object. This round-trip is used to produce consistent JSON output that matches the registered reflection schema.

Parameters
lvLog level.
fileSource file name.
lineSource line number.
funcFunction name.
msgLog message content.
msg_lenMessage length (may truncate to fit the entry struct).
Returns
cJSON object ready for merging extra fields, or NULL on failure.
Note
The returned cJSON must be freed by the caller with cJSON_Delete().

◆ csilk_log_close()

void csilk_log_close ( void  )

Close the global logger and release resources.

Close the global logger.

Closes file handles and destroys mutexes. Safe to call multiple times.

◆ csilk_log_init()

int csilk_log_init ( csilk_log_config_t  config)

Initialize (or reinitialize) the global logger with the given configuration.

Initialize the global logger with config.

Configures the output destination (stdout if no file_path, or a file if set), the minimum log level, coloring (auto-detected for terminals when use_colors is -1), and whether to use structured JSON format. If the logger was previously initialized, csilk_log_close() is called first. A mutex is initialized for thread-safe operation.

Parameters
configLogger configuration struct with desired settings.
Returns
0 on success, -1 if the file could not be opened or mutex init fails.
Note
If file_path is NULL, output goes to stdout and max_file_size is effectively ignored (set to 0 internally).

◆ csilk_log_is_json()

int csilk_log_is_json ( void  )

Check whether the global logger is configured for structured JSON output.

Check whether the logger is in JSON format mode.

Returns
1 if the logger is initialized and json_format is enabled, 0 otherwise.
Note
Useful for handlers that want to produce consistent log output format matching the global setting.

◆ csilk_log_make_kv()

cJSON * csilk_log_make_kv ( const char *  key,
  ... 
)

Build a key-value cJSON object for structured logging.

Create a simple key-value cJSON object for structured logging.

Helper to create a flat JSON object from a NULL-terminated list of strings. Used primarily with CSILK_LOG_KV.

◆ csilk_log_set_request_id()

void csilk_log_set_request_id ( const char *  request_id)

Set the Request ID for the current thread.

Set the Request ID for the current thread (for log correlation).

Stores the request ID in thread-local storage, allowing subsequent log calls on the same thread to automatically include it without passing the context explicitly.

◆ log_json()

static int log_json ( csilk_log_level_t  lv,
const char *  file,
int  line,
const char *  func,
cJSON *  extra,
const char *  msg,
int  msg_len 
)
static

Format and write a structured JSON log line with optional extra fields.

Builds the base log entry via build_json_entry(), merges any extra cJSON fields (the extra object's children are duplicated into the root), serializes to a compact JSON string, and writes it to the output.

Parameters
lvLog level.
fileSource file name.
lineSource line number.
funcFunction name.
extraExtra cJSON object with additional key-value pairs to merge into the log entry. Ownership is taken (cJSON_Delete is called). May be NULL.
msgLog message content.
msg_lenMessage length.
Returns
Number of bytes written, or 0 on failure.

◆ log_text()

static int log_text ( csilk_log_level_t  lv,
const char *  file,
int  line,
const char *  func,
const char *  msg,
int  msg_len 
)
static

Format and write a human-readable plain-text log line.

Produces output like: "2024-01-15 10:30:00 INFO [file.c:42] function(): <request_id> message" ANSI color codes are added when use_colors is enabled. Thread-local request ID is appended if set via csilk_log_set_request_id().

Parameters
lvLog level enum (controls coloring/level label).
fileSource file name (only basename is used).
lineSource line number.
funcFunction name.
msgLog message content (not null-terminated).
msg_lenLength of the message content.
Returns
Number of bytes written to g_logger.fp.

◆ rotate_log_files()

static void rotate_log_files ( void  )
static

Rotate the current log file by renaming it with a ".1" suffix.

Closes the current file, renames "<path>" to "<path>.1", opens a new file at the original path in append mode, and resets the current_size counter. This is a simple single-backup rotation (not multi-generational).

Note
Only called when g_logger.config.max_file_size is exceeded.
Not thread-safe on its own; the caller must hold g_logger.mutex.

Variable Documentation

◆ g_logger

csilk_logger_t g_logger = {{0}, NULL, 0, {0}, 0}
static

◆ level_colors

const char* level_colors[]
static
Initial value:
= {
"\x1b[35m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[41;1m"}

◆ level_names

const char* level_names[] = {"TRACE", "DEBUG", "INFO ", "WARN ", "ERROR", "FATAL"}
static

◆ tl_request_id

_Thread_local char tl_request_id[37] = {0}
static