2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * ***** END GPL LICENSE BLOCK *****
31 /* For 'isatty' to check for color. */
32 #if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
39 /* Only other dependency (could use regular malloc too). */
40 #include "MEM_guardedalloc.h"
45 /* Local utility defines */
46 #define STREQ(a, b) (strcmp(a, b) == 0)
47 #define STREQLEN(a, b, n) (strncmp(a, b, n) == 0)
50 # define PATHSEP_CHAR '\\'
52 # define PATHSEP_CHAR '/'
55 /* -------------------------------------------------------------------- */
56 /** \name Internal Types
59 typedef struct CLG_IDFilter {
60 struct CLG_IDFilter *next;
65 typedef struct CLogContext {
66 /** Single linked list of types. */
68 /* exclude, include filters. */
69 CLG_IDFilter *filters[2];
73 /** Borrowed, not owned. */
83 void (*fatal_fn)(void *file_handle);
84 void (*backtrace_fn)(void *file_handle);
90 /* -------------------------------------------------------------------- */
91 /** \name Mini Buffer Functionality
93 * Use so we can do a single call to write.
96 #define CLOG_BUF_LEN_INIT 512
98 typedef struct CLogStringBuf {
105 static void clg_str_init(CLogStringBuf *cstr, char *buf_stack, uint buf_stack_len)
107 cstr->data = buf_stack;
108 cstr->len_alloc = buf_stack_len;
110 cstr->is_alloc = false;
113 static void clg_str_free(CLogStringBuf *cstr)
115 if (cstr->is_alloc) {
116 MEM_freeN(cstr->data);
120 static void clg_str_reserve(CLogStringBuf *cstr, const uint len)
122 if (len > cstr->len_alloc) {
123 cstr->len_alloc *= 2;
124 if (len > cstr->len_alloc) {
125 cstr->len_alloc = len;
128 if (cstr->is_alloc) {
129 cstr->data = MEM_reallocN(cstr->data, cstr->len_alloc);
132 /* Copy the static buffer. */
133 char *data = MEM_mallocN(cstr->len_alloc, __func__);
134 memcpy(data, cstr->data, cstr->len);
136 cstr->is_alloc = true;
138 cstr->len_alloc = len;
142 static void clg_str_append_with_len(CLogStringBuf *cstr, const char *str, const uint len)
144 uint len_next = cstr->len + len;
145 clg_str_reserve(cstr, len_next);
146 char *str_dst = cstr->data + cstr->len;
147 memcpy(str_dst, str, len);
151 cstr->len = len_next;
154 static void clg_str_append(CLogStringBuf *cstr, const char *str)
156 clg_str_append_with_len(cstr, str, strlen(str));
159 static void clg_str_vappendf(CLogStringBuf *cstr, const char *fmt, va_list args)
161 /* Use limit because windows may use '-1' for a formatting error. */
162 const uint len_max = 65535;
163 uint len_avail = (cstr->len_alloc - cstr->len);
164 if (len_avail == 0) {
165 len_avail = CLOG_BUF_LEN_INIT;
166 clg_str_reserve(cstr, len_avail);
170 va_copy(args_cpy, args);
171 int retval = vsnprintf(cstr->data + cstr->len, len_avail, fmt, args_cpy);
179 if (len_avail >= len_max) {
182 clg_str_reserve(cstr, len_avail);
189 /* -------------------------------------------------------------------- */
190 /** \name Internal Utilities
201 #define COLOR_LEN (COLOR_RESET + 1)
203 static const char *clg_color_table[COLOR_LEN] = {NULL};
205 static void clg_color_table_init(bool use_color)
207 for (int i = 0; i < COLOR_LEN; i++) {
208 clg_color_table[i] = "";
214 clg_color_table[COLOR_DEFAULT] = "\033[1;37m";
215 clg_color_table[COLOR_RED] = "\033[1;31m";
216 clg_color_table[COLOR_GREEN] = "\033[1;32m";
217 clg_color_table[COLOR_YELLOW] = "\033[1;33m";
218 clg_color_table[COLOR_RESET] = "\033[0m";
223 static const char *clg_severity_str[CLG_SEVERITY_LEN] = {
224 [CLG_SEVERITY_INFO] = "INFO",
225 [CLG_SEVERITY_WARN] = "WARN",
226 [CLG_SEVERITY_ERROR] = "ERROR",
227 [CLG_SEVERITY_FATAL] = "FATAL",
230 static const char *clg_severity_as_text(enum CLG_Severity severity)
232 bool ok = (unsigned int)severity < CLG_SEVERITY_LEN;
235 return clg_severity_str[severity];
238 return "INVALID_SEVERITY";
242 static enum eCLogColor clg_severity_to_color(enum CLG_Severity severity)
244 assert((unsigned int)severity < CLG_SEVERITY_LEN);
245 enum eCLogColor color = COLOR_DEFAULT;
247 case CLG_SEVERITY_INFO:
248 color = COLOR_DEFAULT;
250 case CLG_SEVERITY_WARN:
251 color = COLOR_YELLOW;
253 case CLG_SEVERITY_ERROR:
254 case CLG_SEVERITY_FATAL:
258 /* should never get here. */
266 /* -------------------------------------------------------------------- */
267 /** \name Context Type Access
271 * Filter the indentifier based on very basic globbing.
272 * - `foo` exact match of `foo`.
273 * - `foo.bar` exact match for `foo.bar`
274 * - `foo.*` match for `foo` & `foo.bar` & `foo.bar.baz`
275 * - `*` matches everything.
277 static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
279 const int identifier_len = strlen(identifier);
280 for (uint i = 0; i < 2; i++) {
281 const CLG_IDFilter *flt = ctx->filters[i];
282 while (flt != NULL) {
283 const int len = strlen(flt->match);
284 if (STREQ(flt->match, "*") ||
285 ((len == identifier_len) && (STREQ(identifier, flt->match))))
289 if ((len >= 2) && (STREQLEN(".*", &flt->match[len - 2], 2))) {
290 if (((identifier_len == len - 2) && STREQLEN(identifier, flt->match, len - 2)) ||
291 ((identifier_len >= len - 1) && STREQLEN(identifier, flt->match, len - 1)))
303 * \note This should never be called per logging call.
304 * Searching is only to get an initial handle.
306 static CLG_LogType *clg_ctx_type_find_by_name(CLogContext *ctx, const char *identifier)
308 for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
309 if (STREQ(identifier, ty->identifier)) {
316 static CLG_LogType *clg_ctx_type_register(CLogContext *ctx, const char *identifier)
318 assert(clg_ctx_type_find_by_name(ctx, identifier) == NULL);
319 CLG_LogType *ty = MEM_callocN(sizeof(*ty), __func__);
320 ty->next = ctx->types;
322 strncpy(ty->identifier, identifier, sizeof(ty->identifier) - 1);
324 ty->level = ctx->default_type.level;
326 if (clg_ctx_filter_check(ctx, ty->identifier)) {
327 ty->flag |= CLG_FLAG_USE;
332 static void clg_ctx_fatal_action(CLogContext *ctx)
334 if (ctx->callbacks.fatal_fn != NULL) {
335 ctx->callbacks.fatal_fn(ctx->output_file);
337 fflush(ctx->output_file);
341 static void clg_ctx_backtrace(CLogContext *ctx)
343 /* Note: we avoid writing fo 'FILE', for backtrace we make an exception,
344 * if necessary we could have a version of the callback that writes to file descriptor all at once. */
345 ctx->callbacks.backtrace_fn(ctx->output_file);
346 fflush(ctx->output_file);
351 /* -------------------------------------------------------------------- */
352 /** \name Logging API
355 static void write_severity(CLogStringBuf *cstr, enum CLG_Severity severity, bool use_color)
357 assert((unsigned int)severity < CLG_SEVERITY_LEN);
359 enum eCLogColor color = clg_severity_to_color(severity);
360 clg_str_append(cstr, clg_color_table[color]);
361 clg_str_append(cstr, clg_severity_as_text(severity));
362 clg_str_append(cstr, clg_color_table[COLOR_RESET]);
365 clg_str_append(cstr, clg_severity_as_text(severity));
369 static void write_type(CLogStringBuf *cstr, CLG_LogType *lg)
371 clg_str_append(cstr, " (");
372 clg_str_append(cstr, lg->identifier);
373 clg_str_append(cstr, "): ");
376 static void write_file_line_fn(CLogStringBuf *cstr, const char *file_line, const char *fn, const bool use_basename)
378 uint file_line_len = strlen(file_line);
380 uint file_line_offset = file_line_len;
381 while (file_line_offset-- > 0) {
382 if (file_line[file_line_offset] == PATHSEP_CHAR) {
387 file_line += file_line_offset;
388 file_line_len -= file_line_offset;
390 clg_str_append_with_len(cstr, file_line, file_line_len);
393 clg_str_append(cstr, " ");
394 clg_str_append(cstr, fn);
395 clg_str_append(cstr, ": ");
399 CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
403 char cstr_stack_buf[CLOG_BUF_LEN_INIT];
404 clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
406 write_severity(&cstr, severity, lg->ctx->use_color);
407 write_type(&cstr, lg);
410 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
411 clg_str_append(&cstr, message);
413 clg_str_append(&cstr, "\n");
415 /* could be optional */
416 int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
421 if (lg->ctx->callbacks.backtrace_fn) {
422 clg_ctx_backtrace(lg->ctx);
425 if (severity == CLG_SEVERITY_FATAL) {
426 clg_ctx_fatal_action(lg->ctx);
431 CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
432 const char *fmt, ...)
435 char cstr_stack_buf[CLOG_BUF_LEN_INIT];
436 clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
438 write_severity(&cstr, severity, lg->ctx->use_color);
439 write_type(&cstr, lg);
442 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
446 clg_str_vappendf(&cstr, fmt, ap);
449 clg_str_append(&cstr, "\n");
451 /* could be optional */
452 int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
457 if (lg->ctx->callbacks.backtrace_fn) {
458 clg_ctx_backtrace(lg->ctx);
461 if (severity == CLG_SEVERITY_FATAL) {
462 clg_ctx_fatal_action(lg->ctx);
468 /* -------------------------------------------------------------------- */
469 /** \name Logging Context API
472 static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
474 ctx->output_file = file_handle;
475 ctx->output = fileno(ctx->output_file);
476 #if defined(__unix__) || defined(__APPLE__)
477 ctx->use_color = isatty(ctx->output);
481 static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
483 ctx->use_basename = (bool)value;
486 /** Action on fatal severity. */
487 static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
489 ctx->callbacks.fatal_fn = fatal_fn;
492 static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
494 ctx->callbacks.backtrace_fn = backtrace_fn;
497 static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list, const char *type_match, int type_match_len)
499 if (type_match_len == 0) {
502 CLG_IDFilter *flt = MEM_callocN(sizeof(*flt) + (type_match_len + 1), __func__);
503 flt->next = *flt_list;
505 memcpy(flt->match, type_match, type_match_len);
506 /* no need to null terminate since we calloc'd */
509 static void CLG_ctx_type_filter_exclude(CLogContext *ctx, const char *type_match, int type_match_len)
511 clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
514 static void CLG_ctx_type_filter_include(CLogContext *ctx, const char *type_match, int type_match_len)
516 clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
519 static void CLG_ctx_level_set(CLogContext *ctx, int level)
521 ctx->default_type.level = level;
522 for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
527 static CLogContext *CLG_ctx_init(void)
529 CLogContext *ctx = MEM_callocN(sizeof(*ctx), __func__);
530 ctx->use_color = true;
531 ctx->default_type.level = 1;
532 CLG_ctx_output_set(ctx, stdout);
537 static void CLG_ctx_free(CLogContext *ctx)
539 while (ctx->types != NULL) {
540 CLG_LogType *item = ctx->types;
541 ctx->types = item->next;
545 for (uint i = 0; i < 2; i++) {
546 while (ctx->filters[i] != NULL) {
547 CLG_IDFilter *item = ctx->filters[i];
548 ctx->filters[i] = item->next;
557 /* -------------------------------------------------------------------- */
558 /** \name Public Logging API
560 * Currently uses global context.
563 /* We could support multiple at once, for now this seems not needed. */
564 static struct CLogContext *g_ctx = NULL;
568 g_ctx = CLG_ctx_init();
570 clg_color_table_init(g_ctx->use_color);
578 void CLG_output_set(void *file_handle)
580 CLG_ctx_output_set(g_ctx, file_handle);
583 void CLG_output_use_basename_set(int value)
585 CLG_ctx_output_use_basename_set(g_ctx, value);
589 void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
591 CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
594 void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
596 CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
599 void CLG_type_filter_exclude(const char *type_match, int type_match_len)
601 CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
604 void CLG_type_filter_include(const char *type_match, int type_match_len)
606 CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
609 void CLG_level_set(int level)
611 CLG_ctx_level_set(g_ctx, level);
617 /* -------------------------------------------------------------------- */
618 /** \name Logging Reference API
619 * Use to avoid lookups each time.
622 void CLG_logref_init(CLG_LogRef *clg_ref)
624 assert(clg_ref->type == NULL);
625 CLG_LogType *clg_ty = clg_ctx_type_find_by_name(g_ctx, clg_ref->identifier);
626 clg_ref->type = clg_ty ? clg_ty : clg_ctx_type_register(g_ctx, clg_ref->identifier);