Haiku: build fix
[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__) || defined(__HAIKU__)
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         write(lg->ctx->output, cstr.data, cstr.len);
417
418         clg_str_free(&cstr);
419
420         if (lg->ctx->callbacks.backtrace_fn) {
421                 clg_ctx_backtrace(lg->ctx);
422         }
423
424         if (severity == CLG_SEVERITY_FATAL) {
425                 clg_ctx_fatal_action(lg->ctx);
426         }
427 }
428
429 void CLG_logf(
430         CLG_LogType *lg, enum CLG_Severity severity, const char *file_line, const char *fn,
431         const char *fmt, ...)
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         write_severity(&cstr, severity, lg->ctx->use_color);
438         write_type(&cstr, lg);
439
440         {
441                 write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
442
443                 va_list ap;
444                 va_start(ap, fmt);
445                 clg_str_vappendf(&cstr, fmt, ap);
446                 va_end(ap);
447         }
448         clg_str_append(&cstr, "\n");
449
450         /* could be optional */
451         write(lg->ctx->output, cstr.data, cstr.len);
452
453         clg_str_free(&cstr);
454
455         if (lg->ctx->callbacks.backtrace_fn) {
456                 clg_ctx_backtrace(lg->ctx);
457         }
458
459         if (severity == CLG_SEVERITY_FATAL) {
460                 clg_ctx_fatal_action(lg->ctx);
461         }
462 }
463
464 /** \} */
465
466 /* -------------------------------------------------------------------- */
467 /** \name Logging Context API
468  * \{ */
469
470 static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
471 {
472         ctx->output_file = file_handle;
473         ctx->output = fileno(ctx->output_file);
474 #if defined(__unix__) || defined(__APPLE__)
475         ctx->use_color = isatty(ctx->output);
476 #endif
477 }
478
479 static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
480 {
481         ctx->use_basename = (bool)value;
482 }
483
484 /** Action on fatal severity. */
485 static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
486 {
487         ctx->callbacks.fatal_fn = fatal_fn;
488 }
489
490 static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
491 {
492         ctx->callbacks.backtrace_fn = backtrace_fn;
493 }
494
495 static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list, const char *type_match, int type_match_len)
496 {
497         if (type_match_len == 0) {
498                 return;
499         }
500         CLG_IDFilter *flt = MEM_callocN(sizeof(*flt) + (type_match_len + 1), __func__);
501         flt->next = *flt_list;
502         *flt_list = flt;
503         memcpy(flt->match, type_match, type_match_len);
504         /* no need to null terminate since we calloc'd */
505 }
506
507 static void CLG_ctx_type_filter_exclude(CLogContext *ctx, const char *type_match, int type_match_len)
508 {
509         clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
510 }
511
512 static void CLG_ctx_type_filter_include(CLogContext *ctx, const char *type_match, int type_match_len)
513 {
514         clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
515 }
516
517 static void CLG_ctx_level_set(CLogContext *ctx, int level)
518 {
519         ctx->default_type.level = level;
520         for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
521                 ty->level = level;
522         }
523 }
524
525 static CLogContext *CLG_ctx_init(void)
526 {
527         CLogContext *ctx = MEM_callocN(sizeof(*ctx), __func__);
528         ctx->use_color = true;
529         ctx->default_type.level = 1;
530         CLG_ctx_output_set(ctx, stdout);
531
532         return ctx;
533 }
534
535 static void CLG_ctx_free(CLogContext *ctx)
536 {
537         while (ctx->types != NULL) {
538                 CLG_LogType *item = ctx->types;
539                 ctx->types = item->next;
540                 MEM_freeN(item);
541         }
542
543         for (uint i = 0; i < 2; i++) {
544                 while (ctx->filters[i] != NULL) {
545                         CLG_IDFilter *item = ctx->filters[i];
546                         ctx->filters[i] = item->next;
547                         MEM_freeN(item);
548                 }
549         }
550         MEM_freeN(ctx);
551 }
552
553 /** \} */
554
555 /* -------------------------------------------------------------------- */
556 /** \name Public Logging API
557  *
558  * Currently uses global context.
559  * \{ */
560
561 /* We could support multiple at once, for now this seems not needed. */
562 static struct CLogContext *g_ctx = NULL;
563
564 void CLG_init(void)
565 {
566         g_ctx = CLG_ctx_init();
567
568         clg_color_table_init(g_ctx->use_color);
569 }
570
571 void CLG_exit(void)
572 {
573         CLG_ctx_free(g_ctx);
574 }
575
576 void CLG_output_set(void *file_handle)
577 {
578         CLG_ctx_output_set(g_ctx, file_handle);
579 }
580
581 void CLG_output_use_basename_set(int value)
582 {
583         CLG_ctx_output_use_basename_set(g_ctx, value);
584 }
585
586
587 void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
588 {
589         CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
590 }
591
592 void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
593 {
594         CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
595 }
596
597 void CLG_type_filter_exclude(const char *type_match, int type_match_len)
598 {
599         CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
600 }
601
602 void CLG_type_filter_include(const char *type_match, int type_match_len)
603 {
604         CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
605 }
606
607 void CLG_level_set(int level)
608 {
609         CLG_ctx_level_set(g_ctx, level);
610 }
611
612
613 /** \} */
614
615 /* -------------------------------------------------------------------- */
616 /** \name Logging Reference API
617  * Use to avoid lookups each time.
618  * \{ */
619
620 void CLG_logref_init(CLG_LogRef *clg_ref)
621 {
622         assert(clg_ref->type == NULL);
623         CLG_LogType *clg_ty = clg_ctx_type_find_by_name(g_ctx, clg_ref->identifier);
624         clg_ref->type = clg_ty ? clg_ty : clg_ctx_type_register(g_ctx, clg_ref->identifier);
625 }
626
627 /** \} */