doxygen: add newline after \file
[blender.git] / intern / clog / clog.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16
17 /** \file
18  * \ingroup clog
19  */
20
21 #include <stdarg.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdint.h>
25 #include <assert.h>
26
27 /* For 'isatty' to check for color. */
28 #if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
29 #  include <unistd.h>
30 #  include <sys/time.h>
31 #endif
32
33 #if defined(_MSC_VER)
34 #  include <io.h>
35 #  include <windows.h>
36 #endif
37
38 /* For printing timestamp. */
39 #define __STDC_FORMAT_MACROS
40 #include <inttypes.h>
41
42
43 /* Only other dependency (could use regular malloc too). */
44 #include "MEM_guardedalloc.h"
45
46 /* own include. */
47 #include "CLG_log.h"
48
49 /* Local utility defines */
50 #define STREQ(a, b) (strcmp(a, b) == 0)
51 #define STREQLEN(a, b, n) (strncmp(a, b, n) == 0)
52
53 #ifdef _WIN32
54 #  define PATHSEP_CHAR '\\'
55 #else
56 #  define PATHSEP_CHAR '/'
57 #endif
58
59 /* -------------------------------------------------------------------- */
60 /** \name Internal Types
61  * \{ */
62
63 typedef struct CLG_IDFilter {
64         struct CLG_IDFilter *next;
65         /** Over alloc. */
66         char match[0];
67 } CLG_IDFilter;
68
69 typedef struct CLogContext {
70         /** Single linked list of types.  */
71         CLG_LogType *types;
72         /* exclude, include filters.  */
73         CLG_IDFilter *filters[2];
74         bool use_color;
75         bool use_basename;
76         bool use_timestamp;
77
78         /** Borrowed, not owned. */
79         int output;
80         FILE *output_file;
81
82         /** For timer (use_timestamp). */
83         uint64_t timestamp_tick_start;
84
85         /** For new types. */
86         struct {
87                 int level;
88         } default_type;
89
90         struct {
91                 void (*fatal_fn)(void *file_handle);
92                 void (*backtrace_fn)(void *file_handle);
93         } callbacks;
94 } CLogContext;
95
96 /** \} */
97
98 /* -------------------------------------------------------------------- */
99 /** \name Mini Buffer Functionality
100  *
101  * Use so we can do a single call to write.
102  * \{ */
103
104 #define CLOG_BUF_LEN_INIT 512
105
106 typedef struct CLogStringBuf {
107         char *data;
108         uint  len;
109         uint  len_alloc;
110         bool is_alloc;
111 } CLogStringBuf;
112
113 static void clg_str_init(CLogStringBuf *cstr, char *buf_stack, uint buf_stack_len)
114 {
115         cstr->data = buf_stack;
116         cstr->len_alloc = buf_stack_len;
117         cstr->len = 0;
118         cstr->is_alloc = false;
119 }
120
121 static void clg_str_free(CLogStringBuf *cstr)
122 {
123         if (cstr->is_alloc) {
124                 MEM_freeN(cstr->data);
125         }
126 }
127
128 static void clg_str_reserve(CLogStringBuf *cstr, const uint len)
129 {
130         if (len > cstr->len_alloc) {
131                 cstr->len_alloc *= 2;
132                 if (len > cstr->len_alloc) {
133                         cstr->len_alloc = len;
134                 }
135
136                 if (cstr->is_alloc) {
137                         cstr->data = MEM_reallocN(cstr->data, cstr->len_alloc);
138                 }
139                 else {
140                         /* Copy the static buffer. */
141                         char *data = MEM_mallocN(cstr->len_alloc, __func__);
142                         memcpy(data, cstr->data, cstr->len);
143                         cstr->data = data;
144                         cstr->is_alloc = true;
145                 }
146                 cstr->len_alloc = len;
147         }
148 }
149
150 static void clg_str_append_with_len(CLogStringBuf *cstr, const char *str, const uint len)
151 {
152         uint len_next = cstr->len + len;
153         clg_str_reserve(cstr, len_next);
154         char *str_dst = cstr->data + cstr->len;
155         memcpy(str_dst, str, len);
156 #if 0 /* no need. */
157         str_dst[len] = '\0';
158 #endif
159         cstr->len = len_next;
160 }
161
162 static void clg_str_append(CLogStringBuf *cstr, const char *str)
163 {
164         clg_str_append_with_len(cstr, str, strlen(str));
165 }
166
167 static void clg_str_vappendf(CLogStringBuf *cstr, const char *fmt, va_list args)
168 {
169         /* Use limit because windows may use '-1' for a formatting error. */
170         const uint len_max = 65535;
171         uint len_avail = (cstr->len_alloc - cstr->len);
172         if (len_avail == 0) {
173                 len_avail = CLOG_BUF_LEN_INIT;
174                 clg_str_reserve(cstr, len_avail);
175         }
176         while (true) {
177                 va_list args_cpy;
178                 va_copy(args_cpy, args);
179                 int retval = vsnprintf(cstr->data + cstr->len, len_avail, fmt, args_cpy);
180                 va_end(args_cpy);
181                 if (retval != -1) {
182                         cstr->len += retval;
183                         break;
184                 }
185                 else {
186                         len_avail *= 2;
187                         if (len_avail >= len_max) {
188                                 break;
189                         }
190                         clg_str_reserve(cstr, len_avail);
191                 }
192         }
193 }
194
195 /** \} */
196
197 /* -------------------------------------------------------------------- */
198 /** \name Internal Utilities
199  * \{ */
200
201 enum eCLogColor {
202         COLOR_DEFAULT,
203         COLOR_RED,
204         COLOR_GREEN,
205         COLOR_YELLOW,
206
207         COLOR_RESET,
208 };
209 #define COLOR_LEN (COLOR_RESET + 1)
210
211 static const char *clg_color_table[COLOR_LEN] = {NULL};
212
213 static void clg_color_table_init(bool use_color)
214 {
215         for (int i = 0; i < COLOR_LEN; i++) {
216                 clg_color_table[i] = "";
217         }
218         if (use_color) {
219 #ifdef _WIN32
220                 /* TODO */
221 #else
222                 clg_color_table[COLOR_DEFAULT]      = "\033[1;37m";
223                 clg_color_table[COLOR_RED]          = "\033[1;31m";
224                 clg_color_table[COLOR_GREEN]        = "\033[1;32m";
225                 clg_color_table[COLOR_YELLOW]       = "\033[1;33m";
226                 clg_color_table[COLOR_RESET]        = "\033[0m";
227 #endif
228         }
229 }
230
231 static const char *clg_severity_str[CLG_SEVERITY_LEN] = {
232         [CLG_SEVERITY_INFO] =       "INFO",
233         [CLG_SEVERITY_WARN] =       "WARN",
234         [CLG_SEVERITY_ERROR] =      "ERROR",
235         [CLG_SEVERITY_FATAL] =      "FATAL",
236 };
237
238 static const char *clg_severity_as_text(enum CLG_Severity severity)
239 {
240         bool ok = (unsigned int)severity < CLG_SEVERITY_LEN;
241         assert(ok);
242         if (ok) {
243                 return clg_severity_str[severity];
244         }
245         else {
246                 return "INVALID_SEVERITY";
247         }
248 }
249
250 static enum eCLogColor clg_severity_to_color(enum CLG_Severity severity)
251 {
252         assert((unsigned int)severity < CLG_SEVERITY_LEN);
253         enum eCLogColor color = COLOR_DEFAULT;
254         switch (severity) {
255                 case CLG_SEVERITY_INFO:
256                         color = COLOR_DEFAULT;
257                         break;
258                 case CLG_SEVERITY_WARN:
259                         color = COLOR_YELLOW;
260                         break;
261                 case CLG_SEVERITY_ERROR:
262                 case CLG_SEVERITY_FATAL:
263                         color = COLOR_RED;
264                         break;
265                 default:
266                         /* should never get here. */
267                         assert(false);
268         }
269         return color;
270 }
271
272 /** \} */
273
274 /* -------------------------------------------------------------------- */
275 /** \name Context Type Access
276  * \{ */
277
278 /**
279  * Filter the indentifier based on very basic globbing.
280  * - `foo` exact match of `foo`.
281  * - `foo.bar` exact match for `foo.bar`
282  * - `foo.*` match for `foo` & `foo.bar` & `foo.bar.baz`
283  * - `*` matches everything.
284  */
285 static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
286 {
287         const int identifier_len = strlen(identifier);
288         for (uint i = 0; i < 2; i++) {
289                 const CLG_IDFilter *flt = ctx->filters[i];
290                 while (flt != NULL) {
291                         const int len = strlen(flt->match);
292                         if (STREQ(flt->match, "*") ||
293                                 ((len == identifier_len) && (STREQ(identifier, flt->match))))
294                         {
295                                 return (bool)i;
296                         }
297                         if ((len >= 2) && (STREQLEN(".*", &flt->match[len - 2], 2))) {
298                                 if (((identifier_len == len - 2) && STREQLEN(identifier, flt->match, len - 2)) ||
299                                         ((identifier_len >= len - 1) && STREQLEN(identifier, flt->match, len - 1)))
300                                 {
301                                         return (bool)i;
302                                 }
303                         }
304                         flt = flt->next;
305                 }
306         }
307         return false;
308 }
309
310 /**
311  * \note This should never be called per logging call.
312  * Searching is only to get an initial handle.
313  */
314 static CLG_LogType *clg_ctx_type_find_by_name(CLogContext *ctx, const char *identifier)
315 {
316         for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
317                 if (STREQ(identifier, ty->identifier)) {
318                         return ty;
319                 }
320         }
321         return NULL;
322 }
323
324 static CLG_LogType *clg_ctx_type_register(CLogContext *ctx, const char *identifier)
325 {
326         assert(clg_ctx_type_find_by_name(ctx, identifier) == NULL);
327         CLG_LogType *ty = MEM_callocN(sizeof(*ty), __func__);
328         ty->next = ctx->types;
329         ctx->types = ty;
330         strncpy(ty->identifier, identifier, sizeof(ty->identifier) - 1);
331         ty->ctx = ctx;
332         ty->level = ctx->default_type.level;
333
334         if (clg_ctx_filter_check(ctx, ty->identifier)) {
335                 ty->flag |= CLG_FLAG_USE;
336         }
337         return ty;
338 }
339
340 static void clg_ctx_fatal_action(CLogContext *ctx)
341 {
342         if (ctx->callbacks.fatal_fn != NULL) {
343                 ctx->callbacks.fatal_fn(ctx->output_file);
344         }
345         fflush(ctx->output_file);
346         abort();
347 }
348
349 static void clg_ctx_backtrace(CLogContext *ctx)
350 {
351         /* Note: we avoid writing fo 'FILE', for backtrace we make an exception,
352          * if necessary we could have a version of the callback that writes to file descriptor all at once. */
353         ctx->callbacks.backtrace_fn(ctx->output_file);
354         fflush(ctx->output_file);
355 }
356
357 static uint64_t clg_timestamp_ticks_get(void)
358 {
359         uint64_t tick;
360 #if defined(_MSC_VER)
361         tick = GetTickCount64();
362 #else
363         struct timeval tv;
364         gettimeofday(&tv, NULL);
365         tick = tv.tv_sec * 1000 + tv.tv_usec / 1000;
366 #endif
367         return tick;
368 }
369
370 /** \} */
371
372 /* -------------------------------------------------------------------- */
373 /** \name Logging API
374  * \{ */
375
376 static void write_timestamp(CLogStringBuf *cstr, const uint64_t timestamp_tick_start)
377 {
378         char timestamp_str[64];
379         const uint64_t timestamp = clg_timestamp_ticks_get() - timestamp_tick_start;
380         const uint timestamp_len = snprintf(
381                 timestamp_str, sizeof(timestamp_str), "%" PRIu64 ".%03u ",
382                 timestamp / 1000, (uint)(timestamp % 1000));
383         clg_str_append_with_len(cstr, timestamp_str, timestamp_len);
384 }
385
386 static void write_severity(CLogStringBuf *cstr, enum CLG_Severity severity, bool use_color)
387 {
388         assert((unsigned int)severity < CLG_SEVERITY_LEN);
389         if (use_color) {
390                 enum eCLogColor color = clg_severity_to_color(severity);
391                 clg_str_append(cstr, clg_color_table[color]);
392                 clg_str_append(cstr, clg_severity_as_text(severity));
393                 clg_str_append(cstr, clg_color_table[COLOR_RESET]);
394         }
395         else {
396                 clg_str_append(cstr, clg_severity_as_text(severity));
397         }
398 }
399
400 static void write_type(CLogStringBuf *cstr, CLG_LogType *lg)
401 {
402         clg_str_append(cstr, " (");
403         clg_str_append(cstr, lg->identifier);
404         clg_str_append(cstr, "): ");
405 }
406
407 static void write_file_line_fn(CLogStringBuf *cstr, const char *file_line, const char *fn, const bool use_basename)
408 {
409         uint file_line_len = strlen(file_line);
410         if (use_basename) {
411                 uint file_line_offset = file_line_len;
412                 while (file_line_offset-- > 0) {
413                         if (file_line[file_line_offset] == PATHSEP_CHAR) {
414                                 file_line_offset++;
415                                 break;
416                         }
417                 }
418                 file_line += file_line_offset;
419                 file_line_len -= file_line_offset;
420         }
421         clg_str_append_with_len(cstr, file_line, file_line_len);
422
423
424         clg_str_append(cstr, " ");
425         clg_str_append(cstr, fn);
426         clg_str_append(cstr, ": ");
427 }
428
429 void CLG_log_str(
430         CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
431         const char *message)
432 {
433         CLogStringBuf cstr;
434         char cstr_stack_buf[CLOG_BUF_LEN_INIT];
435         clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
436
437         if (lg->ctx->use_timestamp) {
438                 write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
439         }
440
441         write_severity(&cstr, severity, lg->ctx->use_color);
442         write_type(&cstr, lg);
443
444         {
445                 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
446                 clg_str_append(&cstr, message);
447         }
448         clg_str_append(&cstr, "\n");
449
450         /* could be optional */
451         int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
452         (void)bytes_written;
453
454         clg_str_free(&cstr);
455
456         if (lg->ctx->callbacks.backtrace_fn) {
457                 clg_ctx_backtrace(lg->ctx);
458         }
459
460         if (severity == CLG_SEVERITY_FATAL) {
461                 clg_ctx_fatal_action(lg->ctx);
462         }
463 }
464
465 void CLG_logf(
466         CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
467         const char *fmt, ...)
468 {
469         CLogStringBuf cstr;
470         char cstr_stack_buf[CLOG_BUF_LEN_INIT];
471         clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
472
473         if (lg->ctx->use_timestamp) {
474                 write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
475         }
476
477         write_severity(&cstr, severity, lg->ctx->use_color);
478         write_type(&cstr, lg);
479
480         {
481                 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
482
483                 va_list ap;
484                 va_start(ap, fmt);
485                 clg_str_vappendf(&cstr, fmt, ap);
486                 va_end(ap);
487         }
488         clg_str_append(&cstr, "\n");
489
490         /* could be optional */
491         int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
492         (void)bytes_written;
493
494         clg_str_free(&cstr);
495
496         if (lg->ctx->callbacks.backtrace_fn) {
497                 clg_ctx_backtrace(lg->ctx);
498         }
499
500         if (severity == CLG_SEVERITY_FATAL) {
501                 clg_ctx_fatal_action(lg->ctx);
502         }
503 }
504
505 /** \} */
506
507 /* -------------------------------------------------------------------- */
508 /** \name Logging Context API
509  * \{ */
510
511 static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
512 {
513         ctx->output_file = file_handle;
514         ctx->output = fileno(ctx->output_file);
515 #if defined(__unix__) || defined(__APPLE__)
516         ctx->use_color = isatty(ctx->output);
517 #endif
518 }
519
520 static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
521 {
522         ctx->use_basename = (bool)value;
523 }
524
525 static void CLG_ctx_output_use_timestamp_set(CLogContext *ctx, int value)
526 {
527         ctx->use_timestamp = (bool)value;
528         if (ctx->use_timestamp) {
529                 ctx->timestamp_tick_start = clg_timestamp_ticks_get();
530         }
531 }
532
533 /** Action on fatal severity. */
534 static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
535 {
536         ctx->callbacks.fatal_fn = fatal_fn;
537 }
538
539 static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
540 {
541         ctx->callbacks.backtrace_fn = backtrace_fn;
542 }
543
544 static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list, const char *type_match, int type_match_len)
545 {
546         if (type_match_len == 0) {
547                 return;
548         }
549         CLG_IDFilter *flt = MEM_callocN(sizeof(*flt) + (type_match_len + 1), __func__);
550         flt->next = *flt_list;
551         *flt_list = flt;
552         memcpy(flt->match, type_match, type_match_len);
553         /* no need to null terminate since we calloc'd */
554 }
555
556 static void CLG_ctx_type_filter_exclude(CLogContext *ctx, const char *type_match, int type_match_len)
557 {
558         clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
559 }
560
561 static void CLG_ctx_type_filter_include(CLogContext *ctx, const char *type_match, int type_match_len)
562 {
563         clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
564 }
565
566 static void CLG_ctx_level_set(CLogContext *ctx, int level)
567 {
568         ctx->default_type.level = level;
569         for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
570                 ty->level = level;
571         }
572 }
573
574 static CLogContext *CLG_ctx_init(void)
575 {
576         CLogContext *ctx = MEM_callocN(sizeof(*ctx), __func__);
577         ctx->use_color = true;
578         ctx->default_type.level = 1;
579         CLG_ctx_output_set(ctx, stdout);
580
581         return ctx;
582 }
583
584 static void CLG_ctx_free(CLogContext *ctx)
585 {
586         while (ctx->types != NULL) {
587                 CLG_LogType *item = ctx->types;
588                 ctx->types = item->next;
589                 MEM_freeN(item);
590         }
591
592         for (uint i = 0; i < 2; i++) {
593                 while (ctx->filters[i] != NULL) {
594                         CLG_IDFilter *item = ctx->filters[i];
595                         ctx->filters[i] = item->next;
596                         MEM_freeN(item);
597                 }
598         }
599         MEM_freeN(ctx);
600 }
601
602 /** \} */
603
604 /* -------------------------------------------------------------------- */
605 /** \name Public Logging API
606  *
607  * Currently uses global context.
608  * \{ */
609
610 /* We could support multiple at once, for now this seems not needed. */
611 static struct CLogContext *g_ctx = NULL;
612
613 void CLG_init(void)
614 {
615         g_ctx = CLG_ctx_init();
616
617         clg_color_table_init(g_ctx->use_color);
618 }
619
620 void CLG_exit(void)
621 {
622         CLG_ctx_free(g_ctx);
623 }
624
625 void CLG_output_set(void *file_handle)
626 {
627         CLG_ctx_output_set(g_ctx, file_handle);
628 }
629
630 void CLG_output_use_basename_set(int value)
631 {
632         CLG_ctx_output_use_basename_set(g_ctx, value);
633 }
634
635 void CLG_output_use_timestamp_set(int value)
636 {
637         CLG_ctx_output_use_timestamp_set(g_ctx, value);
638 }
639
640 void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
641 {
642         CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
643 }
644
645 void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
646 {
647         CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
648 }
649
650 void CLG_type_filter_exclude(const char *type_match, int type_match_len)
651 {
652         CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
653 }
654
655 void CLG_type_filter_include(const char *type_match, int type_match_len)
656 {
657         CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
658 }
659
660 void CLG_level_set(int level)
661 {
662         CLG_ctx_level_set(g_ctx, level);
663 }
664
665
666 /** \} */
667
668 /* -------------------------------------------------------------------- */
669 /** \name Logging Reference API
670  * Use to avoid lookups each time.
671  * \{ */
672
673 void CLG_logref_init(CLG_LogRef *clg_ref)
674 {
675         assert(clg_ref->type == NULL);
676         CLG_LogType *clg_ty = clg_ctx_type_find_by_name(g_ctx, clg_ref->identifier);
677         clg_ref->type = clg_ty ? clg_ty : clg_ctx_type_register(g_ctx, clg_ref->identifier);
678 }
679
680 /** \} */