Logging in C with support for monoid logging
logging
jsonl
monoid
c
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h>
3#include <string.h>
4#include <unistd.h>
5#include "test.h"
6#include "clog.h"
7#include "dynarr.h"
8
9/* --- stdout capture helpers -------------------------------------------- */
10
11static FILE *capture_begin(int *saved_fd) {
12 FILE *tmp = tmpfile();
13 *saved_fd = dup(STDOUT_FILENO);
14 dup2(fileno(tmp), STDOUT_FILENO);
15 return tmp;
16}
17
18static void capture_end(FILE *tmp, int saved_fd, char *buf, size_t size) {
19 fflush(stdout);
20 dup2(saved_fd, STDOUT_FILENO);
21 close(saved_fd);
22 rewind(tmp);
23 if (fgets(buf, (int)size, tmp) == NULL) buf[0] = '\0';
24 fclose(tmp);
25}
26
27/* --- monoid tests ------------------------------------------------------- */
28
29static void test_init_empty(void) {
30 CLog log = clog_log_init();
31 ASSERT_NOT_NULL(log);
32 ASSERT_EQ(da_length(log), (size_t)0);
33 clog_log_free(log);
34}
35
36static void test_append_count(void) {
37 CLog log = clog_log_init();
38 clogm(CLOG_INF, log, "one");
39 clogm(CLOG_DBG, log, "two");
40 clogm(CLOG_ERR, log, "three");
41 ASSERT_EQ(da_length(log), (size_t)3);
42 clog_log_free(log);
43}
44
45static void test_entry_fields(void) {
46 CLog log = clog_log_init();
47 clogm(CLOG_WRN, log, "hello");
48 ClogEntry *e = &log[0];
49 ASSERT_EQ(e->level, CLOG_WRN);
50 ASSERT_STR_EQ(e->msg, "hello");
51 ASSERT_NOT_NULL(e->file);
52 ASSERT_NOT_NULL(e->func);
53 ASSERT(e->line > 0);
54 ASSERT(e->ts[0] != '\0');
55 clog_log_free(log);
56}
57
58static void test_msg_truncation(void) {
59 CLog log = clog_log_init();
60 char big[CLOG_MSG_MAX + 64];
61 memset(big, 'a', sizeof(big) - 1);
62 big[sizeof(big) - 1] = '\0';
63 clogm(CLOG_INF, log, big);
64 ASSERT_EQ(strlen(log[0].msg), (size_t)(CLOG_MSG_MAX - 1));
65 clog_log_free(log);
66}
67
68/* --- format tests ------------------------------------------------------- */
69
70static void test_jsonl_fields(void) {
71 clog_set_fmt(CLOG_FMT_JSONL);
72 int saved; char buf[4096];
73 FILE *tmp = capture_begin(&saved);
74 clog(CLOG_ERR, "boom");
75 capture_end(tmp, saved, buf, sizeof(buf));
76
77 ASSERT_NOT_NULL(strstr(buf, "\"level\":\"ERR\""));
78 ASSERT_NOT_NULL(strstr(buf, "\"msg\":\"boom\""));
79 ASSERT_NOT_NULL(strstr(buf, "\"ts\":\""));
80 ASSERT_NOT_NULL(strstr(buf, "\"file\":\""));
81 ASSERT_NOT_NULL(strstr(buf, "\"func\":\""));
82 clog_set_fmt(CLOG_FMT_JSONL);
83}
84
85static void test_jsonl_escaping(void) {
86 clog_set_fmt(CLOG_FMT_JSONL);
87 int saved; char buf[4096];
88 FILE *tmp = capture_begin(&saved);
89 clog(CLOG_INF, "say \"hello\"");
90 capture_end(tmp, saved, buf, sizeof(buf));
91
92 ASSERT_NOT_NULL(strstr(buf, "say \\\"hello\\\""));
93 clog_set_fmt(CLOG_FMT_JSONL);
94}
95
96static void test_text_fields(void) {
97 clog_set_fmt(CLOG_FMT_TEXT);
98 int saved; char buf[4096];
99 FILE *tmp = capture_begin(&saved);
100 clog(CLOG_INF, "hello world");
101 capture_end(tmp, saved, buf, sizeof(buf));
102
103 ASSERT_NOT_NULL(strstr(buf, "INF"));
104 ASSERT_NOT_NULL(strstr(buf, "hello world"));
105 clog_set_fmt(CLOG_FMT_JSONL);
106}
107
108static void test_print_smoke(void) {
109 clog_set_fmt(CLOG_FMT_JSONL);
110 CLog log = clog_log_init();
111 clogm(CLOG_INF, log, "a");
112 clogm(CLOG_WRN, log, "b");
113 int saved; char buf[4096];
114 FILE *tmp = capture_begin(&saved);
115 clog_log_print(log);
116 capture_end(tmp, saved, buf, sizeof(buf));
117
118 ASSERT_NOT_NULL(strstr(buf, "\"level\":\"INF\""));
119 clog_log_free(log);
120 clog_set_fmt(CLOG_FMT_JSONL);
121}
122
123/* ----------------------------------------------------------------------- */
124
125int main(void) {
126 test_init_empty();
127 test_append_count();
128 test_entry_fields();
129 test_msg_truncation();
130 test_jsonl_fields();
131 test_jsonl_escaping();
132 test_text_fields();
133 test_print_smoke();
134 printf("test_clog: all tests passed\n");
135 return 0;
136}