aboutsummaryrefslogtreecommitdiffstats
path: root/src/log.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-01-13 20:00:25 +0100
committerDavid Robillard <d@drobilla.net>2023-12-02 18:49:08 -0500
commit456bdeef35ffbfbdad7609e8b8a4ef71372786fd (patch)
tree25fabaa4f361f66a2bac860f06722e7f51776cbc /src/log.c
parentfc2114a10769349d38b3215bc95ded855a2be5b6 (diff)
downloadserd-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.c325
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;
+}