ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
json.h File Reference

📝 JSON structured logging output More...

Go to the source code of this file.

Functions

void log_json_write (int fd, log_level_t level, uint64_t time_nanoseconds, const char *file, int line, const char *func, const char *message)
 Write a log entry as a JSON object to the json output fd.
void log_set_json_output (int fd)
 Set the JSON output file descriptor.
void log_json_async_safe (int fd, log_level_t level, const char *file, int line, const char *func, const char *message)
 Async-safe JSON logging for signal handlers.

Detailed Description

📝 JSON structured logging output

Definition in file include/ascii-chat/log/json.h.

Function Documentation

◆ log_json_async_safe()

void log_json_async_safe ( int fd,
log_level_t level,
const char * file,
int line,
const char * func,
const char * message )

Async-safe JSON logging for signal handlers.

This function formats and writes JSON logs using ONLY async-safe operations:

  • snprintf for string formatting
  • write() for output
  • No allocations, no locks, no library calls

Suitable for calling from signal handlers (SIGTERM, SIGINT, etc.)

Parameters
fdFile descriptor to write to (typically STDOUT_FILENO or STDERR_FILENO)
levelLog level (DEBUG, INFO, WARN, ERROR, FATAL)
fileSource file name (can be NULL)
lineSource line number (can be 0)
funcFunction name (can be NULL)
messageLog message (must not be NULL)

This function formats and writes JSON logs using ONLY async-safe operations:

  • snprintf for string formatting
  • write() for output
  • No allocations, no locks, no library calls

Suitable for calling from signal handlers (SIGTERM, SIGINT, etc.)

Parameters
fdFile descriptor to write to (typically STDOUT_FILENO or STDERR_FILENO)
levelLog level
fileSource file name (may be NULL)
lineSource line number
funcFunction name (may be NULL)
messageLog message (must not be NULL)

Definition at line 273 of file log/json.c.

273 {
274 if (fd < 0 || !message) {
275 return;
276 }
277
278 /* Format JSON manually on stack using only async-safe operations */
279 char json_buffer[LOG_JSON_JSON_BUFFER_SIZE];
280 char escaped_message[LOG_JSON_ESCAPED_MESSAGE_BUFFER_SIZE];
281 char escaped_file[LOG_JSON_ESCAPED_FILE_BUFFER_SIZE];
282 char escaped_func[LOG_JSON_ESCAPED_FUNC_BUFFER_SIZE];
283 char timestamp_buf[LOG_TIMESTAMP_BUFFER_SIZE];
284
285 /* Escape the strings (normalize file path to project-relative) */
286 json_escape_async_safe(message, escaped_message, sizeof(escaped_message));
287 if (file) {
288 const char *rel_file = extract_project_relative_path(file, ASCIICHAT_OWNED_SOURCE_DIRS);
289 json_escape_async_safe(rel_file, escaped_file, sizeof(escaped_file));
290 } else {
291 escaped_file[0] = '\0';
292 }
293 json_escape_async_safe(func ? func : "", escaped_func, sizeof(escaped_func));
294
295 /* Format timestamp using only async-safe operations */
296 uint64_t time_ns = time_get_realtime_ns();
297 format_timestamp_microseconds(time_ns, timestamp_buf, sizeof(timestamp_buf));
298
299 /* Get thread ID (note: thread_current_id is assumed to be async-safe) */
300 uint64_t tid = (uint64_t)thread_current_id();
301
302 /* Format complete JSON object with timestamp */
303 int written = snprintf(json_buffer, sizeof(json_buffer),
304 "{\"header\":{\"timestamp\":\"%s\",\"level\":\"%s\",\"tid\":%lu,"
305 "\"file\":\"%s\",\"line\":%d,\"func\":\"%s\"},"
306 "\"body\":{\"message\":\"%s\"}}\n",
307 timestamp_buf, log_level_to_string(level), (unsigned long)tid, file ? escaped_file : "", line,
308 func ? escaped_func : "", escaped_message);
309
310 /* Write to fd if successful */
311 if (written > 0 && written < (int)sizeof(json_buffer)) {
312 platform_write_all(fd, (const uint8_t *)json_buffer, (size_t)written);
313 }
314}
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
Definition log/log.h:101
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
const char * extract_project_relative_path(const char *file, const char *const *owned_dirs)
Extract a path relative to the source root embedded by CMake.
#define LOG_JSON_JSON_BUFFER_SIZE
Definition log/json.c:8
#define LOG_JSON_ESCAPED_MESSAGE_BUFFER_SIZE
Definition log/json.c:9
#define LOG_JSON_ESCAPED_FILE_BUFFER_SIZE
Definition log/json.c:10
#define LOG_JSON_ESCAPED_FUNC_BUFFER_SIZE
Definition log/json.c:11

