Logging in C with support for monoid logging
logging
jsonl
monoid
c
1#include "clog.h"
2#include "dynarr.h"
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <time.h>
7
8static ClogFmt g_fmt = CLOG_FMT_JSONL;
9
10/// Set the global output format. Defaults to CLOG_FMT_JSONL.
11void clog_set_fmt(ClogFmt fmt) { g_fmt = fmt; }
12
13/* --- helpers ------------------------------------------------------------ */
14
15static const char *level_str(ClogLevel level) {
16 switch (level) {
17 case CLOG_INF: return "INF";
18 case CLOG_DBG: return "DBG";
19 case CLOG_WRN: return "WRN";
20 case CLOG_ERR: return "ERR";
21 default: return "???";
22 }
23}
24
25static const char *level_color(ClogLevel level) {
26 switch (level) {
27 case CLOG_INF: return "\033[36m"; /* cyan */
28 case CLOG_DBG: return "\033[37m"; /* white */
29 case CLOG_WRN: return "\033[33m"; /* yellow */
30 case CLOG_ERR: return "\033[1;31m"; /* bold red */
31 default: return "";
32 }
33}
34
35/// Fill ts[26] with the current local time in ANSI C format, newline stripped.
36static void get_ts(char ts[26]) {
37 time_t t = time(NULL);
38 const char *s = ctime(&t);
39 strncpy(ts, s, 25);
40 ts[25] = '\0';
41 if (ts[24] == '\n') ts[24] = '\0';
42}
43
44/// Write a JSON-escaped string to stdout without surrounding quotes.
45static void write_json_str(const char *s) {
46 for (; *s; s++) {
47 switch (*s) {
48 case '"': fputs("\\\"", stdout); break;
49 case '\\': fputs("\\\\", stdout); break;
50 case '\n': fputs("\\n", stdout); break;
51 case '\r': fputs("\\r", stdout); break;
52 case '\t': fputs("\\t", stdout); break;
53 default:
54 if ((unsigned char)*s < 0x20) {
55 printf("\\u%04x", (unsigned char)*s);
56 } else {
57 putchar(*s);
58 }
59 }
60 }
61}
62
63/// Print a single ClogEntry to stdout using the active format.
64static void print_entry(const ClogEntry *e) {
65 const char *lvl = level_str(e->level);
66 switch (g_fmt) {
67 case CLOG_FMT_JSONL:
68 printf("{\"ts\":\"");
69 write_json_str(e->ts);
70 printf("\",\"level\":\"%s\",\"file\":\"", lvl);
71 write_json_str(e->file);
72 printf("\",\"line\":%u,\"func\":\"", e->line);
73 write_json_str(e->func);
74 printf("\",\"msg\":\"");
75 write_json_str(e->msg);
76 printf("\"}\n");
77 break;
78 case CLOG_FMT_TEXT:
79 printf("%s:%u:%s %s %s %s\n", e->file, e->line, e->func, e->ts, lvl, e->msg);
80 break;
81 case CLOG_FMT_TEXT_COLOR:
82 printf("%s:%u:%s %s %s%s\033[0m %s\n", e->file, e->line, e->func, e->ts, level_color(e->level), lvl, e->msg);
83 break;
84 default:
85 fprintf(stderr, "Incorrect CLog format has been set: %d\n", g_fmt);
86 exit(EXIT_FAILURE);
87 }
88}
89
90/// Build a ClogEntry from the given fields, capturing the current timestamp.
91static ClogEntry make_entry(ClogLevel level, const char *file, unsigned int line, const char *func, const char *msg) {
92 ClogEntry e;
93 e.level = level;
94 get_ts(e.ts);
95 e.file = file;
96 e.line = line;
97 e.func = func;
98 strncpy(e.msg, msg, CLOG_MSG_MAX - 1);
99 e.msg[CLOG_MSG_MAX - 1] = '\0';
100 return e;
101}
102
103/* --- lifecycle ---------------------------------------------------------- */
104
105/// Allocate and return a new empty CLog (dynamic array of ClogEntry).
106CLog clog_log_init(void) {
107 return da_new(ClogEntry);
108}
109
110/// Free a CLog and all its entries.
111void clog_log_free(CLog log) {
112 da_free(log);
113}
114
115/// Print all entries in log to stdout using the active format.
116void clog_log_print(const CLog log) {
117 size_t n = da_length(log);
118 for (size_t i = 0; i < n; i++) {
119 print_entry(&log[i]);
120 }
121}
122
123/* --- write -------------------------------------------------------------- */
124
125/// Immediately print a log entry to stdout. Called via the clog() macro.
126void _clog_write_imm(ClogLevel level, const char *file, unsigned int line, const char *func, const char *msg) {
127 ClogEntry e = make_entry(level, file, line, func, msg);
128 print_entry(&e);
129}
130
131/// Append a log entry to a CLog. Called via the clogm() macro.
132void _clog_write_log(CLog *log, ClogLevel level, const char *file, unsigned int line, const char *func, const char *msg) {
133 ClogEntry e = make_entry(level, file, line, func, msg);
134 CLog arr = *log;
135 da_append(arr, e);
136 *log = arr;
137}