ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
log/format.c
Go to the documentation of this file.
1
14#include <ascii-chat/common.h>
15#include <ascii-chat/log/log.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <ctype.h>
21#include <time.h>
22
23/* ============================================================================
24 * Default Format String (mode-aware)
25 * ============================================================================ */
26
27/* ============================================================================
28 * Format Parser Implementation
29 * ============================================================================ */
30
44static log_template_t *parse_format_string(const char *format_str, bool console_only) {
45 if (!format_str) {
46 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid format string: NULL");
47 return NULL;
48 }
49
50 bool is_valid = utf8_is_valid(format_str);
51
52 if (!is_valid) {
53 SET_ERRNO(ERROR_INVALID_STATE, "Invalid UTF-8 in log format string");
54 return NULL;
55 }
56
57 // Use regular malloc/calloc instead of SAFE_* to avoid debug tracking overhead at startup
58 // This avoids mutex contention and memory tracking overhead during log initialization
59 log_template_t *result = (log_template_t *)calloc(1, sizeof(log_template_t));
60 if (!result) {
61 return NULL;
62 }
63
64 size_t format_len = strlen(format_str) + 1;
65 result->original = (char *)malloc(format_len);
66 if (!result->original) {
67 free(result);
68 return NULL;
69 }
70 asciichat_error_t strcpy_result = SAFE_STRCPY(result->original, format_len, format_str);
71 if (strcpy_result != ASCIICHAT_OK) {
72 log_error("Failed to copy format string: %s", asciichat_error_string(strcpy_result));
73 free(result->original);
74 free(result);
75 return NULL;
76 }
77 result->console_only = console_only;
78
79 /* Pre-allocate spec array (worst case: every char is a specifier) */
80 result->specs = (log_format_spec_t *)calloc(strlen(format_str) + 1, sizeof(log_format_spec_t));
81 if (!result->specs) {
82 free(result->original);
83 free(result);
84 return NULL;
85 }
86
87 const char *p = format_str;
88 size_t spec_idx = 0;
89
90 while (*p) {
91 /* Check for escape sequences first */
92 if (*p == '\\' && *(p + 1)) {
93 char next = *(p + 1);
94
95 if (next == 'n') {
96 /* \n - platform-aware newline */
97 result->specs[spec_idx].type = LOG_FORMAT_NEWLINE;
98 spec_idx++;
99 p += 2;
100 } else if (next == '\\') {
101 /* \\ - escaped backslash (output single \‍) */
102 result->specs[spec_idx].type = LOG_FORMAT_LITERAL;
103 result->specs[spec_idx].literal = (char *)malloc(2);
104 if (!result->specs[spec_idx].literal) {
105 goto cleanup;
106 }
107 result->specs[spec_idx].literal[0] = '\\';
108 result->specs[spec_idx].literal[1] = '\0';
109 result->specs[spec_idx].literal_len = 1;
110 spec_idx++;
111 p += 2;
112 } else {
113 /* Invalid escape sequence - treat backslash as literal */
114 result->specs[spec_idx].type = LOG_FORMAT_LITERAL;
115 result->specs[spec_idx].literal = (char *)malloc(2);
116 if (!result->specs[spec_idx].literal) {
117 goto cleanup;
118 }
119 result->specs[spec_idx].literal[0] = '\\';
120 result->specs[spec_idx].literal[1] = '\0';
121 result->specs[spec_idx].literal_len = 1;
122 spec_idx++;
123 p++;
124 }
125 } else if (*p == '%' && *(p + 1)) {
126 if (*(p + 1) == '%') {
127 /* %% - escaped percent (output single %) */
128 result->specs[spec_idx].type = LOG_FORMAT_LITERAL;
129 result->specs[spec_idx].literal = (char *)malloc(2);
130 if (!result->specs[spec_idx].literal) {
131 goto cleanup;
132 }
133 result->specs[spec_idx].literal[0] = '%';
134 result->specs[spec_idx].literal[1] = '\0';
135 result->specs[spec_idx].literal_len = 1;
136 spec_idx++;
137 p += 2;
138 } else {
139 /* Start of format specifier */
140 p++;
141
142 if (strncmp(p, "time(", 5) == 0) {
143 /* Parse %time(format) */
144 p += 5;
145 const char *fmt_start = p;
146 const char *fmt_end = strchr(p, ')');
147 if (!fmt_end) {
148 /* Parse error: unterminated time format */
149 log_error("Invalid %%time format: missing closing )");
150 goto cleanup;
151 }
152
153 result->specs[spec_idx].type = LOG_FORMAT_TIME;
154 size_t fmt_len = fmt_end - fmt_start;
155 result->specs[spec_idx].literal = (char *)malloc(fmt_len + 1);
156 if (!result->specs[spec_idx].literal) {
157 goto cleanup;
158 }
159 memcpy(result->specs[spec_idx].literal, fmt_start, fmt_len);
160 result->specs[spec_idx].literal[fmt_len] = '\0';
161 result->specs[spec_idx].literal_len = fmt_len;
162
163 spec_idx++;
164 p = fmt_end + 1;
165 } else if (strncmp(p, "level_aligned", 13) == 0) {
166 result->specs[spec_idx].type = LOG_FORMAT_LEVEL_ALIGNED;
167 spec_idx++;
168 p += 13;
169 } else if (strncmp(p, "level", 5) == 0) {
170 result->specs[spec_idx].type = LOG_FORMAT_LEVEL;
171 spec_idx++;
172 p += 5;
173 } else if (strncmp(p, "file_relative", 13) == 0) {
174 result->specs[spec_idx].type = LOG_FORMAT_FILE_RELATIVE;
175 spec_idx++;
176 p += 13;
177 } else if (strncmp(p, "file", 4) == 0) {
178 result->specs[spec_idx].type = LOG_FORMAT_FILE;
179 spec_idx++;
180 p += 4;
181 } else if (strncmp(p, "line", 4) == 0) {
182 result->specs[spec_idx].type = LOG_FORMAT_LINE;
183 spec_idx++;
184 p += 4;
185 } else if (strncmp(p, "func", 4) == 0) {
186 result->specs[spec_idx].type = LOG_FORMAT_FUNC;
187 spec_idx++;
188 p += 4;
189 } else if (strncmp(p, "tname", 5) == 0) {
190 result->specs[spec_idx].type = LOG_FORMAT_TNAME;
191 spec_idx++;
192 p += 5;
193 } else if (strncmp(p, "tid", 3) == 0) {
194 result->specs[spec_idx].type = LOG_FORMAT_TID;
195 spec_idx++;
196 p += 3;
197 } else if (strncmp(p, "colored_message", 15) == 0) {
198 result->specs[spec_idx].type = LOG_FORMAT_COLORED_MESSAGE;
199 spec_idx++;
200 p += 15;
201 } else if (strncmp(p, "ms", 2) == 0) {
202 result->specs[spec_idx].type = LOG_FORMAT_MICROSECONDS;
203 spec_idx++;
204 p += 2;
205 } else if (strncmp(p, "ns", 2) == 0) {
206 result->specs[spec_idx].type = LOG_FORMAT_NANOSECONDS;
207 spec_idx++;
208 p += 2;
209 } else if (strncmp(p, "message", 7) == 0) {
210 result->specs[spec_idx].type = LOG_FORMAT_MESSAGE;
211 spec_idx++;
212 p += 7;
213 } else if (strncmp(p, "colorlog_level_string_to_color", 30) == 0) {
214 result->specs[spec_idx].type = LOG_FORMAT_COLORLOG_LEVEL;
215 spec_idx++;
216 p += 30;
217 } else if (strncmp(p, "color(", 6) == 0) {
218 /* Parse %color(LEVEL, content) */
219 p += 6; /* Skip "color(" */
220
221 /* Find the matching closing paren */
222 int paren_depth = 1;
223 const char *color_start = p;
224 while (*p && paren_depth > 0) {
225 if (*p == '(')
226 paren_depth++;
227 else if (*p == ')')
228 paren_depth--;
229 if (paren_depth > 0)
230 p++;
231 }
232
233 if (!*p || paren_depth != 0) {
234 /* Parse error: unterminated color format */
235 log_error("Invalid %%color format: missing closing )");
236 goto cleanup;
237 }
238
239 size_t color_arg_len = p - color_start;
240
241 /* Store as "LEVEL|content" for later parsing */
242 if (result->specs) {
243 result->specs[spec_idx].type = LOG_FORMAT_COLOR;
244 result->specs[spec_idx].literal = (char *)malloc(color_arg_len + 1);
245 }
246
247 if (!result->specs[spec_idx].literal) {
248 goto cleanup;
249 }
250
251 if (result->specs) {
252 memcpy(result->specs[spec_idx].literal, color_start, color_arg_len);
253 result->specs[spec_idx].literal[color_arg_len] = '\0';
254 result->specs[spec_idx].literal_len = color_arg_len;
255 }
256
257 spec_idx++;
258 p++; /* Skip closing paren */
259 } else {
260 /* Unknown ascii-chat specifier - treat as strftime format code and pass to strftime
261 * This allows strftime formats like %H, %M, %S, %A, %B, etc. to work
262 * without needing custom parsing for each one. strftime will handle validation. */
263 const char *fmt_start = p;
264 size_t fmt_len = 0;
265
266 /* Collect the format code (usually 1-2 chars, but allow flexibility) */
267 while (*p && *p != '%' && *p != '\\' && fmt_len < 8) {
268 p++;
269 fmt_len++;
270 }
271
272 if (fmt_len == 0) {
273 log_error("Empty format specifier: %%");
274 goto cleanup;
275 }
276
277 result->specs[spec_idx].type = LOG_FORMAT_STRFTIME_CODE;
278 result->specs[spec_idx].literal = (char *)malloc(fmt_len + 1);
279 if (!result->specs[spec_idx].literal) {
280 goto cleanup;
281 }
282 memcpy(result->specs[spec_idx].literal, fmt_start, fmt_len);
283 result->specs[spec_idx].literal[fmt_len] = '\0';
284 result->specs[spec_idx].literal_len = fmt_len;
285
286 spec_idx++;
287 /* p already advanced in the while loop above */
288 }
289 }
290 } else {
291 /* Literal text until next escape or specifier */
292 const char *text_start = p;
293 while (*p && *p != '\\' && *p != '%') {
294 p++;
295 }
296
297 result->specs[spec_idx].type = LOG_FORMAT_LITERAL;
298 size_t text_len = p - text_start;
299 result->specs[spec_idx].literal = (char *)malloc(text_len + 1);
300 if (!result->specs[spec_idx].literal) {
301 goto cleanup;
302 }
303
304 /* Safe memcpy for UTF-8 literal text (already validated) */
305 memcpy(result->specs[spec_idx].literal, text_start, text_len);
306 result->specs[spec_idx].literal[text_len] = '\0';
307 result->specs[spec_idx].literal_len = text_len;
308 spec_idx++;
309 }
310 }
311
312 result->spec_count = spec_idx;
313 return result;
314
315cleanup:
316 log_template_free(result);
317 result = NULL;
318 return NULL;
319}
320
321log_template_t *log_template_parse(const char *format_str, bool console_only) {
322 return parse_format_string(format_str, console_only);
323}
324
326 if (!format) {
327 SET_ERRNO(ERROR_INVALID_PARAM, "null format");
328 return;
329 }
330
331 if (format->specs) {
332 for (size_t i = 0; i < format->spec_count; i++) {
333 if (format->specs[i].literal) {
334 free(format->specs[i].literal);
335 format->specs[i].literal = NULL;
336 }
337 }
338 free(format->specs);
339 format->specs = NULL;
340 }
341
342 if (format->original) {
343 free(format->original);
344 format->original = NULL;
345 }
346
347 free(format);
348 format = NULL;
349}
350
351/* ============================================================================
352 * Format Application Implementation
353 * ============================================================================ */
354
363// get_level_string_padded is declared in logging.h and defined in logging.c
364
371static const char *get_level_string(log_level_t level) {
372 switch (level) {
373 case LOG_DEV:
374 return "DEV";
375 case LOG_DEBUG:
376 return "DEBUG";
377 case LOG_INFO:
378 return "INFO";
379 case LOG_WARN:
380 return "WARN";
381 case LOG_ERROR:
382 return "ERROR";
383 case LOG_FATAL:
384 return "FATAL";
385 default:
386 return "";
387 }
388}
389
402static log_color_t parse_color_level(const char *level_name, log_level_t current_level) {
403 if (!level_name) {
404 return LOG_COLOR_RESET;
405 }
406
407 /* Special case: "*" means use the current log level */
408 if (strcmp(level_name, "*") == 0) {
409 switch (current_level) {
410 case LOG_DEV:
411 return LOG_COLOR_DEV;
412 case LOG_DEBUG:
413 return LOG_COLOR_DEBUG;
414 case LOG_INFO:
415 return LOG_COLOR_INFO;
416 case LOG_WARN:
417 return LOG_COLOR_WARN;
418 case LOG_ERROR:
419 return LOG_COLOR_ERROR;
420 case LOG_FATAL:
421 return LOG_COLOR_FATAL;
422 default:
423 return LOG_COLOR_RESET;
424 }
425 }
426
427 /* Log level names */
428 if (strcmp(level_name, "DEV") == 0) {
429 return LOG_COLOR_DEV;
430 } else if (strcmp(level_name, "DEBUG") == 0) {
431 return LOG_COLOR_DEBUG;
432 } else if (strcmp(level_name, "INFO") == 0) {
433 return LOG_COLOR_INFO;
434 } else if (strcmp(level_name, "WARN") == 0) {
435 return LOG_COLOR_WARN;
436 } else if (strcmp(level_name, "ERROR") == 0) {
437 return LOG_COLOR_ERROR;
438 } else if (strcmp(level_name, "FATAL") == 0) {
439 return LOG_COLOR_FATAL;
440 }
441
442 /* Literal color names */
443 if (strcmp(level_name, "GREY") == 0 || strcmp(level_name, "GRAY") == 0) {
444 return LOG_COLOR_GREY;
445 }
446
447 return LOG_COLOR_RESET;
448}
449
470static int render_format_content(const char *content, char *buf, size_t buf_size, log_level_t level,
471 const char *timestamp, const char *file, int line, const char *func, uint64_t tid,
472 const char *message, bool use_colors, uint64_t time_nanoseconds) {
473 if (!content || !buf || buf_size == 0) {
474 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments: content=%p, buf=%p, buf_size=%zu", content, buf, buf_size);
475 return -1;
476 }
477
478 /* Parse content as a format string and apply it */
479 log_template_t *content_format = log_template_parse(content, false);
480 if (!content_format) {
481 return -1;
482 }
483
484 int written = log_template_apply(content_format, buf, buf_size, level, timestamp, file, line, func, tid, message,
485 use_colors, time_nanoseconds);
486 log_template_free(content_format);
487
488 return written;
489}
490
491int log_template_apply(const log_template_t *format, char *buf, size_t buf_size, log_level_t level,
492 const char *timestamp, const char *file, int line, const char *func, uint64_t tid,
493 const char *message, bool use_colors, uint64_t time_nanoseconds) {
494 /* Check all critical pointers - format could be freed by another thread (TOCTOU race) */
495 if (!format || !buf || buf_size == 0) {
496 return -1;
497 }
498
499 int total_written = 0;
500 char *p = buf;
501 size_t remaining = buf_size - 1; /* Reserve space for null terminator */
502
503 (void)timestamp; /* May be used in future; for now, LOG_FORMAT_TIME uses custom formatting */
504
505 /* Safety check: verify format->specs is valid before dereferencing
506 * This protects against use-after-free if format is freed by another thread (TOCTOU race).
507 * Between our initial NULL check and here, the main thread may have freed this memory. */
508 if (!format || !format->specs || format->spec_count == 0) {
509 return -1;
510 }
511
512 for (size_t i = 0; i < format->spec_count; i++) {
513 /* Re-check specs pointer in loop to catch use-after-free during iteration */
514 if (!format->specs || i >= format->spec_count) {
515 break; /* Format was freed, stop processing */
516 }
517 const log_format_spec_t *spec = &format->specs[i];
518
519 /* Additional safety check: if spec is null or invalid, stop processing */
520 if (!spec) {
521 break;
522 }
523
524 int written = 0;
525
526 switch (spec->type) {
528 written = safe_snprintf(p, remaining + 1, "%s", spec->literal ? spec->literal : "");
529 break;
530
531 case LOG_FORMAT_TIME:
532 /* Format time using custom time formatter */
533 if (spec->literal) {
534 written = time_format_now(spec->literal, p, remaining + 1);
535 if (written <= 0) {
536 log_debug("time_format_now failed for format: %s", spec->literal);
537 written = 0;
538 }
539 }
540 break;
541
542 case LOG_FORMAT_LEVEL:
543 /* Log level as string (DEV, DEBUG, INFO, WARN, ERROR, FATAL) */
544 written = safe_snprintf(p, remaining + 1, "%s", get_level_string(level));
545 break;
546
548 /* Log level padded to exactly 5 characters (prevents truncation to [DEBU]/[ERRO]) */
549 written = safe_snprintf(p, remaining + 1, "%s", get_level_string_padded(level));
550 break;
551
552 case LOG_FORMAT_FILE:
553 if (file) {
554 written = safe_snprintf(p, remaining + 1, "%s", file);
555 }
556 break;
557
559 if (file) {
560 const char *rel_file = extract_project_relative_path(file);
561 written = safe_snprintf(p, remaining + 1, "%s", rel_file);
562 }
563 break;
564
565 case LOG_FORMAT_LINE:
566 if (line > 0) {
567 written = safe_snprintf(p, remaining + 1, "%d", line);
568 }
569 break;
570
571 case LOG_FORMAT_FUNC:
572 if (func) {
573 written = safe_snprintf(p, remaining + 1, "%s", func);
574 }
575 break;
576
577 case LOG_FORMAT_TID:
578 written = safe_snprintf(p, remaining + 1, "%llu", (unsigned long long)tid);
579 break;
580
581 case LOG_FORMAT_TNAME: {
583 uintptr_t key = asciichat_thread_to_key(thread);
584 const char *tname = named_get(key);
585 if (tname) {
586 written = safe_snprintf(p, remaining + 1, "%s", tname);
587 } else {
588 written = safe_snprintf(p, remaining + 1, "%llu", (unsigned long long)tid);
589 }
590 break;
591 }
592
594 /* Extract microseconds from nanoseconds (ns_value % 1_000_000_000 / 1000) */
595 long nanoseconds = (long)(time_nanoseconds % NS_PER_SEC_INT);
596 long microseconds = nanoseconds / 1000;
597 if (microseconds < 0)
598 microseconds = 0;
599 if (microseconds > 999999)
600 microseconds = 999999;
601 written = safe_snprintf(p, remaining + 1, "%06ld", microseconds);
602 break;
603 }
604
606 /* Extract nanoseconds component (ns_value % 1_000_000_000) */
607 long nanoseconds = (long)(time_nanoseconds % NS_PER_SEC_INT);
608 if (nanoseconds < 0)
609 nanoseconds = 0;
610 if (nanoseconds > 999999999)
611 nanoseconds = 999999999;
612 written = safe_snprintf(p, remaining + 1, "%09ld", nanoseconds);
613 break;
614 }
615
617 /* strftime format code (like %H, %M, %S, %A, %B, etc.)
618 * Let strftime handle all the parsing and validation */
619 if (spec->literal && spec->literal_len < 64) {
620 /* Construct format string with % prefix on stack */
621 char format_str[66]; /* 1 (%) + 64 (literal) + 1 (null) */
622 format_str[0] = '%';
623 memcpy(format_str + 1, spec->literal, spec->literal_len);
624 format_str[spec->literal_len + 1] = '\0';
625 written = time_format_now(format_str, p, remaining + 1);
626 if (written <= 0) {
627 log_debug("time_format_now failed for format code: %s", format_str);
628 written = 0;
629 }
630 }
631 break;
632 }
633
635 if (message) {
636 /* Format hex addresses as named object descriptions (type/name format) */
637 const char *formatted_msg = log_named_format_or_original(message);
638 written = safe_snprintf(p, remaining + 1, "%s", formatted_msg);
639 }
640 break;
641
643 /* Apply colorize_log_message() for number/unit/hex highlighting, then format named objects */
644 if (message) {
645 const char *formatted_msg = log_named_format_or_original(message);
646 const char *colorized_msg = colorize_log_message(formatted_msg);
647 written = safe_snprintf(p, remaining + 1, "%s", colorized_msg);
648 }
649 break;
650 }
651
653 /* Color code for the log level (placeholder for future color support) */
654 (void)use_colors; /* Suppress unused parameter warning */
655 written = 0;
656 break;
657
658 case LOG_FORMAT_COLOR: {
659 /* Parse and apply %color(LEVEL, content)
660 * literal stores "LEVEL,content" where LEVEL is the color level name (or "*" for current level)
661 * and content is a format string to render and colorize */
662 if (!spec->literal || spec->literal_len == 0) {
663 written = 0;
664 break;
665 }
666
667 /* Find the comma separating LEVEL and content */
668 const char *comma_pos = strchr(spec->literal, ',');
669 if (!comma_pos) {
670 /* Invalid format - no comma found */
671 log_debug("log_template_apply: %%color format missing comma in: %s", spec->literal);
672 written = 0;
673 break;
674 }
675
676 /* Extract level name (before comma) */
677 size_t level_len = comma_pos - spec->literal;
678 char level_name[32];
679 if (level_len >= sizeof(level_name)) {
680 /* Level name too long */
681 written = 0;
682 break;
683 }
684 memcpy(level_name, spec->literal, level_len);
685 level_name[level_len] = '\0';
686
687 /* Parse level name to log_color_t (pass current level for "*" support) */
688 log_color_t color = parse_color_level(level_name, level);
689
690 /* Extract content (after comma), skip leading whitespace */
691 const char *content_start = comma_pos + 1;
692 while (*content_start == ' ' || *content_start == '\t') {
693 content_start++;
694 }
695
696 /* Render content recursively */
697 char content_buf[512];
698 int content_len = render_format_content(content_start, content_buf, sizeof(content_buf), level, timestamp, file,
699 line, func, tid, message, use_colors, time_nanoseconds);
700
701 if (content_len < 0 || content_len >= (int)sizeof(content_buf)) {
702 written = 0;
703 break;
704 }
705
706 /* Apply color to rendered content using colored_string */
707 const char *colored_content = colored_string(color, content_buf);
708
709 /* Copy colored content to output buffer */
710 written = safe_snprintf(p, remaining + 1, "%s", colored_content);
711 break;
712 }
713
715 /* Platform-aware newline */
716#ifdef _WIN32
717 written = safe_snprintf(p, remaining + 1, "\r\n");
718#else
719 written = safe_snprintf(p, remaining + 1, "\n");
720#endif
721 break;
722
723 default:
724 break;
725 }
726
727 if (written < 0) {
728 /* snprintf error */
729 return -1;
730 }
731
732 if ((size_t)written > remaining) {
733 /* Buffer overflow prevention */
734 log_debug("log_template_apply: buffer overflow prevented");
735 return -1;
736 }
737
738 p += written;
739 remaining -= written;
740 total_written += written;
741 }
742
743 *p = '\0';
744 return total_written;
745}
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
Definition colorize.c:509
Log message colorization for terminal output.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
unsigned long long uint64_t
Definition common.h:59
const char * named_get(uintptr_t key)
Look up the registered name for a resource.
uintptr_t asciichat_thread_to_key(asciichat_thread_t thread)
Convert a thread handle to a registry key.
Definition threading.c:87
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
int log_template_apply(const log_template_t *format, char *buf, size_t buf_size, log_level_t level, const char *timestamp, const char *file, int line, const char *func, uint64_t tid, const char *message, bool use_colors, uint64_t time_nanoseconds)
Apply format to a log entry and write result to buffer.
Definition log/format.c:491
const char * log_named_format_or_original(const char *message)
Format named objects in a message (thread-local buffer)
Definition log/named.c:913
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:130
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
log_level_t
Logging levels enumeration.
Definition types.h:29
void log_template_free(log_template_t *format)
Free compiled format structure.
Definition log/format.c:325
log_template_t * log_template_parse(const char *format_str, bool console_only)
Parse a format string into compiled format structure.
Definition log/format.c:321
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
@ LOG_COLOR_RESET
Definition log/log.h:138
@ LOG_COLOR_GREY
Definition log/log.h:137
@ LOG_COLOR_FATAL
Definition log/log.h:136
@ LOG_COLOR_ERROR
Definition log/log.h:135
@ LOG_COLOR_DEV
Definition log/log.h:131
@ LOG_COLOR_DEBUG
Definition log/log.h:132
@ LOG_COLOR_INFO
Definition log/log.h:133
@ LOG_COLOR_WARN
Definition log/log.h:134
#define NS_PER_SEC_INT
Definition time.h:157
int time_format_now(const char *format_str, char *buf, size_t buf_size)
Format current time using strftime format string.
Definition util/time.c:786
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
bool utf8_is_valid(const char *str)
Check if a string is valid UTF-8.
Definition utf8.c:156
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Definition path.c:456
void * asciichat_thread_t
@ LOG_FORMAT_LEVEL
Definition log/format.h:28
@ LOG_FORMAT_COLOR
Definition log/format.h:38
@ LOG_FORMAT_NEWLINE
Definition log/format.h:43
@ LOG_FORMAT_FILE_RELATIVE
Definition log/format.h:31
@ LOG_FORMAT_LINE
Definition log/format.h:32
@ LOG_FORMAT_MICROSECONDS
Definition log/format.h:40
@ LOG_FORMAT_TID
Definition log/format.h:34
@ LOG_FORMAT_COLORED_MESSAGE
Definition log/format.h:39
@ LOG_FORMAT_LITERAL
Definition log/format.h:26
@ LOG_FORMAT_COLORLOG_LEVEL
Definition log/format.h:37
@ LOG_FORMAT_MESSAGE
Definition log/format.h:36
@ LOG_FORMAT_TIME
Definition log/format.h:27
@ LOG_FORMAT_LEVEL_ALIGNED
Definition log/format.h:29
@ LOG_FORMAT_FUNC
Definition log/format.h:33
@ LOG_FORMAT_NANOSECONDS
Definition log/format.h:41
@ LOG_FORMAT_TNAME
Definition log/format.h:35
@ LOG_FORMAT_STRFTIME_CODE
Definition log/format.h:42
@ LOG_FORMAT_FILE
Definition log/format.h:30
const char * get_level_string_padded(log_level_t level)
Get padded level string for consistent alignment.
Definition log/log.c:149
📝 Logging API with multiple log levels and terminal output control
Format named objects in log messages (replaces hex addresses with type/name descriptions)
📂 Path Manipulation Utilities
A single parsed format specifier.
Definition log/format.h:53
log_template_type_t type
Definition log/format.h:54
Compiled log format ready for use in log_template_apply()
Definition log/format.h:66
char * original
Definition log/format.h:69
log_format_spec_t * specs
Definition log/format.h:67
size_t spec_count
Definition log/format.h:68
⏱️ High-precision timing utilities using sokol_time.h and uthash
#define LOG_DEV
Definition types.h:38
#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
🔤 UTF-8 Encoding and Decoding Utilities
🔤 String Manipulation and Shell Escaping Utilities