ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
json.c
Go to the documentation of this file.
1
7#include <ascii-chat/common.h>
13#include <yyjson.h>
14#include <stdint.h>
15#include <string.h>
16#include <time.h>
17
18/* ============================================================================
19 * Note: log_set_json_output() is implemented in logging.c where the g_log
20 * struct is defined.
21 * ============================================================================ */
22
23#include <unistd.h>
24#include <stdarg.h>
25
34static size_t format_timestamp_microseconds(uint64_t time_nanoseconds, char *buf, size_t buf_size) {
35 if (buf_size < 32) {
36 return 0;
37 }
38
39 /* Extract seconds and nanoseconds */
40 time_t seconds = (time_t)(time_nanoseconds / NS_PER_SEC_INT);
41 long nanoseconds = (long)(time_nanoseconds % NS_PER_SEC_INT);
42
43 /* Convert seconds to struct tm */
44 struct tm tm_info;
45 platform_localtime(&seconds, &tm_info);
46
47 /* Format the time part (HH:MM:SS) */
48 size_t len = strftime(buf, buf_size, "%H:%M:%S", &tm_info);
49 if (len == 0 || len >= buf_size) {
50 return 0;
51 }
52
53 /* Convert nanoseconds to microseconds for display */
54 long microseconds = nanoseconds / 1000;
55 if (microseconds < 0) {
56 microseconds = 0;
57 }
58 if (microseconds > 999999) {
59 microseconds = 999999;
60 }
61
62 /* Append microseconds */
63 int result = safe_snprintf(buf + len, buf_size - len, ".%06ld", microseconds);
64 if (result < 0 || result >= (int)(buf_size - len)) {
65 return 0;
66 }
67
68 return len + (size_t)result;
69}
70
77static const char *log_level_to_string(log_level_t level) {
78 switch (level) {
79 case LOG_DEBUG:
80 return "DEBUG";
81 case LOG_INFO:
82 return "INFO";
83 case LOG_WARN:
84 return "WARN";
85 case LOG_ERROR:
86 return "ERROR";
87 case LOG_FATAL:
88 return "FATAL";
89 default:
90 return "UNKNOWN";
91 }
92}
93
94void log_json_write(int fd, log_level_t level, uint64_t time_nanoseconds, const char *file, int line, const char *func,
95 const char *message) {
96 if (fd < 0 || !message) {
97 return;
98 }
99
100 /* Create root object */
101 yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
102 if (!doc) {
103 return;
104 }
105
106 yyjson_mut_val *root = yyjson_mut_obj(doc);
107 if (!root) {
108 yyjson_mut_doc_free(doc);
109 return;
110 }
111 yyjson_mut_doc_set_root(doc, root);
112
113 /* Create "header" object */
114 yyjson_mut_val *header = yyjson_mut_obj(doc);
115 if (header) {
116 yyjson_mut_obj_add_val(doc, root, "header", header);
117
118 /* Add timestamp (HH:MM:SS.microseconds) */
119 char timestamp_buf[32];
120 size_t ts_len = format_timestamp_microseconds(time_nanoseconds, timestamp_buf, sizeof(timestamp_buf));
121 if (ts_len > 0) {
122 yyjson_mut_obj_add_strncpy(doc, header, "timestamp", timestamp_buf, ts_len);
123 }
124
125 /* Add level */
126 yyjson_mut_obj_add_str(doc, header, "level", log_level_to_string(level));
127
128 /* Add thread ID */
130 yyjson_mut_obj_add_uint(doc, header, "tid", tid);
131
132 /* Add file (if not NULL) - normalize to project-relative path */
133 if (file) {
134 const char *rel_file = extract_project_relative_path(file);
135 yyjson_mut_obj_add_str(doc, header, "file", rel_file);
136 }
137
138 /* Add line (if > 0) */
139 if (line > 0) {
140 yyjson_mut_obj_add_int(doc, header, "line", line);
141 }
142
143 /* Add func (if not NULL) */
144 if (func) {
145 yyjson_mut_obj_add_str(doc, header, "func", func);
146 }
147 }
148
149 /* Create "body" object with message */
150 yyjson_mut_val *body = yyjson_mut_obj(doc);
151 if (body) {
152 yyjson_mut_obj_add_val(doc, root, "body", body);
153 yyjson_mut_obj_add_str(doc, body, "message", message);
154 }
155
156 /* Serialize to compact JSON (single line, no pretty-printing) */
157 size_t json_len = 0;
158 char *json_str = yyjson_mut_write(doc, 0, &json_len);
159
160 if (json_str && json_len > 0) {
161 /* Write JSON string */
162 platform_write(fd, (const uint8_t *)json_str, json_len);
163
164 /* Write newline for NDJSON format */
165 platform_write(fd, (const uint8_t *)"\n", 1);
166
167 /* Free serialized JSON string */
168 free(json_str);
169 }
170
171 /* Free document */
172 yyjson_mut_doc_free(doc);
173}
174
186static size_t json_escape_async_safe(const char *src, char *dest, size_t dest_size) {
187 if (!src || !dest || dest_size < 2) {
188 return 0;
189 }
190
191 size_t pos = 0;
192 for (const char *p = src; *p && pos < dest_size - 1; p++) {
193 char c = *p;
194 if (c == '"' || c == '\\') {
195 if (pos + 1 < dest_size - 1) {
196 dest[pos++] = '\\';
197 dest[pos++] = c;
198 } else {
199 break;
200 }
201 } else if (c == '\n') {
202 if (pos + 1 < dest_size - 1) {
203 dest[pos++] = '\\';
204 dest[pos++] = 'n';
205 } else {
206 break;
207 }
208 } else if (c == '\r') {
209 if (pos + 1 < dest_size - 1) {
210 dest[pos++] = '\\';
211 dest[pos++] = 'r';
212 } else {
213 break;
214 }
215 } else if (c == '\t') {
216 if (pos + 1 < dest_size - 1) {
217 dest[pos++] = '\\';
218 dest[pos++] = 't';
219 } else {
220 break;
221 }
222 } else if ((unsigned char)c < 32) {
223 /* Skip other control characters */
224 continue;
225 } else {
226 dest[pos++] = c;
227 }
228 }
229 dest[pos] = '\0';
230 return pos;
231}
232
250void log_json_async_safe(int fd, log_level_t level, const char *file, int line, const char *func, const char *message) {
251 if (fd < 0 || !message) {
252 return;
253 }
254
255 /* Format JSON manually on stack using only async-safe operations */
256 char json_buffer[2048];
257 char escaped_message[512];
258 char escaped_file[256];
259 char escaped_func[128];
260 char timestamp_buf[32];
261
262 /* Escape the strings (normalize file path to project-relative) */
263 json_escape_async_safe(message, escaped_message, sizeof(escaped_message));
264 if (file) {
265 const char *rel_file = extract_project_relative_path(file);
266 json_escape_async_safe(rel_file, escaped_file, sizeof(escaped_file));
267 } else {
268 escaped_file[0] = '\0';
269 }
270 json_escape_async_safe(func ? func : "", escaped_func, sizeof(escaped_func));
271
272 /* Format timestamp using only async-safe operations */
273 uint64_t time_ns = time_get_realtime_ns();
274 format_timestamp_microseconds(time_ns, timestamp_buf, sizeof(timestamp_buf));
275
276 /* Get thread ID (note: asciichat_thread_current_id is assumed to be async-safe) */
278
279 /* Format complete JSON object with timestamp */
280 int written = snprintf(json_buffer, sizeof(json_buffer),
281 "{\"header\":{\"timestamp\":\"%s\",\"level\":\"%s\",\"tid\":%lu,"
282 "\"file\":\"%s\",\"line\":%d,\"func\":\"%s\"},"
283 "\"body\":{\"message\":\"%s\"}}\n",
284 timestamp_buf, log_level_to_string(level), (unsigned long)tid, file ? escaped_file : "", line,
285 func ? escaped_func : "", escaped_message);
286
287 /* Write to fd if successful */
288 if (written > 0 && written < (int)sizeof(json_buffer)) {
289 platform_write_all(fd, (const uint8_t *)json_buffer, (size_t)written);
290 }
291}
Platform abstraction layer umbrella header providing unified cross-platform API.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
log_level_t
Logging levels enumeration.
Definition types.h:29
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
#define NS_PER_SEC_INT
Definition time.h:157
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
Definition system.c:224
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
Definition util.c:50
ssize_t platform_write(int fd, const void *buf, size_t count)
Safe file write (write replacement)
Definition wasm/system.c:72
uint64_t asciichat_thread_current_id(void)
Get the current thread's unique numeric ID.
Definition threading.c:92
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Definition path.c:456
📝 JSON structured logging output
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.
Definition json.c:94
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.
Definition json.c:250
📝 Logging API with multiple log levels and terminal output control
📂 Path Manipulation Utilities
⏱️ High-precision timing utilities using sokol_time.h and uthash
#define LOG_DEBUG
Definition types.h:39
#define LOG_FATAL
Definition types.h:43
#define LOG_ERROR
Definition types.h:42
#define LOG_WARN
Definition types.h:41
#define LOG_INFO
Definition types.h:40