Merge branch 'master' into blender2.8
[blender.git] / intern / clog / clog.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
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.
8  *
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.
13  *
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.
17  *
18  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file clog/clog.c
22  *  \ingroup clog
23  */
24
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdint.h>
29 #include <assert.h>
30
31 /* For 'isatty' to check for color. */
32 #if defined(__unix__) || defined(__APPLE__)
33 #  include <unistd.h>
34 #endif
35
36 #if defined(_MSC_VER)
37 #  include <io.h>
38 #endif
39 /* Only other dependency (could use regular malloc too). */
40 #include "MEM_guardedalloc.h"
41
42 /* own include. */
43 #include "CLG_log.h"
44
45 /* Local utility defines */
46 #define STREQ(a, b) (strcmp(a, b) == 0)
47 #define STREQLEN(a, b, n) (strncmp(a, b, n) == 0)
48
49 #ifdef _WIN32
50 #  define PATHSEP_CHAR '\\'
51 #else
52 #  define PATHSEP_CHAR '/'
53 #endif
54
55 /* -------------------------------------------------------------------- */
56 /** \name Internal Types
57  * \{ */
58
59 typedef struct CLG_IDFilter {
60         struct CLG_IDFilter *next;
61         /** Over alloc. */
62         char match[0];
63 } CLG_IDFilter;
64
65 typedef struct CLogContext {
66         /** Single linked list of types.  */
67         CLG_LogType *types;
68         /* exclude, include filters.  */
69         CLG_IDFilter *filters[2];
70         bool use_color;
71         bool use_basename;
72
73         /** Borrowed, not owned. */
74         int output;
75         FILE *output_file;
76
77         /** For new types. */
78         struct {
79                 int level;
80         } default_type;
81
82         struct {
83                 void (*fatal_fn)(void *file_handle);
84                 void (*backtrace_fn)(void *file_handle);
85         } callbacks;
86 } CLogContext;
87
88 /** \} */
89
90 /* -------------------------------------------------------------------- */
91 /** \name Mini Buffer Functionality
92  *
93  * Use so we can do a single call to write.
94  * \{ */
95
96 #define CLOG_BUF_LEN_INIT 512
97
98 typedef struct CLogStringBuf {
99         char *data;
100         uint  len;
101         uint  len_alloc;
102         bool is_alloc;
103 } CLogStringBuf;
104
105 static void clg_str_init(CLogStringBuf *cstr, char *buf_stack, uint buf_stack_len)
106 {
107         cstr->data = buf_stack;
108         cstr->len_alloc = buf_stack_len;
109         cstr->len = 0;
110         cstr->is_alloc = false;
111 }
112
113 static void clg_str_free(CLogStringBuf *cstr)
114 {
115         if (cstr->is_alloc) {
116                 MEM_freeN(cstr->data);
117         }
118 }
119
120 static void clg_str_reserve(CLogStringBuf *cstr, const uint len)
121 {
122         if (len > cstr->len_alloc) {
123                 cstr->len_alloc *= 2;
124                 if (len > cstr->len_alloc) {
125                         cstr->len_alloc = len;
126                 }
127
128                 if (cstr->is_alloc) {
129                         cstr->data = MEM_reallocN(cstr->data, cstr->len_alloc);
130                 }
131                 else {
132                         /* Copy the static buffer. */
133                         char *data = MEM_mallocN(cstr->len_alloc, __func__);
134                         memcpy(data, cstr->data, cstr->len);
135                         cstr->data = data;
136                         cstr->is_alloc = true;
137                 }
138                 cstr->len_alloc = len;
139         }
140 }
141
142 static void clg_str_append_with_len(CLogStringBuf *cstr, const char *str, const uint len)
143 {
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);
148 #if 0 /* no need. */
149         str_dst[len] = '\0';
150 #endif
151         cstr->len = len_next;
152 }
153
154 static void clg_str_append(CLogStringBuf *cstr, const char *str)
155 {
156         clg_str_append_with_len(cstr, str, strlen(str));
157 }
158
159 static void clg_str_vappendf(CLogStringBuf *cstr, const char *fmt, va_list args)
160 {
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);
167         }
168         while (true) {
169                 va_list args_cpy;
170                 va_copy(args_cpy, args);
171                 int retval = vsnprintf(cstr->data + cstr->len, len_avail, fmt, args_cpy);
172                 va_end(args_cpy);
173                 if (retval != -1) {
174                         cstr->len += retval;
175                         break;
176                 }
177                 else {
178                         len_avail *= 2;
179                         if (len_avail >= len_max) {
180                                 break;
181                         }
182                         clg_str_reserve(cstr, len_avail);
183                 }
184         }
185 }
186
187 /** \} */
188
189 /* -------------------------------------------------------------------- */
190 /** \name Internal Utilities
191  * \{ */
192
193 enum eCLogColor {
194         COLOR_DEFAULT,
195         COLOR_RED,
196         COLOR_GREEN,
197         COLOR_YELLOW,
198
199         COLOR_RESET,
200 };
201 #define COLOR_LEN (COLOR_RESET + 1)
202
203 static const char *clg_color_table[COLOR_LEN] = {NULL};
204
205 static void clg_color_table_init(bool use_color)
206 {
207         for (int i = 0; i < COLOR_LEN; i++) {
208                 clg_color_table[i] = "";
209         }
210         if (use_color) {
211 #ifdef _WIN32
212                 /* TODO */
213 #else
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";
219 #endif
220         }
221 }
222
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",
228 };
229
230 static const char *clg_severity_as_text(enum CLG_Severity severity)
231 {
232         bool ok = (unsigned int)severity < CLG_SEVERITY_LEN;
233         assert(ok);
234         if (ok) {
235                 return clg_severity_str[severity];
236         }
237         else {
238                 return "INVALID_SEVERITY";
239         }
240 }
241
242 static enum eCLogColor clg_severity_to_color(enum CLG_Severity severity)
243 {
244         assert((unsigned int)severity < CLG_SEVERITY_LEN);
245         enum eCLogColor color = COLOR_DEFAULT;
246         switch (severity) {
247                 case CLG_SEVERITY_INFO:
248                         color = COLOR_DEFAULT;
249                         break;
250                 case CLG_SEVERITY_WARN:
251                         color = COLOR_YELLOW;
252                         break;
253                 case CLG_SEVERITY_ERROR:
254                 case CLG_SEVERITY_FATAL:
255                         color = COLOR_RED;
256                         break;
257                 default:
258                         /* should never get here. */
259                         assert(false);
260         }
261         return color;
262 }
263
264 /** \} */
265
266 /* -------------------------------------------------------------------- */
267 /** \name Context Type Access
268  * \{ */
269
270 /**
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.
276  */
277 static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
278 {
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))))
286                         {
287                                 return (bool)i;
288                         }
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)))
292                                 {
293                                         return (bool)i;
294                                 }
295                         }
296                         flt = flt->next;
297                 }
298         }
299         return false;
300 }
301
302 /**
303  * \note This should never be called per logging call.
304  * Searching is only to get an initial handle.
305  */
306 static CLG_LogType *clg_ctx_type_find_by_name(CLogContext *ctx, const char *identifier)
307 {
308         for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
309                 if (STREQ(identifier, ty->identifier)) {
310                         return ty;
311                 }
312         }
313         return NULL;
314 }
315
316 static CLG_LogType *clg_ctx_type_register(CLogContext *ctx, const char *identifier)
317 {
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;
321         ctx->types = ty;
322         strncpy(ty->identifier, identifier, sizeof(ty->identifier) - 1);
323         ty->ctx = ctx;
324         ty->level = ctx->default_type.level;
325
326         if (clg_ctx_filter_check(ctx, ty->identifier)) {
327                 ty->flag |= CLG_FLAG_USE;
328         }
329         return ty;
330 }
331
332 static void clg_ctx_fatal_action(CLogContext *ctx)
333 {
334         if (ctx->callbacks.fatal_fn != NULL) {
335                 ctx->callbacks.fatal_fn(ctx->output_file);
336         }
337         fflush(ctx->output_file);
338         abort();
339 }
340
341 static void clg_ctx_backtrace(CLogContext *ctx)
342 {
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);
347 }
348
349 /** \} */
350
351 /* -------------------------------------------------------------------- */
352 /** \name Logging API
353  * \{ */
354
355 static void write_severity(CLogStringBuf *cstr, enum CLG_Severity severity, bool use_color)
356 {
357         assert((unsigned int)severity < CLG_SEVERITY_LEN);
358         if (use_color) {
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]);
363         }
364         else {
365                 clg_str_append(cstr, clg_severity_as_text(severity));
366         }
367 }
368
369 static void write_type(CLogStringBuf *cstr, CLG_LogType *lg)
370 {
371         clg_str_append(cstr, " (");
372         clg_str_append(cstr, lg->identifier);
373         clg_str_append(cstr, "): ");
374 }
375
376 static void write_file_line_fn(CLogStringBuf *cstr, const char *file_line, const char *fn, const bool use_basename)
377 {
378         uint file_line_len = strlen(file_line);
379         if (use_basename) {
380                 uint file_line_offset = file_line_len;
381                 while (file_line_offset-- > 0) {
382                         if (file_line[file_line_offset] == PATHSEP_CHAR) {
383                                 file_line_offset++;
384                                 break;
385                         }
386                 }
387                 file_line += file_line_offset;
388                 file_line_len -= file_line_offset;
389         }
390         clg_str_append_with_len(cstr, file_line, file_line_len);
391
392
393         clg_str_append(cstr, " ");
394         clg_str_append(cstr, fn);
395         clg_str_append(cstr, ": ");
396 }
397
398 void CLG_log_str(
399         CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
400         const char *message)
401 {
402         CLogStringBuf cstr;
403         char cstr_stack_buf[CLOG_BUF_LEN_INIT];
404         clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
405
406         write_severity(&cstr, severity, lg->ctx->use_color);
407         write_type(&cstr, lg);
408
409         {
410                 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
411                 clg_str_append(&cstr, message);
412         }
413         clg_str_append(&cstr, "\n");
414
415         /* could be optional */
416         int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
417         (void)bytes_written;
418
419         clg_str_free(&cstr);
420
421         if (lg->ctx->callbacks.backtrace_fn) {
422                 clg_ctx_backtrace(lg->ctx);
423         }
424
425         if (severity == CLG_SEVERITY_FATAL) {
426                 clg_ctx_fatal_action(lg->ctx);
427         }
428 }
429
430 void CLG_logf(
431         CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
432         const char *fmt, ...)
433 {
434         CLogStringBuf cstr;
435         char cstr_stack_buf[CLOG_BUF_LEN_INIT];
436         clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
437
438         write_severity(&cstr, severity, lg->ctx->use_color);
439         write_type(&cstr, lg);
440
441         {
442                 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
443
444                 va_list ap;
445                 va_start(ap, fmt);
446                 clg_str_vappendf(&cstr, fmt, ap);
447                 va_end(ap);
448         }
449         clg_str_append(&cstr, "\n");
450
451         /* could be optional */
452         int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
453         (void)bytes_written;
454
455         clg_str_free(&cstr);
456
457         if (lg->ctx->callbacks.backtrace_fn) {
458                 clg_ctx_backtrace(lg->ctx);
459         }
460
461         if (severity == CLG_SEVERITY_FATAL) {
462                 clg_ctx_fatal_action(lg->ctx);
463         }
464 }
465
466 /** \} */
467
468 /* -------------------------------------------------------------------- */
469 /** \name Logging Context API
470  * \{ */
471
472 static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
473 {
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);
478 #endif
479 }
480
481 static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
482 {
483         ctx->use_basename = (bool)value;
484 }
485
486 /** Action on fatal severity. */
487 static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
488 {
489         ctx->callbacks.fatal_fn = fatal_fn;
490 }
491
492 static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
493 {
494         ctx->callbacks.backtrace_fn = backtrace_fn;
495 }
496
497 static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list, const char *type_match, int type_match_len)
498 {
499         if (type_match_len == 0) {
500                 return;
501         }
502         CLG_IDFilter *flt = MEM_callocN(sizeof(*flt) + (type_match_len + 1), __func__);
503         flt->next = *flt_list;
504         *flt_list = flt;
505         memcpy(flt->match, type_match, type_match_len);
506         /* no need to null terminate since we calloc'd */
507 }
508
509 static void CLG_ctx_type_filter_exclude(CLogContext *ctx, const char *type_match, int type_match_len)
510 {
511         clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
512 }
513
514 static void CLG_ctx_type_filter_include(CLogContext *ctx, const char *type_match, int type_match_len)
515 {
516         clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
517 }
518
519 static void CLG_ctx_level_set(CLogContext *ctx, int level)
520 {
521         ctx->default_type.level = level;
522         for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
523                 ty->level = level;
524         }
525 }
526
527 static CLogContext *CLG_ctx_init(void)
528 {
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);
533
534         return ctx;
535 }
536
537 static void CLG_ctx_free(CLogContext *ctx)
538 {
539         while (ctx->types != NULL) {
540                 CLG_LogType *item = ctx->types;
541                 ctx->types = item->next;
542                 MEM_freeN(item);
543         }
544
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;
549                         MEM_freeN(item);
550                 }
551         }
552         MEM_freeN(ctx);
553 }
554
555 /** \} */
556
557 /* -------------------------------------------------------------------- */
558 /** \name Public Logging API
559  *
560  * Currently uses global context.
561  * \{ */
562
563 /* We could support multiple at once, for now this seems not needed. */
564 static struct CLogContext *g_ctx = NULL;
565
566 void CLG_init(void)
567 {
568         g_ctx = CLG_ctx_init();
569
570         clg_color_table_init(g_ctx->use_color);
571 }
572
573 void CLG_exit(void)
574 {
575         CLG_ctx_free(g_ctx);
576 }
577
578 void CLG_output_set(void *file_handle)
579 {
580         CLG_ctx_output_set(g_ctx, file_handle);
581 }
582
583 void CLG_output_use_basename_set(int value)
584 {
585         CLG_ctx_output_use_basename_set(g_ctx, value);
586 }
587
588
589 void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
590 {
591         CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
592 }
593
594 void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
595 {
596         CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
597 }
598
599 void CLG_type_filter_exclude(const char *type_match, int type_match_len)
600 {
601         CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
602 }
603
604 void CLG_type_filter_include(const char *type_match, int type_match_len)
605 {
606         CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
607 }
608
609 void CLG_level_set(int level)
610 {
611         CLG_ctx_level_set(g_ctx, level);
612 }
613
614
615 /** \} */
616
617 /* -------------------------------------------------------------------- */
618 /** \name Logging Reference API
619  * Use to avoid lookups each time.
620  * \{ */
621
622 void CLG_logref_init(CLG_LogRef *clg_ref)
623 {
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);
627 }
628
629 /** \} */