The numeric levels have no obvious meaning. This removes the distinction between severity and levels, instead there is a single list of named levels with defined meaning. Debug means information that's mainly useful for developers, and trace is for very verbose code execution tracing. Pull Request: https://projects.blender.org/blender/blender/pulls/140244
960 lines
24 KiB
C++
960 lines
24 KiB
C++
/* SPDX-FileCopyrightText: 2018-2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup intern_clog
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <cstdarg>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <mutex>
|
|
|
|
/* Disable for small single threaded programs
|
|
* to avoid having to link with pthreads. */
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
# include "atomic_ops.h"
|
|
# include <pthread.h>
|
|
#endif
|
|
|
|
/* For 'isatty' to check for color. */
|
|
#if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
|
|
# include <sys/time.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#if defined(_MSC_VER)
|
|
# include <Windows.h>
|
|
|
|
# include <VersionHelpers.h> /* This needs to be included after Windows.h. */
|
|
# include <io.h>
|
|
# if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
|
# define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
|
# endif
|
|
#endif
|
|
|
|
/* For printing timestamp. */
|
|
#define __STDC_FORMAT_MACROS
|
|
#include <cinttypes>
|
|
|
|
#include <algorithm>
|
|
|
|
/* Only other dependency (could use regular malloc too). */
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* own include. */
|
|
#include "CLG_log.h"
|
|
|
|
/* Local utility defines */
|
|
#define STREQ(a, b) (strcmp(a, b) == 0)
|
|
#define STREQLEN(a, b, n) (strncmp(a, b, n) == 0)
|
|
|
|
#ifdef _WIN32
|
|
# define PATHSEP_CHAR '\\'
|
|
#else
|
|
# define PATHSEP_CHAR '/'
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal Types
|
|
* \{ */
|
|
|
|
static std::mutex LOG_MUTEX;
|
|
|
|
struct CLG_IDFilter {
|
|
struct CLG_IDFilter *next;
|
|
/** Over alloc. */
|
|
char match[0];
|
|
};
|
|
|
|
struct CLogContext {
|
|
/** Single linked list of types. */
|
|
CLG_LogType *types;
|
|
/** Single linked list of references. */
|
|
CLG_LogRef *refs;
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
pthread_mutex_t types_lock;
|
|
#endif
|
|
|
|
/* exclude, include filters. */
|
|
CLG_IDFilter *filters[2];
|
|
bool use_color;
|
|
bool use_source;
|
|
bool use_basename;
|
|
bool use_timestamp;
|
|
bool use_memory;
|
|
|
|
/** Borrowed, not owned. */
|
|
int output;
|
|
FILE *output_file;
|
|
|
|
/** For timer (use_timestamp). */
|
|
uint64_t timestamp_tick_start;
|
|
|
|
/** For new types. */
|
|
struct {
|
|
CLG_Level level;
|
|
} default_type;
|
|
|
|
struct {
|
|
void (*error_fn)(void *file_handle);
|
|
void (*fatal_fn)(void *file_handle);
|
|
void (*backtrace_fn)(void *file_handle);
|
|
} callbacks;
|
|
};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mini Buffer Functionality
|
|
*
|
|
* Use so we can do a single call to write.
|
|
* \{ */
|
|
|
|
#define CLOG_BUF_LEN_INIT 512
|
|
|
|
struct CLogStringBuf {
|
|
char *data;
|
|
uint len;
|
|
uint len_alloc;
|
|
bool is_alloc;
|
|
};
|
|
|
|
static void clg_str_init(CLogStringBuf *cstr, char *buf_stack, uint buf_stack_len)
|
|
{
|
|
cstr->data = buf_stack;
|
|
cstr->len_alloc = buf_stack_len;
|
|
cstr->len = 0;
|
|
cstr->is_alloc = false;
|
|
}
|
|
|
|
static void clg_str_free(CLogStringBuf *cstr)
|
|
{
|
|
if (cstr->is_alloc) {
|
|
MEM_freeN(cstr->data);
|
|
}
|
|
}
|
|
|
|
static void clg_str_reserve(CLogStringBuf *cstr, const uint len)
|
|
{
|
|
if (len > cstr->len_alloc) {
|
|
cstr->len_alloc *= 2;
|
|
cstr->len_alloc = std::max(len, cstr->len_alloc);
|
|
|
|
if (cstr->is_alloc) {
|
|
cstr->data = static_cast<char *>(MEM_reallocN(cstr->data, cstr->len_alloc));
|
|
}
|
|
else {
|
|
/* Copy the static buffer. */
|
|
char *data = MEM_malloc_arrayN<char>(cstr->len_alloc, __func__);
|
|
memcpy(data, cstr->data, cstr->len);
|
|
cstr->data = data;
|
|
cstr->is_alloc = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void clg_str_append_with_len(CLogStringBuf *cstr, const char *str, const uint len)
|
|
{
|
|
uint len_next = cstr->len + len;
|
|
clg_str_reserve(cstr, len_next);
|
|
char *str_dst = cstr->data + cstr->len;
|
|
memcpy(str_dst, str, len);
|
|
#if 0 /* no need. */
|
|
str_dst[len] = '\0';
|
|
#endif
|
|
cstr->len = len_next;
|
|
}
|
|
|
|
static void clg_str_append(CLogStringBuf *cstr, const char *str)
|
|
{
|
|
clg_str_append_with_len(cstr, str, strlen(str));
|
|
}
|
|
|
|
static void clg_str_append_char(CLogStringBuf *cstr, const char c, const uint len)
|
|
{
|
|
uint len_next = cstr->len + len;
|
|
clg_str_reserve(cstr, len_next);
|
|
char *str_dst = cstr->data + cstr->len;
|
|
memset(str_dst, c, len);
|
|
#if 0 /* no need. */
|
|
str_dst[len] = '\0';
|
|
#endif
|
|
cstr->len = len_next;
|
|
}
|
|
|
|
ATTR_PRINTF_FORMAT(2, 0)
|
|
static void clg_str_vappendf(CLogStringBuf *cstr, const char *format, va_list args)
|
|
{
|
|
/* Use limit because windows may use '-1' for a formatting error. */
|
|
const uint len_max = 65535;
|
|
uint len_avail = cstr->len_alloc - cstr->len;
|
|
while (true) {
|
|
va_list args_cpy;
|
|
va_copy(args_cpy, args);
|
|
int retval = vsnprintf(cstr->data + cstr->len, len_avail, format, args_cpy);
|
|
va_end(args_cpy);
|
|
|
|
if (retval < 0) {
|
|
/* Some encoding error happened, not much we can do here, besides skipping/canceling this
|
|
* message. */
|
|
break;
|
|
}
|
|
|
|
if ((uint)retval <= len_avail) {
|
|
/* Copy was successful. */
|
|
cstr->len += (uint)retval;
|
|
break;
|
|
}
|
|
|
|
/* `vsnprintf` was not successful, due to lack of allocated space, `retval` contains expected
|
|
* length of the formatted string, use it to allocate required amount of memory. */
|
|
uint len_alloc = cstr->len + (uint)retval;
|
|
if (len_alloc >= len_max) {
|
|
/* Safe upper-limit, just in case... */
|
|
break;
|
|
}
|
|
|
|
clg_str_reserve(cstr, len_alloc);
|
|
len_avail = cstr->len_alloc - cstr->len;
|
|
}
|
|
}
|
|
|
|
static void clg_str_indent_multiline(CLogStringBuf *cstr, const uint indent_len)
|
|
{
|
|
/* If there are multiple lines, indent them the same as the first line for readability. */
|
|
if (indent_len < 2) {
|
|
return;
|
|
}
|
|
|
|
uint num_newlines = 0;
|
|
for (uint i = 0; i < cstr->len; i++) {
|
|
if (cstr->data[i] == '\n') {
|
|
num_newlines++;
|
|
}
|
|
}
|
|
if (num_newlines == 0) {
|
|
return;
|
|
}
|
|
|
|
const char *old_data = cstr->data;
|
|
const uint old_len = cstr->len;
|
|
const bool old_is_alloc = cstr->is_alloc;
|
|
|
|
cstr->len_alloc = cstr->len + (num_newlines * indent_len);
|
|
cstr->len = 0;
|
|
cstr->data = MEM_malloc_arrayN<char>(cstr->len_alloc, __func__);
|
|
cstr->is_alloc = true;
|
|
|
|
for (uint i = 0; i < old_len; i++) {
|
|
cstr->data[cstr->len++] = old_data[i];
|
|
if (old_data[i] == '\n') {
|
|
memset(cstr->data + cstr->len, ' ', indent_len);
|
|
cstr->data[cstr->len + indent_len - 2] = '|';
|
|
cstr->len += indent_len;
|
|
}
|
|
}
|
|
|
|
if (old_is_alloc) {
|
|
MEM_freeN(old_data);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal Utilities
|
|
* \{ */
|
|
|
|
enum eCLogColor {
|
|
COLOR_DEFAULT,
|
|
COLOR_RED,
|
|
COLOR_GREEN,
|
|
COLOR_YELLOW,
|
|
COLOR_DIM,
|
|
|
|
COLOR_RESET,
|
|
};
|
|
#define COLOR_LEN (COLOR_RESET + 1)
|
|
|
|
static const char *clg_color_table[COLOR_LEN] = {nullptr};
|
|
#ifdef _WIN32
|
|
static DWORD clg_previous_console_mode = 0;
|
|
#endif
|
|
|
|
static void clg_color_table_init(bool use_color)
|
|
{
|
|
for (int i = 0; i < COLOR_LEN; i++) {
|
|
clg_color_table[i] = "";
|
|
}
|
|
if (use_color) {
|
|
clg_color_table[COLOR_DEFAULT] = "\033[1;37m";
|
|
clg_color_table[COLOR_RED] = "\033[1;31m";
|
|
clg_color_table[COLOR_GREEN] = "\033[1;32m";
|
|
clg_color_table[COLOR_YELLOW] = "\033[1;33m";
|
|
clg_color_table[COLOR_DIM] = "\033[2;37m";
|
|
clg_color_table[COLOR_RESET] = "\033[0m";
|
|
}
|
|
}
|
|
|
|
static const char *clg_level_as_text(enum CLG_Level level)
|
|
{
|
|
switch (level) {
|
|
case CLG_LEVEL_FATAL:
|
|
return "FATAL";
|
|
case CLG_LEVEL_ERROR:
|
|
return "ERROR";
|
|
case CLG_LEVEL_WARN:
|
|
return "WARNING";
|
|
case CLG_LEVEL_INFO:
|
|
return "INFO";
|
|
case CLG_LEVEL_DEBUG:
|
|
return "DEBUG";
|
|
case CLG_LEVEL_TRACE:
|
|
return "TRACE";
|
|
}
|
|
|
|
return "INVALID_LEVEL";
|
|
}
|
|
|
|
static enum eCLogColor clg_level_to_color(enum CLG_Level level)
|
|
{
|
|
switch (level) {
|
|
case CLG_LEVEL_FATAL:
|
|
case CLG_LEVEL_ERROR:
|
|
return COLOR_RED;
|
|
case CLG_LEVEL_WARN:
|
|
return COLOR_YELLOW;
|
|
case CLG_LEVEL_INFO:
|
|
case CLG_LEVEL_DEBUG:
|
|
case CLG_LEVEL_TRACE:
|
|
return COLOR_DEFAULT;
|
|
}
|
|
/* should never get here. */
|
|
assert(false);
|
|
return COLOR_DEFAULT;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Context Type Access
|
|
* \{ */
|
|
|
|
/**
|
|
* Filter the identifier based on very basic globbing.
|
|
* - `foo` matches everything starting with `foo`.
|
|
* - `*bar*` match for `foo.bar` & `baz.bar` & `foo.barbaz`
|
|
* - `*` matches everything.
|
|
*/
|
|
static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
|
|
{
|
|
if (ctx->filters[0] == nullptr && ctx->filters[1] == nullptr &&
|
|
ctx->default_type.level >= CLG_LEVEL_INFO)
|
|
{
|
|
/* No filters but level specified? Match everything. */
|
|
return true;
|
|
}
|
|
|
|
const size_t identifier_len = strlen(identifier);
|
|
for (uint i = 0; i < 2; i++) {
|
|
const CLG_IDFilter *flt = ctx->filters[i];
|
|
while (flt != nullptr) {
|
|
const size_t len = strlen(flt->match);
|
|
if (STREQ(flt->match, "*") ||
|
|
((len <= identifier_len) && (STREQLEN(identifier, flt->match, len))))
|
|
{
|
|
return (bool)i;
|
|
}
|
|
if (flt->match[0] == '*' && flt->match[len - 1] == '*') {
|
|
char *match = MEM_calloc_arrayN<char>(len - 1, __func__);
|
|
memcpy(match, flt->match + 1, len - 2);
|
|
const bool success = (strstr(identifier, match) != nullptr);
|
|
MEM_freeN(match);
|
|
if (success) {
|
|
return (bool)i;
|
|
}
|
|
}
|
|
else if ((len >= 2) && (STREQLEN(".*", &flt->match[len - 2], 2))) {
|
|
/* `foo.*` and `foo.bar.*` support kept for backwards compatibility.
|
|
* `foo` and `foo.bar` now do the same thing. */
|
|
if (((identifier_len == len - 2) && STREQLEN(identifier, flt->match, len - 2)) ||
|
|
((identifier_len >= len - 1) && STREQLEN(identifier, flt->match, len - 1)))
|
|
{
|
|
return (bool)i;
|
|
}
|
|
}
|
|
flt = flt->next;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* \note This should never be called per logging call.
|
|
* Searching is only to get an initial handle.
|
|
*/
|
|
static CLG_LogType *clg_ctx_type_find_by_name(CLogContext *ctx, const char *identifier)
|
|
{
|
|
for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
|
|
if (STREQ(identifier, ty->identifier)) {
|
|
return ty;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static CLG_LogType *clg_ctx_type_register(CLogContext *ctx, const char *identifier)
|
|
{
|
|
assert(clg_ctx_type_find_by_name(ctx, identifier) == nullptr);
|
|
CLG_LogType *ty = MEM_callocN<CLG_LogType>(__func__);
|
|
ty->next = ctx->types;
|
|
ctx->types = ty;
|
|
strncpy(ty->identifier, identifier, sizeof(ty->identifier) - 1);
|
|
ty->ctx = ctx;
|
|
|
|
if (clg_ctx_filter_check(ctx, ty->identifier)) {
|
|
ty->level = ctx->default_type.level;
|
|
}
|
|
else {
|
|
ty->level = std::min(ctx->default_type.level, CLG_LEVEL_WARN);
|
|
}
|
|
|
|
return ty;
|
|
}
|
|
|
|
static void clg_ctx_error_action(CLogContext *ctx)
|
|
{
|
|
if (ctx->callbacks.error_fn != nullptr) {
|
|
ctx->callbacks.error_fn(ctx->output_file);
|
|
}
|
|
}
|
|
|
|
static void clg_ctx_fatal_action(CLogContext *ctx)
|
|
{
|
|
if (ctx->callbacks.fatal_fn != nullptr) {
|
|
ctx->callbacks.fatal_fn(ctx->output_file);
|
|
}
|
|
fflush(ctx->output_file);
|
|
abort();
|
|
}
|
|
|
|
static void clg_ctx_backtrace(CLogContext *ctx)
|
|
{
|
|
/* NOTE: we avoid writing to 'FILE', for back-trace we make an exception,
|
|
* if necessary we could have a version of the callback that writes to file
|
|
* descriptor all at once. */
|
|
ctx->callbacks.backtrace_fn(ctx->output_file);
|
|
fflush(ctx->output_file);
|
|
}
|
|
|
|
static uint64_t clg_timestamp_ticks_get()
|
|
{
|
|
uint64_t tick;
|
|
#if defined(_MSC_VER)
|
|
tick = GetTickCount64();
|
|
#else
|
|
struct timeval tv;
|
|
gettimeofday(&tv, nullptr);
|
|
tick = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
#endif
|
|
return tick;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Logging API
|
|
* \{ */
|
|
|
|
static void write_timestamp(CLogStringBuf *cstr, const uint64_t timestamp_tick_start)
|
|
{
|
|
char timestamp_str[128];
|
|
const uint64_t timestamp = clg_timestamp_ticks_get() - timestamp_tick_start;
|
|
const int h = int(timestamp / (1000 * 60 * 60));
|
|
const int m = int((timestamp / (1000 * 60)) % 60);
|
|
const int s = int((timestamp / 1000) % 60);
|
|
const int r = int(timestamp % 1000);
|
|
|
|
const uint timestamp_len =
|
|
(h > 0) ?
|
|
snprintf(timestamp_str, sizeof(timestamp_str), "%.2d:%.2d:%.2d.%.3d ", h, m, s, r) :
|
|
snprintf(timestamp_str, sizeof(timestamp_str), "%.2d:%.2d.%.3d ", m, s, r);
|
|
|
|
clg_str_append_with_len(cstr, timestamp_str, timestamp_len);
|
|
}
|
|
|
|
static void write_memory(CLogStringBuf *cstr)
|
|
{
|
|
const uint64_t mem_in_use = MEM_get_memory_in_use() / (1024 * 1024);
|
|
char memory_str[128];
|
|
const uint len = snprintf(memory_str, sizeof(memory_str), "%dM", (int)mem_in_use);
|
|
|
|
clg_str_append_with_len(cstr, memory_str, len);
|
|
|
|
const uint memory_align_width = 5;
|
|
const uint num_spaces = (len < memory_align_width) ? memory_align_width - len : 0;
|
|
clg_str_append_char(cstr, ' ', num_spaces + 2);
|
|
}
|
|
|
|
static void write_level(CLogStringBuf *cstr, enum CLG_Level level, bool use_color)
|
|
{
|
|
if (level >= CLG_LEVEL_INFO) {
|
|
return;
|
|
}
|
|
|
|
if (use_color) {
|
|
enum eCLogColor color = clg_level_to_color(level);
|
|
clg_str_append(cstr, clg_color_table[color]);
|
|
clg_str_append(cstr, clg_level_as_text(level));
|
|
clg_str_append(cstr, clg_color_table[COLOR_RESET]);
|
|
}
|
|
else {
|
|
clg_str_append(cstr, clg_level_as_text(level));
|
|
}
|
|
|
|
clg_str_append(cstr, " ");
|
|
}
|
|
|
|
static void write_type(CLogStringBuf *cstr, const CLG_LogType *lg)
|
|
{
|
|
const uint len = strlen(lg->identifier);
|
|
clg_str_append_with_len(cstr, lg->identifier, len);
|
|
|
|
const uint type_align_width = 16;
|
|
const uint num_spaces = (len < type_align_width) ? type_align_width - len : 0;
|
|
clg_str_append_char(cstr, ' ', num_spaces + 1);
|
|
}
|
|
|
|
static void write_file_line_fn(CLogStringBuf *cstr,
|
|
const char *file_line,
|
|
const char *fn,
|
|
const bool use_basename,
|
|
const bool use_color)
|
|
{
|
|
if (use_color) {
|
|
clg_str_append(cstr, clg_color_table[COLOR_DIM]);
|
|
}
|
|
|
|
uint file_line_len = strlen(file_line);
|
|
if (use_basename) {
|
|
uint file_line_offset = file_line_len;
|
|
while (file_line_offset-- > 0) {
|
|
if (file_line[file_line_offset] == PATHSEP_CHAR) {
|
|
file_line_offset++;
|
|
break;
|
|
}
|
|
}
|
|
file_line += file_line_offset;
|
|
file_line_len -= file_line_offset;
|
|
}
|
|
clg_str_append_with_len(cstr, file_line, file_line_len);
|
|
|
|
clg_str_append(cstr, " ");
|
|
clg_str_append(cstr, fn);
|
|
|
|
if (use_color) {
|
|
clg_str_append(cstr, clg_color_table[COLOR_RESET]);
|
|
}
|
|
}
|
|
|
|
void CLG_log_str(const CLG_LogType *lg,
|
|
enum CLG_Level level,
|
|
const char *file_line,
|
|
const char *fn,
|
|
const char *message)
|
|
{
|
|
CLogStringBuf cstr;
|
|
char cstr_stack_buf[CLOG_BUF_LEN_INIT];
|
|
clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
|
|
|
|
if (lg->ctx->use_timestamp) {
|
|
write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
|
|
}
|
|
if (lg->ctx->use_memory) {
|
|
write_memory(&cstr);
|
|
}
|
|
write_type(&cstr, lg);
|
|
|
|
clg_str_append(&cstr, "| ");
|
|
|
|
const uint64_t multiline_indent_len = cstr.len;
|
|
|
|
write_level(&cstr, level, lg->ctx->use_color);
|
|
|
|
clg_str_append(&cstr, message);
|
|
|
|
if (lg->ctx->use_source) {
|
|
clg_str_append(&cstr, "\n");
|
|
write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename, lg->ctx->use_color);
|
|
}
|
|
|
|
clg_str_indent_multiline(&cstr, multiline_indent_len);
|
|
|
|
clg_str_append(&cstr, "\n");
|
|
|
|
/* Output could be optional. */
|
|
{
|
|
/* Mutex to avoid garbled output with threads and multi line output. */
|
|
std::scoped_lock lock(LOG_MUTEX);
|
|
int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
|
|
(void)bytes_written;
|
|
}
|
|
|
|
clg_str_free(&cstr);
|
|
|
|
if (lg->ctx->callbacks.backtrace_fn) {
|
|
clg_ctx_backtrace(lg->ctx);
|
|
}
|
|
|
|
if (level == CLG_LEVEL_FATAL) {
|
|
clg_ctx_fatal_action(lg->ctx);
|
|
}
|
|
}
|
|
|
|
void CLG_logf(const CLG_LogType *lg,
|
|
enum CLG_Level level,
|
|
const char *file_line,
|
|
const char *fn,
|
|
const char *format,
|
|
...)
|
|
{
|
|
CLogStringBuf cstr;
|
|
char cstr_stack_buf[CLOG_BUF_LEN_INIT];
|
|
clg_str_init(&cstr, cstr_stack_buf, sizeof(cstr_stack_buf));
|
|
|
|
if (lg->ctx->use_timestamp) {
|
|
write_timestamp(&cstr, lg->ctx->timestamp_tick_start);
|
|
}
|
|
if (lg->ctx->use_memory) {
|
|
write_memory(&cstr);
|
|
}
|
|
write_type(&cstr, lg);
|
|
|
|
clg_str_append(&cstr, "| ");
|
|
|
|
const uint64_t multiline_indent_len = cstr.len;
|
|
|
|
write_level(&cstr, level, lg->ctx->use_color);
|
|
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
clg_str_vappendf(&cstr, format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
if (lg->ctx->use_source) {
|
|
clg_str_append(&cstr, "\n");
|
|
write_file_line_fn(&cstr, file_line, fn, lg->ctx->use_basename, lg->ctx->use_color);
|
|
}
|
|
|
|
clg_str_indent_multiline(&cstr, multiline_indent_len);
|
|
|
|
clg_str_append(&cstr, "\n");
|
|
|
|
/* Output could be optional. */
|
|
{
|
|
/* Mutex to avoid garbled output with threads and multi line output. */
|
|
std::scoped_lock lock(LOG_MUTEX);
|
|
int bytes_written = write(lg->ctx->output, cstr.data, cstr.len);
|
|
(void)bytes_written;
|
|
}
|
|
|
|
clg_str_free(&cstr);
|
|
|
|
if (lg->ctx->callbacks.backtrace_fn) {
|
|
clg_ctx_backtrace(lg->ctx);
|
|
}
|
|
|
|
if (level == CLG_LEVEL_ERROR) {
|
|
clg_ctx_error_action(lg->ctx);
|
|
}
|
|
|
|
if (level == CLG_LEVEL_FATAL) {
|
|
clg_ctx_fatal_action(lg->ctx);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Logging Context API
|
|
* \{ */
|
|
|
|
static void CLG_ctx_output_set(CLogContext *ctx, void *file_handle)
|
|
{
|
|
ctx->output_file = static_cast<FILE *>(file_handle);
|
|
ctx->output = fileno(ctx->output_file);
|
|
#if defined(__unix__) || defined(__APPLE__)
|
|
ctx->use_color = isatty(ctx->output);
|
|
#elif defined(WIN32)
|
|
/* As of Windows 10 build 18298 all the standard consoles supports color
|
|
* like the Linux Terminal do, but it needs to be turned on.
|
|
* To turn on colors we need to enable virtual terminal processing by passing the flag
|
|
* ENABLE_VIRTUAL_TERMINAL_PROCESSING into SetConsoleMode.
|
|
* If the system doesn't support virtual terminal processing it will fail silently and the flag
|
|
* will not be set. */
|
|
|
|
GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &clg_previous_console_mode);
|
|
|
|
ctx->use_color = 0;
|
|
if (IsWindows10OrGreater() && isatty(ctx->output)) {
|
|
DWORD mode = clg_previous_console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
if (SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), mode)) {
|
|
ctx->use_color = 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void CLG_ctx_output_use_source_set(CLogContext *ctx, int value)
|
|
{
|
|
ctx->use_source = (bool)value;
|
|
}
|
|
|
|
static void CLG_ctx_output_use_basename_set(CLogContext *ctx, int value)
|
|
{
|
|
ctx->use_basename = (bool)value;
|
|
}
|
|
|
|
static void CLG_ctx_output_use_timestamp_set(CLogContext *ctx, int value)
|
|
{
|
|
ctx->use_timestamp = (bool)value;
|
|
if (ctx->use_timestamp) {
|
|
ctx->timestamp_tick_start = clg_timestamp_ticks_get();
|
|
}
|
|
}
|
|
|
|
static void CLG_ctx_output_use_memory_set(CLogContext *ctx, int value)
|
|
{
|
|
ctx->use_memory = (bool)value;
|
|
}
|
|
|
|
/** Action on error level. */
|
|
static void CLT_ctx_error_fn_set(CLogContext *ctx, void (*error_fn)(void *file_handle))
|
|
{
|
|
ctx->callbacks.error_fn = error_fn;
|
|
}
|
|
|
|
/** Action on fatal level. */
|
|
static void CLG_ctx_fatal_fn_set(CLogContext *ctx, void (*fatal_fn)(void *file_handle))
|
|
{
|
|
ctx->callbacks.fatal_fn = fatal_fn;
|
|
}
|
|
|
|
static void CLG_ctx_backtrace_fn_set(CLogContext *ctx, void (*backtrace_fn)(void *file_handle))
|
|
{
|
|
ctx->callbacks.backtrace_fn = backtrace_fn;
|
|
}
|
|
|
|
static void clg_ctx_type_filter_append(CLG_IDFilter **flt_list,
|
|
const char *type_match,
|
|
int type_match_len)
|
|
{
|
|
if (type_match_len == 0) {
|
|
return;
|
|
}
|
|
CLG_IDFilter *flt = static_cast<CLG_IDFilter *>(
|
|
MEM_callocN(sizeof(*flt) + type_match_len + 1, __func__));
|
|
flt->next = *flt_list;
|
|
*flt_list = flt;
|
|
memcpy(flt->match, type_match, type_match_len);
|
|
/* no need to null terminate since we calloc'd */
|
|
}
|
|
|
|
static void CLG_ctx_type_filter_exclude(CLogContext *ctx,
|
|
const char *type_match,
|
|
int type_match_len)
|
|
{
|
|
clg_ctx_type_filter_append(&ctx->filters[0], type_match, type_match_len);
|
|
}
|
|
|
|
static void CLG_ctx_type_filter_include(CLogContext *ctx,
|
|
const char *type_match,
|
|
int type_match_len)
|
|
{
|
|
clg_ctx_type_filter_append(&ctx->filters[1], type_match, type_match_len);
|
|
if (ctx->default_type.level <= CLG_LEVEL_WARN) {
|
|
ctx->default_type.level = CLG_LEVEL_INFO;
|
|
}
|
|
}
|
|
|
|
static void CLG_ctx_level_set(CLogContext *ctx, CLG_Level level)
|
|
{
|
|
ctx->default_type.level = level;
|
|
for (CLG_LogType *ty = ctx->types; ty; ty = ty->next) {
|
|
ty->level = level;
|
|
}
|
|
}
|
|
|
|
static CLogContext *CLG_ctx_init()
|
|
{
|
|
CLogContext *ctx = MEM_callocN<CLogContext>(__func__);
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
pthread_mutex_init(&ctx->types_lock, nullptr);
|
|
#endif
|
|
ctx->default_type.level = CLG_LEVEL_WARN;
|
|
ctx->use_source = true;
|
|
CLG_ctx_output_set(ctx, stdout);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static void CLG_ctx_free(CLogContext *ctx)
|
|
{
|
|
#if defined(WIN32)
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), clg_previous_console_mode);
|
|
#endif
|
|
while (ctx->types != nullptr) {
|
|
CLG_LogType *item = ctx->types;
|
|
ctx->types = item->next;
|
|
MEM_freeN(item);
|
|
}
|
|
|
|
while (ctx->refs != nullptr) {
|
|
CLG_LogRef *item = ctx->refs;
|
|
ctx->refs = item->next;
|
|
item->type = nullptr;
|
|
}
|
|
|
|
for (uint i = 0; i < 2; i++) {
|
|
while (ctx->filters[i] != nullptr) {
|
|
CLG_IDFilter *item = ctx->filters[i];
|
|
ctx->filters[i] = item->next;
|
|
MEM_freeN(item);
|
|
}
|
|
}
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
pthread_mutex_destroy(&ctx->types_lock);
|
|
#endif
|
|
MEM_freeN(ctx);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public Logging API
|
|
*
|
|
* Currently uses global context.
|
|
* \{ */
|
|
|
|
/* We could support multiple at once, for now this seems not needed. */
|
|
static struct CLogContext *g_ctx = nullptr;
|
|
|
|
void CLG_init()
|
|
{
|
|
g_ctx = CLG_ctx_init();
|
|
|
|
clg_color_table_init(g_ctx->use_color);
|
|
}
|
|
|
|
void CLG_exit()
|
|
{
|
|
CLG_ctx_free(g_ctx);
|
|
}
|
|
|
|
void CLG_output_set(void *file_handle)
|
|
{
|
|
CLG_ctx_output_set(g_ctx, file_handle);
|
|
}
|
|
|
|
void CLG_output_use_source_set(int value)
|
|
{
|
|
CLG_ctx_output_use_source_set(g_ctx, value);
|
|
}
|
|
|
|
void CLG_output_use_basename_set(int value)
|
|
{
|
|
CLG_ctx_output_use_basename_set(g_ctx, value);
|
|
}
|
|
|
|
void CLG_output_use_timestamp_set(int value)
|
|
{
|
|
CLG_ctx_output_use_timestamp_set(g_ctx, value);
|
|
}
|
|
|
|
void CLG_output_use_memory_set(int value)
|
|
{
|
|
CLG_ctx_output_use_memory_set(g_ctx, value);
|
|
}
|
|
|
|
void CLG_error_fn_set(void (*error_fn)(void *file_handle))
|
|
{
|
|
CLT_ctx_error_fn_set(g_ctx, error_fn);
|
|
}
|
|
|
|
void CLG_fatal_fn_set(void (*fatal_fn)(void *file_handle))
|
|
{
|
|
CLG_ctx_fatal_fn_set(g_ctx, fatal_fn);
|
|
}
|
|
|
|
void CLG_backtrace_fn_set(void (*fatal_fn)(void *file_handle))
|
|
{
|
|
CLG_ctx_backtrace_fn_set(g_ctx, fatal_fn);
|
|
}
|
|
|
|
void CLG_type_filter_exclude(const char *type_match, int type_match_len)
|
|
{
|
|
CLG_ctx_type_filter_exclude(g_ctx, type_match, type_match_len);
|
|
}
|
|
|
|
void CLG_type_filter_include(const char *type_match, int type_match_len)
|
|
{
|
|
CLG_ctx_type_filter_include(g_ctx, type_match, type_match_len);
|
|
}
|
|
|
|
void CLG_level_set(CLG_Level level)
|
|
{
|
|
CLG_ctx_level_set(g_ctx, level);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Logging Reference API
|
|
*
|
|
* Use to avoid look-ups each time.
|
|
* \{ */
|
|
|
|
void CLG_logref_init(CLG_LogRef *clg_ref)
|
|
{
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
/* Only runs once when initializing a static type in most cases. */
|
|
pthread_mutex_lock(&g_ctx->types_lock);
|
|
#endif
|
|
if (clg_ref->type == nullptr) {
|
|
/* Add to the refs list so we can nullptr the pointers to 'type' when CLG_exit() is called. */
|
|
clg_ref->next = g_ctx->refs;
|
|
g_ctx->refs = clg_ref;
|
|
|
|
CLG_LogType *clg_ty = clg_ctx_type_find_by_name(g_ctx, clg_ref->identifier);
|
|
if (clg_ty == nullptr) {
|
|
clg_ty = clg_ctx_type_register(g_ctx, clg_ref->identifier);
|
|
}
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
atomic_cas_ptr((void **)&clg_ref->type, clg_ref->type, clg_ty);
|
|
#else
|
|
clg_ref->type = clg_ty;
|
|
#endif
|
|
}
|
|
#ifdef WITH_CLOG_PTHREADS
|
|
pthread_mutex_unlock(&g_ctx->types_lock);
|
|
#endif
|
|
}
|
|
|
|
int CLG_color_support_get(CLG_LogRef *clg_ref)
|
|
{
|
|
if (clg_ref->type == nullptr) {
|
|
CLG_logref_init(clg_ref);
|
|
}
|
|
return clg_ref->type->ctx->use_color;
|
|
}
|
|
|
|
/** \} */
|