References extract_project_relative_path(), LOG_JSON_ESCAPED_FILE_BUFFER_SIZE, LOG_JSON_ESCAPED_FUNC_BUFFER_SIZE, LOG_JSON_ESCAPED_MESSAGE_BUFFER_SIZE, LOG_JSON_JSON_BUFFER_SIZE, LOG_TIMESTAMP_BUFFER_SIZE, and platform_write_all().

Referenced by log_console_impl().

◆ log_json_write()

void log_json_write ( int fd,
log_level_t level,
uint64_t time_nanoseconds,
const char * file,
int line,
const char * func,
const char * message )

Write a log entry as a JSON object to the json output fd.

Outputs a newline-delimited JSON (NDJSON) format log entry with the following structure:

{
"header": {
"timestamp": "14:30:45.123456",
"level": "INFO",
"tid": 12345,
"file": "lib/network/server.c",
"line": 42,
"func": "handle_client"
},
"body": {
"message": "Client connected"
}
}

The file, line, and func fields are omitted from the output when they are NULL/0 (which typically occurs in release builds).

Parameters
fdFile descriptor to write JSON to (-1 = disabled)
levelLog level (DEBUG, INFO, WARN, ERROR, FATAL)
time_nanosecondsCurrent time in nanoseconds (used to format timestamp)
fileSource file name (can be NULL)
lineLine number (can be 0)
funcFunction name (can be NULL)
messageFormatted log message
See also
log_set_json_output() - Set the JSON output file descriptor

Definition at line 104 of file log/json.c.

105 {
106 if (fd < 0 || !message) {
107 return;
108 }
109
110 /* Create root object */
111 yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
112 if (!doc) {
113 return;
114 }
115
116 yyjson_mut_val *root = yyjson_mut_obj(doc);
117 if (!root) {
118 yyjson_mut_doc_free(doc);
119 return;
120 }
121 yyjson_mut_doc_set_root(doc, root);
122
123 /* Create "header" object */
124 yyjson_mut_val *header = yyjson_mut_obj(doc);
125 if (header) {
126 yyjson_mut_obj_add_val(doc, root, "header", header);
127
128 /* Add timestamp (HH:MM:SS.microseconds) */
129 char timestamp_buf[LOG_TIMESTAMP_BUFFER_SIZE];
130 size_t ts_len = format_timestamp_microseconds(time_nanoseconds, timestamp_buf, sizeof(timestamp_buf));
131 if (ts_len > 0) {
132 yyjson_mut_obj_add_strncpy(doc, header, "timestamp", timestamp_buf, ts_len);
133 }
134
135 /* Add level */
136 yyjson_mut_obj_add_str(doc, header, "level", log_level_to_string(level));
137
138 /* Add thread ID */
139 uint64_t tid = (uint64_t)thread_current_id();
140 yyjson_mut_obj_add_uint(doc, header, "tid", tid);
141
142 /* Add file (if not NULL) - normalize to project-relative path */
143 if (file) {
144 const char *rel_file = extract_project_relative_path(file, ASCIICHAT_OWNED_SOURCE_DIRS);
145 yyjson_mut_obj_add_str(doc, header, "file", rel_file);
146 }
147
148 /* Add line (if > 0) */
149 if (line > 0) {
150 yyjson_mut_obj_add_int(doc, header, "line", line);
151 }
152
153 /* Add func (if not NULL) */
154 if (func) {
155 yyjson_mut_obj_add_str(doc, header, "func", func);
156 }
157 }
158
159 /* Create "body" object with message */
160 yyjson_mut_val *body = yyjson_mut_obj(doc);
161 if (body) {
162 yyjson_mut_obj_add_val(doc, root, "body", body);
163 yyjson_mut_obj_add_str(doc, body, "message", message);
164 }
165
166 /* Serialize to compact JSON (single line, no pretty-printing) */
167 size_t json_len = 0;
168 char *json_str = yyjson_mut_write(doc, 0, &json_len);
169
170 if (json_str && json_len > 0) {
171 // Include the delimiter in the same write so concurrent records stay on separate lines.
172 json_str[json_len] = '\n';
173 platform_write_all(fd, (const uint8_t *)json_str, json_len + 1);
174
175 /* Free serialized JSON string */
176 free(json_str);
177 }
178
179 /* Free document */
180 yyjson_mut_doc_free(doc);
181}

References extract_project_relative_path(), LOG_TIMESTAMP_BUFFER_SIZE, and platform_write_all().

Referenced by log_msg(), and log_plain_msg().

◆ log_set_json_output()

void log_set_json_output ( int fd)

Set the JSON output file descriptor.

Sets which file descriptor should receive JSON-formatted logs. Pass -1 to disable JSON output.

Parameters
fdFile descriptor (typically STDERR_FILENO for terminal, or an open log file)
Note
This function is thread-safe (uses atomic operations)

Definition at line 775 of file log.c.

775 {
776 atomic_store_int(&g_log.json_file, fd);
777}

Referenced by main().