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 <assert.h>
22 #include <stdarg.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 /* Disable for small single threaded programs
28  * to avoid having to link with pthreads. */
29 #ifdef WITH_CLOG_PTHREADS
30 #  include "atomic_ops.h"
31 #  include <pthread.h>
32 #endif
33 
34 /* For 'isatty' to check for color. */
35 #if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
36 #  include <sys/time.h>
37 #  include <unistd.h>
38 #endif
39 
40 #if defined(_MSC_VER)
41 #  include <io.h>
42 #  include <windows.h>
43 #endif
44 
45 /* For printing timestamp. */
46 #define __STDC_FORMAT_MACROS
47 #include <inttypes.h>
48 
49 /* Only other dependency (could use regular malloc too). */
50 #include "MEM_guardedalloc.h"
51 
52 /* own include. */
53 #include "CLG_log.h"
54 
55 /* Local utility defines */
56 #define STREQ(a, b) (strcmp(a, b) == 0)
57 #define STREQLEN(a, b, n) (strncmp(a, b, n) == 0)
58 
59 #ifdef _WIN32
60 #  define PATHSEP_CHAR '\\'
61 #else
62 #  define PATHSEP_CHAR '/'
63 #endif
64 
65 /* -------------------------------------------------------------------- */
66 /** \name Internal Types
67  * \{ */
68 
69 typedef struct CLG_IDFilter {
70   struct CLG_IDFilter *next;
71   /** Over alloc. */
72   char match[0];
73 } CLG_IDFilter;
74 
75 typedef struct CLogContext {
76   /** Single linked list of types.  */
77   CLG_LogType *types;
78 #ifdef WITH_CLOG_PTHREADS
79   pthread_mutex_t types_lock;
80 #endif
81 
82   /* exclude, include filters.  */
83   CLG_IDFilter *filters[2];
84   bool use_color;
85   bool use_basename;
86   bool use_timestamp;
87 
88   /** Borrowed, not owned. */
89   int output;
90   FILE *output_file;
91 
92   /** For timer (use_timestamp). */
93   uint64_t timestamp_tick_start;
94 
95   /** For new types. */
96   struct {
97     int level;
98   } default_type;
99 
100   struct {
101     void (*error_fn)(void *file_handle);
102     void (*fatal_fn)(void *file_handle);
103     void (*backtrace_fn)(void *file_handle);
104   } callbacks;
105 } CLogContext;
106 
107 /** \} */
108 
109 /* -------------------------------------------------------------------- */
110 /** \name Mini Buffer Functionality
111  *
112  * Use so we can do a single call to write.
113  * \{ */
114 
115 #define CLOG_BUF_LEN_INIT 512
116 
117 typedef struct CLogStringBuf {
118   char *data;
119   uint len;
120   uint len_alloc;
121   bool is_alloc;
122 } CLogStringBuf;
123 
clg_str_init(CLogStringBuf * cstr,char * buf_stack,uint buf_stack_len)124 static void clg_str_init(CLogStringBuf *cstr, char *buf_stack, uint buf_stack_len)
125 {
126   cstr->data = buf_stack;
127   cstr->len_alloc = buf_stack_len;
128   cstr->len = 0;
129   cstr->is_alloc = false;
130 }
131 
clg_str_free(CLogStringBuf * cstr)132 static void clg_str_free(CLogStringBuf *cstr)
133 {
134   if (cstr->is_alloc) {
135     MEM_freeN(cstr->data);
136   }
137 }
138 
clg_str_reserve(CLogStringBuf * cstr,const uint len)139 static void clg_str_reserve(CLogStringBuf *cstr, const uint len)
140 {
141   if (len > cstr->len_alloc) {
142     cstr->len_alloc *= 2;
143     if (len > cstr->len_alloc) {
144       cstr->len_alloc = len;
145     }
146 
147     if (cstr->is_alloc) {
148       cstr->data = MEM_reallocN(cstr->data, cstr->len_alloc);
149     }
150     else {
151       /* Copy the static buffer. */
152       char *data = MEM_mallocN(cstr->len_alloc, __func__);
153       memcpy(data, cstr->data, cstr->len);
154       cstr->data = data;
155       cstr->is_alloc = true;
156     }
157   }
158 }
159 
clg_str_append_with_len(CLogStringBuf * cstr,const char * str,const uint len)160 static void clg_str_append_with_len(CLogStringBuf *cstr, const char *str, const uint len)
161 {
162   uint len_next = cstr->len + len;
163   clg_str_reserve(cstr, len_next);
164   char *str_dst = cstr->data + cstr->len;
165   memcpy(str_dst, str, len);
166 #if 0 /* no need. */
167   str_dst[len] = '\0';
168 #endif
169   cstr->len = len_next;
170 }
171 
clg_str_append(CLogStringBuf * cstr,const char * str)172 static void clg_str_append(CLogStringBuf *cstr, const char *str)
173 {
174   clg_str_append_with_len(cstr, str, strlen(str));
175 }
176 
177 ATTR_PRINTF_FORMAT(2, 0)
clg_str_vappendf(CLogStringBuf * cstr,const char * fmt,va_list args)178 static void clg_str_vappendf(CLogStringBuf *cstr, const char *fmt, va_list args)
179 {
180   /* Use limit because windows may use '-1' for a formatting error. */
181   const uint len_max = 65535;
182   while (true) {
183     uint len_avail = cstr->len_alloc - cstr->len;
184 
185     va_list args_cpy;
186     va_copy(args_cpy, args);
187     int retval = vsnprintf(cstr->data + cstr->len, len_avail, fmt, args_cpy);
188     va_end(args_cpy);
189 
190     if (retval < 0) {
191       /* Some encoding error happened, not much we can do here, besides skipping/canceling this
192        * message. */
193       break;
194     }
195     else if ((uint)retval <= len_avail) {
196       /* Copy was successful. */
197       cstr->len += (uint)retval;
198       break;
199     }
200     else {
201       /* vsnprintf was not successful, due to lack of allocated space, retval contains expected
202        * length of the formatted string, use it to allocate required amount of memory. */
203       uint len_alloc = cstr->len + (uint)retval;
204       if (len_alloc >= len_max) {
205         /* Safe upper-limit, just in case... */
206         break;
207       }
208       clg_str_reserve(cstr, len_alloc);
209       len_avail = cstr->len_alloc - cstr->len;
210     }
211   }
212 }
213 
214 /** \} */
215 
216 /* -------------------------------------------------------------------- */
217 /** \name Internal Utilities
218  * \{ */
219 
220 enum eCLogColor {
221   COLOR_DEFAULT,
222   COLOR_RED,
223   COLOR_GREEN,
224   COLOR_YELLOW,
225 
226   COLOR_RESET,
227 };
228 #define COLOR_LEN (COLOR_RESET + 1)
229 
230 static const char *clg_color_table[COLOR_LEN] = {NULL};
231 
clg_color_table_init(bool use_color)232 static void clg_color_table_init(bool use_color)
233 {
234   for (int i = 0; i < COLOR_LEN; i++) {
235     clg_color_table[i] = "";
236   }
237   if (use_color) {
238     clg_color_table[COLOR_DEFAULT] = "\033[1;37m";
239     clg_color_table[COLOR_RED] = "\033[1;31m";
240     clg_color_table[COLOR_GREEN] = "\033[1;32m";
241     clg_color_table[COLOR_YELLOW] = "\033[1;33m";
242     clg_color_table[COLOR_RESET] = "\033[0m";
243   }
244 }
245 
246 static const char *clg_severity_str[CLG_SEVERITY_LEN] = {
247     [CLG_SEVERITY_INFO] = "INFO",
248     [CLG_SEVERITY_WARN] = "WARN",
249     [CLG_SEVERITY_ERROR] = "ERROR",
250     [CLG_SEVERITY_FATAL] = "FATAL",
251 };
252 
clg_severity_as_text(enum CLG_Severity severity)253 static const char *clg_severity_as_text(enum CLG_Severity severity)
254 {
255   bool ok = (unsigned int)severity < CLG_SEVERITY_LEN;
256   assert(ok);
257   if (ok) {
258     return clg_severity_str[severity];
259   }
260   else {
261     return "INVALID_SEVERITY";
262   }
263 }
264 
clg_severity_to_color(enum CLG_Severity severity)265 static enum eCLogColor clg_severity_to_color(enum CLG_Severity severity)
266 {
267   assert((unsigned int)severity < CLG_SEVERITY_LEN);
268   enum eCLogColor color = COLOR_DEFAULT;
269   switch (severity) {
270     case CLG_SEVERITY_INFO:
271       color = COLOR_DEFAULT;
272       break;
273     case CLG_SEVERITY_WARN:
274       color = COLOR_YELLOW;
275       break;
276     case CLG_SEVERITY_ERROR:
277     case CLG_SEVERITY_FATAL:
278       color = COLOR_RED;
279       break;
280     default:
281       /* should never get here. */
282       assert(false);
283   }
284   return color;
285 }
286 
287 /** \} */
288 
289 /* -------------------------------------------------------------------- */
290 /** \name Context Type Access
291  * \{ */
292 
293 /**
294  * Filter the identifier based on very basic globbing.
295  * - `foo` exact match of `foo`.
296  * - `foo.bar` exact match for `foo.bar`
297  * - `foo.*` match for `foo` & `foo.bar` & `foo.bar.baz`
298  * - `*` matches everything.
299  */
clg_ctx_filter_check(CLogContext * ctx,const char * identifier)300 static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
301 {
302   const int identifier_len = strlen(identifier);
303   for (uint i = 0; i < 2; i++) {
304     const CLG_IDFilter *flt = ctx->filters[i];
305     while (flt != NULL) {
306       const int len = strlen(flt->match);
307       if (STREQ(flt->match, "*") || ((len == identifier_len) && (STREQ(identifier, flt->match)))) {
308         return (bool)i;
309       }
310       if ((len >= 2) && (STREQLEN(".*", &flt->match[len - 2], 2))) {
311         if (((identifier_len == len - 2) && STREQLEN(identifier, flt->match, len - 2)) ||
312             ((identifier_len >= len - 1) && STREQLEN(identifier, flt->match, len - 1))) {
313           return (bool)i;
314         }
315       }
316       flt = flt->next;
317     }
318   }
319   return false;
320 }
321 
322 /**
323  * \note This should never be called per logging call.
324  * Searching is only to get an initial handle.
325  */
clg_ctx_type_find_by_name(CLogContext * ctx,const char * identifier)326 static CLG_LogType *clg_ctx_type_find_by_name(CLogContext *ctx, const char *identifier)
327 {
328   for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
329     if (STREQ(identifier, ty->identifier)) {
330       return ty;
331     }
332   }
333   return NULL;
334 }
335 
clg_ctx_type_register(CLogContext * ctx,const char * identifier)336 static CLG_LogType *clg_ctx_type_register(CLogContext *ctx, const char *identifier)
337 {
338   assert(clg_ctx_type_find_by_name(ctx, identifier) == NULL);
339   CLG_LogType *ty = MEM_callocN(sizeof(*ty), __func__);
340   ty->next = ctx->types;
341   ctx->types = ty;
342   strncpy(ty->identifier, identifier, sizeof(ty->identifier) - 1);
343   ty->ctx = ctx;
344   ty->level = ctx->default_type.level;
345 
346   if (clg_ctx_filter_check(ctx, ty->identifier)) {
347     ty->flag |= CLG_FLAG_USE;
348   }
349   return ty;
350 }
351 
clg_ctx_error_action(CLogContext * ctx)352 static void clg_ctx_error_action(CLogContext *ctx)
353 {
354   if (ctx->callbacks.error_fn != NULL) {
355     ctx->callbacks.error_fn(ctx->output_file);
356   }
357 }
358 
clg_ctx_fatal_action(CLogContext * ctx)359 static void clg_ctx_fatal_action(CLogContext *ctx)
360 {
361   if (ctx->callbacks.fatal_fn != NULL) {
362     ctx->callbacks.fatal_fn(ctx->output_file);
363   }
364   fflush(ctx->output_file);
365   abort();
366 }
367 
clg_ctx_backtrace(CLogContext * ctx)368 static void clg_ctx_backtrace(CLogContext *ctx)
369 {
370   /* Note: we avoid writing to 'FILE', for back-trace we make an exception,
371    * if necessary we could have a version of the callback that writes to file
372    * descriptor all at once. */
373   ctx->callbacks.backtrace_fn(ctx->output_file);
374   fflush(ctx->output_file);
375 }
376 
clg_timestamp_ticks_get(void)377 static uint64_t clg_timestamp_ticks_get(void)
378 {
379   uint64_t tick;
380 #if defined(_MSC_VER)
381   tick = GetTickCount64();
382 #else
383   struct timeval tv;
384   gettimeofday(&tv, NULL);
385   tick = tv.tv_sec * 1000 + tv.tv_usec / 1000;
386 #endif
387   return tick;
388 }
389 
390 /** \} */
391 
392 /* -------------------------------------------------------------------- */
393 /** \name Logging API
394  * \{ */
395 
write_timestamp(CLogStringBuf * cstr,const uint64_t timestamp_tick_start)396 static void write_timestamp(CLogStringBuf *cstr, const uint64_t timestamp_tick_start)
397 {
398   char timestamp_str[64];
399   const uint64_t timestamp = clg_timestamp_ticks_get() - timestamp_tick_start;
400   const uint timestamp_len = snprintf(timestamp_str,
401                                       sizeof(timestamp_str),
402                                       "%" PRIu64 ".%03u ",
403                                       timestamp / 1000,
404                                       (uint)(timestamp % 1000));
405   clg_str_append_with_len(cstr, timestamp_str, timestamp_len);
406 }
407 
write_severity(CLogStringBuf * cstr,enum CLG_Severity severity,bool use_color)408 static void write_severity(CLogStringBuf *cstr, enum CLG_Severity severity, bool use_color)
409 {
410   assert((unsigned int)severity < CLG_SEVERITY_LEN);
411   if (use_color) {
412     enum eCLogColor color = clg_severity_to_color(severity);
413     clg_str_append(cstr, clg_color_table[color]);
414     clg_str_append(cstr, clg_severity_as_text(severity));
415     clg_str_append(cstr, clg_color_table[COLOR_RESET]);
416   }
417   else {
418     clg_str_append(cstr, clg_severity_as_text(severity));
419   }
420 }
421 
write_type(CLogStringBuf * cstr,CLG_LogType * lg)422 static void write_type(CLogStringBuf *cstr, CLG_LogType *lg)
423 {
424   clg_str_append(cstr, " (");
425   clg_str_append(cstr, lg->identifier);
426   clg_str_append(cstr, "): ");
427 }
428 
write_file_line_fn(CLogStringBuf * cstr,const char * file_line,const char * fn,const bool use_basename)429 static void write_file_line_fn(CLogStringBuf *cstr,
430                                const char *file_line,
431                                const char *fn,
432                                const bool use_basename)
433 {
434   uint file_line_len = strlen(file_line);
435   if (use_basename) {
436     uint file_line_offset = file_line_len;
437     while (file_line_offset-- > 0) {
438       if (file_line[file_line_offset] == PATHSEP_CHAR) {
439         file_line_offset++;
440         break;
441       }
442     }
443     file_line += file_line_offset;
444     file_line_len -= file_line_offset;
445   }
446   clg_str_append_with_len(cstr, file_line, file_line_len);
447 
448   clg_str_append(cstr, " ");
449   clg_str_append(cstr, fn);
450   clg_str_append(cstr, ": ");
451 }
452 
CLG_log_str(CLG_LogType * lg,enum CLG_Severity severity,const char * file_line,const char * fn,const char * message)453 void CLG_log_str(CLG_LogType *lg,
454                  enum CLG_Severity severity,
455                  const char *file_line,
456                  const char *fn,
457                  const char *message)
458 {
459   CLogStringBuf cstr;
460   char cstr_stack_buf[CLOG_BUF_LEN_INIT];
461   clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
462 
463   if (lg->ctx->use_timestamp) {
464     write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
465   }
466 
467   write_severity(&cstr, severity, lg->ctx->use_color);
468   write_type(&cstr, lg);
469 
470   {
471     write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
472     clg_str_append(&cstr, message);
473   }
474   clg_str_append(&cstr, "\n");
475 
476   /* could be optional */
477   int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
478   (void)bytes_written;
479 
480   clg_str_free(&cstr);
481 
482   if (lg->ctx->callbacks.backtrace_fn) {
483     clg_ctx_backtrace(lg->ctx);
484   }
485 
486   if (severity == CLG_SEVERITY_FATAL) {
487     clg_ctx_fatal_action(lg->ctx);
488   }
489 }
490 
CLG_logf(CLG_LogType * lg,enum CLG_Severity severity,const char * file_line,const char * fn,const char * fmt,...)491 void CLG_logf(CLG_LogType *lg,
492               enum CLG_Severity severity,
493               const char *file_line,
494               const char *fn,
495               const char *fmt,
496               ...)
497 {
498   CLogStringBuf cstr;
499   char cstr_stack_buf[CLOG_BUF_LEN_INIT];
500   clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
501 
502   if (lg->ctx->use_timestamp) {
503     write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
504   }
505 
506   write_severity(&cstr, severity, lg->ctx->use_color);
507   write_type(&cstr, lg);
508 
509   {
510     write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename);
511 
512     va_list ap;
513     va_start(ap, fmt);
514     clg_str_vappendf(&cstr, fmt, ap);
515     va_end(ap);
516   }
517   clg_str_append(&cstr, "\n");
518 
519   /* could be optional */
520   int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
521   (void)bytes_written;
522 
523   clg_str_free(&cstr);
524 
525   if (lg->ctx->callbacks.backtrace_fn) {
526     clg_ctx_backtrace(lg->ctx);
527   }
528 
529   if (severity == CLG_SEVERITY_ERROR) {
530     clg_ctx_error_action(lg->ctx);
531   }
532 
533   if (severity == CLG_SEVERITY_FATAL) {
534     clg_ctx_fatal_action(lg->ctx);
535   }
536 }
537 
538 /** \} */
539 
540 /* -------------------------------------------------------------------- */
541 /** \name Logging Context API
542  * \{ */
543 
CLG_ctx_output_set(CLogContext * ctx,void * file_handle)544 static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
545 {
546   ctx->output_file = file_handle;
547   ctx->output = fileno(ctx->output_file);
548 #if defined(__unix__) || defined(__APPLE__)
549   ctx->use_color = isatty(ctx->output);
550 #elif defined(WIN32)
551   /* Windows Terminal supports color like the Linux terminals do while the standard console does
552    * not, the way to tell the two apart is to look at the `WT_SESSION` environment variable which
553    * will only be defined for Windows Terminal. */
554 
555   /* #getenv is used here rather than #BLI_getenv since it would be a bad level call
556    * and there are no benefits for using it in this context. */
557   ctx->use_color = isatty(ctx->output) && getenv("WT_SESSION");
558 #endif
559 }
560 
CLG_ctx_output_use_basename_set(CLogContext * ctx,int value)561 static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
562 {
563   ctx->use_basename = (bool)value;
564 }
565 
CLG_ctx_output_use_timestamp_set(CLogContext * ctx,int value)566 static void CLG_ctx_output_use_timestamp_set(CLogContext *ctx, int value)
567 {
568   ctx->use_timestamp = (bool)value;
569   if (ctx->use_timestamp) {
570     ctx->timestamp_tick_start = clg_timestamp_ticks_get();
571   }
572 }
573 
574 /** Action on error severity. */
CLT_ctx_error_fn_set(CLogContext * ctx,void (* error_fn)(void * file_handle))575 static void CLT_ctx_error_fn_set(CLogContext *ctx, void (*error_fn)(void *file_handle))
576 {
577   ctx->callbacks.error_fn = error_fn;
578 }
579 
580 /** Action on fatal severity. */
CLG_ctx_fatal_fn_set(CLogContext * ctx,void (* fatal_fn)(void * file_handle))581 static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
582 {
583   ctx->callbacks.fatal_fn = fatal_fn;
584 }
585 
CLG_ctx_backtrace_fn_set(CLogContext * ctx,void (* backtrace_fn)(void * file_handle))586 static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
587 {
588   ctx->callbacks.backtrace_fn = backtrace_fn;
589 }
590 
clg_ctx_type_filter_append(CLG_IDFilter ** flt_list,const char * type_match,int type_match_len)591 static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list,
592                                        const char *type_match,
593                                        int type_match_len)
594 {
595   if (type_match_len == 0) {
596     return;
597   }
598   CLG_IDFilter *flt = MEM_callocN(sizeof(*flt) + (type_match_len + 1), __func__);
599   flt->next = *flt_list;
600   *flt_list = flt;
601   memcpy(flt->match, type_match, type_match_len);
602   /* no need to null terminate since we calloc'd */
603 }
604 
CLG_ctx_type_filter_exclude(CLogContext * ctx,const char * type_match,int type_match_len)605 static void CLG_ctx_type_filter_exclude(CLogContext *ctx,
606                                         const char *type_match,
607                                         int type_match_len)
608 {
609   clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
610 }
611 
CLG_ctx_type_filter_include(CLogContext * ctx,const char * type_match,int type_match_len)612 static void CLG_ctx_type_filter_include(CLogContext *ctx,
613                                         const char *type_match,
614                                         int type_match_len)
615 {
616   clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
617 }
618 
CLG_ctx_level_set(CLogContext * ctx,int level)619 static void CLG_ctx_level_set(CLogContext *ctx, int level)
620 {
621   ctx->default_type.level = level;
622   for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
623     ty->level = level;
624   }
625 }
626 
CLG_ctx_init(void)627 static CLogContext *CLG_ctx_init(void)
628 {
629   CLogContext *ctx = MEM_callocN(sizeof(*ctx), __func__);
630 #ifdef WITH_CLOG_PTHREADS
631   pthread_mutex_init(&ctx->types_lock, NULL);
632 #endif
633   ctx->default_type.level = 1;
634   CLG_ctx_output_set(ctx, stdout);
635 
636   return ctx;
637 }
638 
CLG_ctx_free(CLogContext * ctx)639 static void CLG_ctx_free(CLogContext *ctx)
640 {
641   while (ctx->types != NULL) {
642     CLG_LogType *item = ctx->types;
643     ctx->types = item->next;
644     MEM_freeN(item);
645   }
646 
647   for (uint i = 0; i < 2; i++) {
648     while (ctx->filters[i] != NULL) {
649       CLG_IDFilter *item = ctx->filters[i];
650       ctx->filters[i] = item->next;
651       MEM_freeN(item);
652     }
653   }
654 #ifdef WITH_CLOG_PTHREADS
655   pthread_mutex_destroy(&ctx->types_lock);
656 #endif
657   MEM_freeN(ctx);
658 }
659 
660 /** \} */
661 
662 /* -------------------------------------------------------------------- */
663 /** \name Public Logging API
664  *
665  * Currently uses global context.
666  * \{ */
667 
668 /* We could support multiple at once, for now this seems not needed. */
669 static struct CLogContext *g_ctx = NULL;
670 
CLG_init(void)671 void CLG_init(void)
672 {
673   g_ctx = CLG_ctx_init();
674 
675   clg_color_table_init(g_ctx->use_color);
676 }
677 
CLG_exit(void)678 void CLG_exit(void)
679 {
680   CLG_ctx_free(g_ctx);
681 }
682 
CLG_output_set(void * file_handle)683 void CLG_output_set(void *file_handle)
684 {
685   CLG_ctx_output_set(g_ctx, file_handle);
686 }
687 
CLG_output_use_basename_set(int value)688 void CLG_output_use_basename_set(int value)
689 {
690   CLG_ctx_output_use_basename_set(g_ctx, value);
691 }
692 
CLG_output_use_timestamp_set(int value)693 void CLG_output_use_timestamp_set(int value)
694 {
695   CLG_ctx_output_use_timestamp_set(g_ctx, value);
696 }
697 
CLG_error_fn_set(void (* error_fn)(void * file_handle))698 void CLG_error_fn_set(void (*error_fn)(void *file_handle))
699 {
700   CLT_ctx_error_fn_set(g_ctx, error_fn);
701 }
702 
CLG_fatal_fn_set(void (* fatal_fn)(void * file_handle))703 void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
704 {
705   CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
706 }
707 
CLG_backtrace_fn_set(void (* fatal_fn)(void * file_handle))708 void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
709 {
710   CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
711 }
712 
CLG_type_filter_exclude(const char * type_match,int type_match_len)713 void CLG_type_filter_exclude(const char *type_match, int type_match_len)
714 {
715   CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
716 }
717 
CLG_type_filter_include(const char * type_match,int type_match_len)718 void CLG_type_filter_include(const char *type_match, int type_match_len)
719 {
720   CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
721 }
722 
CLG_level_set(int level)723 void CLG_level_set(int level)
724 {
725   CLG_ctx_level_set(g_ctx, level);
726 }
727 
728 /** \} */
729 
730 /* -------------------------------------------------------------------- */
731 /** \name Logging Reference API
732  *
733  * Use to avoid look-ups each time.
734  * \{ */
735 
CLG_logref_init(CLG_LogRef * clg_ref)736 void CLG_logref_init(CLG_LogRef *clg_ref)
737 {
738 #ifdef WITH_CLOG_PTHREADS
739   /* Only runs once when initializing a static type in most cases. */
740   pthread_mutex_lock(&g_ctx->types_lock);
741 #endif
742   if (clg_ref->type == NULL) {
743     CLG_LogType *clg_ty = clg_ctx_type_find_by_name(g_ctx, clg_ref->identifier);
744     if (clg_ty == NULL) {
745       clg_ty = clg_ctx_type_register(g_ctx, clg_ref->identifier);
746     }
747 #ifdef WITH_CLOG_PTHREADS
748     atomic_cas_ptr((void **)&clg_ref->type, clg_ref->type, clg_ty);
749 #else
750     clg_ref->type = clg_ty;
751 #endif
752   }
753 #ifdef WITH_CLOG_PTHREADS
754   pthread_mutex_unlock(&g_ctx->types_lock);
755 #endif
756 }
757 
CLG_color_support_get(CLG_LogRef * clg_ref)758 int CLG_color_support_get(CLG_LogRef *clg_ref)
759 {
760   if (clg_ref->type == NULL) {
761     CLG_logref_init(clg_ref);
762   }
763   return clg_ref->type->ctx->use_color;
764 }
765 
766 /** \} */
767