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