diff options
author | David Robillard <d@drobilla.net> | 2021-01-13 20:00:25 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2023-12-02 18:49:08 -0500 |
commit | 456bdeef35ffbfbdad7609e8b8a4ef71372786fd (patch) | |
tree | 25fabaa4f361f66a2bac860f06722e7f51776cbc /src/log.c | |
parent | fc2114a10769349d38b3215bc95ded855a2be5b6 (diff) | |
download | serd-456bdeef35ffbfbdad7609e8b8a4ef71372786fd.tar.gz serd-456bdeef35ffbfbdad7609e8b8a4ef71372786fd.tar.bz2 serd-456bdeef35ffbfbdad7609e8b8a4ef71372786fd.zip |
[WIP] Add extensible logging API
Diffstat (limited to 'src/log.c')
-rw-r--r-- | src/log.c | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..6ba885c6 --- /dev/null +++ b/src/log.c @@ -0,0 +1,325 @@ +// Copyright 2011-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#include "log.h" +#include "serd_config.h" +#include "world.h" + +#include "serd/attributes.h" +#include "serd/caret.h" +#include "serd/log.h" +#include "serd/node.h" +#include "serd/status.h" +#include "serd/string_view.h" +#include "serd/world.h" +#include "zix/attributes.h" + +#if USE_ISATTY +# include <unistd.h> +#endif + +#include <assert.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char* const log_level_strings[] = {"emergency", + "alert", + "critical", + "error", + "warning", + "note", + "info", + "debug"}; + +static int +level_color(const SerdLogLevel level) +{ + switch (level) { + case SERD_LOG_LEVEL_EMERGENCY: + case SERD_LOG_LEVEL_ALERT: + case SERD_LOG_LEVEL_CRITICAL: + case SERD_LOG_LEVEL_ERROR: + return 31; // Red + case SERD_LOG_LEVEL_WARNING: + return 33; // Yellow + case SERD_LOG_LEVEL_NOTICE: + case SERD_LOG_LEVEL_INFO: + case SERD_LOG_LEVEL_DEBUG: + break; + } + + return 1; // White +} + +static void +serd_ansi_start(const bool enabled, + FILE* const stream, + const int color, + const bool bold) +{ + if (enabled) { + fprintf(stream, bold ? "\033[0;%d;1m" : "\033[0;%dm", color); + } +} + +static void +serd_ansi_reset(const bool enabled, FILE* const stream) +{ + if (enabled) { + fprintf(stream, "\033[0m"); + fflush(stream); + } +} + +static bool +terminal_supports_color(const int fd) +{ + // https://no-color.org/ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + if (getenv("NO_COLOR")) { + return false; + } + + // https://bixense.com/clicolors/ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const char* const clicolor_force = getenv("CLICOLOR_FORCE"); + if (clicolor_force && !!strcmp(clicolor_force, "0")) { + return true; + } + + // https://bixense.com/clicolors/ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const char* const clicolor = getenv("CLICOLOR"); + if (clicolor && !strcmp(clicolor, "0")) { + return false; + } + +#if USE_ISATTY + // Assume support if stream is a TTY (blissfully ignoring termcap nightmares) + return isatty(fd); +#else + (void)stream; + return false; +#endif +} + +SerdStatus +serd_log_init(SerdLog* const log) +{ + log->func = NULL; + log->handle = NULL; + log->stderr_color = terminal_supports_color(1); + return SERD_SUCCESS; +} + +SerdStatus +serd_quiet_log_func(void* const handle, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const SerdStringView message) +{ + (void)handle; + (void)level; + (void)n_fields; + (void)fields; + (void)message; + return SERD_SUCCESS; +} + +static const char* +get_log_field(const size_t n_fields, + const SerdLogField* const fields, + const char* const key) +{ + for (size_t i = 0; i < n_fields; ++i) { + if (!strcmp(fields[i].key, key)) { + return fields[i].value; + } + } + + return NULL; +} + +void +serd_set_log_func(SerdWorld* const world, + const SerdLogFunc log_func, + void* const handle) +{ + assert(world); + + world->log.func = log_func; + world->log.handle = handle; +} + +SERD_LOG_FUNC(5, 0) +static SerdStatus +serd_default_vxlogf(const bool stderr_color, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const char* const fmt, + va_list args) +{ + assert(fmt); + + // Print input file and position prefix if available + const char* const file = get_log_field(n_fields, fields, "SERD_FILE"); + const char* const line = get_log_field(n_fields, fields, "SERD_LINE"); + const char* const col = get_log_field(n_fields, fields, "SERD_COL"); + if (file) { + serd_ansi_start(stderr_color, stderr, 1, true); + if (line && col) { + fprintf(stderr, "%s:%s:%s: ", file, line, col); + } else { + fprintf(stderr, "%s: ", file); + } + serd_ansi_reset(stderr_color, stderr); + } + + // Print GCC-style level prefix (error, warning, etc) + serd_ansi_start(stderr_color, stderr, level_color(level), true); + fprintf(stderr, "%s: ", log_level_strings[level]); + serd_ansi_reset(stderr_color, stderr); + + // Format and print the message itself + vfprintf(stderr, fmt, args); + + // Print clang-tidy style check suffix + const char* const check = get_log_field(n_fields, fields, "SERD_CHECK"); + if (check) { + fprintf(stderr, " [%s]", check); + } + + fprintf(stderr, "\n"); + return SERD_SUCCESS; +} + +SerdStatus +serd_vxlogf(const SerdWorld* const world, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const char* const fmt, + va_list args) +{ + assert(world); + assert(fmt); + + if (world->log.func) { + char message[512] = {0}; + const int r = vsnprintf(message, sizeof(message), fmt, args); + + return (r <= 0 || (size_t)r >= sizeof(message)) + ? SERD_BAD_ARG + : world->log.func(world->log.handle, + level, + n_fields, + fields, + serd_substring(message, (size_t)r)); + } + + return serd_default_vxlogf( + world->log.stderr_color, level, n_fields, fields, fmt, args); +} + +SerdStatus +serd_xlogf(const SerdWorld* const world, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const char* const fmt, + ...) +{ + assert(world); + assert(fmt); + + va_list args; // NOLINT(cppcoreguidelines-init-variables) + va_start(args, fmt); + + const SerdStatus st = serd_vxlogf(world, level, n_fields, fields, fmt, args); + + va_end(args); + return st; +} + +SerdStatus +serd_vlogf(const SerdWorld* const world, + const SerdLogLevel level, + const char* const fmt, + va_list args) +{ + assert(world); + assert(fmt); + + return serd_vxlogf(world, level, 0U, NULL, fmt, args); +} + +SerdStatus +serd_logf(const SerdWorld* const world, + const SerdLogLevel level, + const char* const fmt, + ...) +{ + assert(world); + assert(fmt); + + va_list args; // NOLINT(cppcoreguidelines-init-variables) + va_start(args, fmt); + + const SerdStatus st = serd_vxlogf(world, level, 0U, NULL, fmt, args); + + va_end(args); + return st; +} + +SerdStatus +serd_vlogf_at(const SerdWorld* ZIX_NONNULL world, + SerdLogLevel level, + const SerdCaret* caret, + const char* ZIX_NONNULL fmt, + va_list args) +{ + assert(world); + assert(fmt); + + const SerdNode* const document = caret ? serd_caret_document(caret) : NULL; + if (!document) { + return serd_vxlogf(world, level, 0U, NULL, fmt, args); + } + + char line[24]; + char col[24]; + snprintf(line, sizeof(line), "%u", serd_caret_line(caret)); + snprintf(col, sizeof(col), "%u", serd_caret_column(caret)); + + const SerdLogField fields[] = { + {"SERD_FILE", serd_node_string(document)}, + {"SERD_LINE", line}, + {"SERD_COL", col}, + }; + + return serd_vxlogf(world, level, 3, fields, fmt, args); +} + +SerdStatus +serd_logf_at(const SerdWorld* ZIX_NONNULL world, + SerdLogLevel level, + const SerdCaret* caret, + const char* ZIX_NONNULL fmt, + ...) +{ + assert(world); + assert(fmt); + + va_list args; // NOLINT(cppcoreguidelines-init-variables) + va_start(args, fmt); + + const SerdStatus st = serd_vlogf_at(world, level, caret, fmt, args); + + va_end(args); + return st; +} |