From 1bcd7f2f7ac4bc332195fdeadefc43588fcd1b78 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 19 Dec 2018 23:10:58 +0000 Subject: [PATCH] Add a simple logging framework It's not very pretty now, but it will get better. Signed-off-by: Yuxuan Shui --- src/compton.c | 5 + src/log.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ src/log.h | 82 ++++++++++--- src/meson.build | 2 +- 4 files changed, 374 insertions(+), 14 deletions(-) create mode 100644 src/log.c diff --git a/src/compton.c b/src/compton.c index bb833f66..15b866c8 100644 --- a/src/compton.c +++ b/src/compton.c @@ -3568,6 +3568,9 @@ session_init(session_t *ps_old, int argc, char **argv) { #endif }; + log_init_tls(); + log_add_target_tls(stderr_logger_new()); + // Allocate a session and copy default values into it session_t *ps = cmalloc(session_t); *ps = s_def; @@ -4053,6 +4056,8 @@ session_destroy(session_t *ps) { if (ps == ps_g) ps_g = NULL; + + log_deinit_tls(); } /* diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..839071ed --- /dev/null +++ b/src/log.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_OPENGL +#include +#endif + +#include "compiler.h" +#include "log.h" +#include "utils.h" + +thread_local struct log *tls_logger; + +struct log_target; + +struct log { + struct log_target *head; + + int log_level; +}; + +struct log_target { + const struct log_ops *ops; + struct log_target *next; +}; + +struct log_ops { + void (*write)(struct log_target *, const char *, size_t); + void (*destroy)(struct log_target *); + + /// Additional strings to print around the log_level string + const char *(*colorize_begin)(enum log_level); + const char *(*colorize_end)(enum log_level); +}; + +/// Helper function for writing null terminated strings +static void log_strwrite(struct log_target *tgt, const char *str) { + return tgt->ops->write(tgt, str, strlen(str)); +} + +static attr_const const char *log_level_to_string(enum log_level level) { + switch (level) { + case LOG_LEVEL_TRACE: return "TRACE"; + case LOG_LEVEL_DEBUG: return "DEBUG"; + case LOG_LEVEL_INFO: return "INFO"; + case LOG_LEVEL_WARN: return "WARN"; + case LOG_LEVEL_ERROR: return "ERROR"; + default: assert(false); + } +} + +attr_const enum log_level string_to_log_level(const char *str) { + if (strcasecmp(str, "TRACE") == 0) + return LOG_LEVEL_TRACE; + else if (strcasecmp(str, "DEBUG") == 0) + return LOG_LEVEL_DEBUG; + else if (strcasecmp(str, "INFO") == 0) + return LOG_LEVEL_INFO; + else if (strcasecmp(str, "WARN") == 0) + return LOG_LEVEL_WARN; + else if (strcasecmp(str, "ERROR") == 0) + return LOG_LEVEL_ERROR; + return LOG_LEVEL_INVALID; +} + +struct log *log_new(void) { + auto ret = cmalloc(struct log); + ret->log_level = LOG_LEVEL_WARN; + ret->head = NULL; + return ret; +} + +void log_add_target(struct log *l, struct log_target *tgt) { + tgt->next = l->head; + l->head = tgt; +} + +/// Destroy a log struct +void log_destroy(struct log *l) { + // free all tgt + struct log_target *head = l->head; + while (head) { + auto next = head->next; + head->ops->destroy(head); + head = next; + } + free(l); +} + +void log_set_level(struct log *l, int level) { + assert(level < LOG_LEVEL_INVALID && level > 0); + l->log_level = level; +} + +attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, + const char *fmt, ...) { + assert(level < LOG_LEVEL_INVALID && level > 0); + if (level < l->log_level) + return; + + char *buf = NULL; + va_list args; + + va_start(args, fmt); + size_t len = vasprintf(&buf, fmt, args); + va_end(args); + + struct timespec ts; + timespec_get(&ts, TIME_UTC); + auto tm = localtime(&ts.tv_sec); + char time_buf[100]; + strftime(time_buf, sizeof time_buf, "%x %T", tm); + + if (!buf) + return; + + const char *log_level_str = log_level_to_string(level); + char *common = NULL; + size_t plen = asprintf(&common, "[ %s.%03ld %s %s ] ", time_buf, + ts.tv_nsec / 1000000, func, log_level_str); + if (!common) + return; + + common = crealloc(common, plen + len + 2); + strcpy(common + plen, buf); + strcpy(common + plen + len, "\n"); + + struct log_target *head = l->head; + while (head) { + if (head->ops->colorize_begin) { + // construct target specific prefix + const char *p = head->ops->colorize_begin(level); + const char *s = ""; + if (head->ops->colorize_end) + s = head->ops->colorize_end(level); + char *str = NULL; + size_t plen2 = + asprintf(&str, "[ %s.%03ld %s %s%s%s ] ", time_buf, + ts.tv_nsec / 1000000, func, p, log_level_str, s); + if (!str) { + log_strwrite(head, common); + continue; + } + str = crealloc(str, plen2 + len + 2); + strcpy(str + plen2, buf); + strcpy(str + plen2 + len, "\n"); + log_strwrite(head, str); + free(str); + } else { + log_strwrite(head, common); + } + head = head->next; + } + free(common); +} + +/// A trivial deinitializer that simply frees the memory +static attr_unused void logger_trivial_destroy(struct log_target *tgt) { + free(tgt); +} + +/// A null log target that does nothing +static const struct log_ops null_logger_ops; +static struct log_target null_logger_target = { + .ops = &null_logger_ops, +}; + +struct log_target *null_logger_new(void) { + return &null_logger_target; +} + +static void null_logger_write(struct log_target *tgt, const char *str, size_t len) { + return; +} + +static const struct log_ops null_logger_ops = { + .write = null_logger_write, +}; + +/// A file based logger that writes to file (or stdout/stderr) +struct file_logger { + struct log_target tgt; + FILE *f; + struct log_ops ops; +}; + +void file_logger_write(struct log_target *tgt, const char *str, size_t len) { + auto f = (struct file_logger *)tgt; + fwrite(str, 1, len, f->f); +} + +void file_logger_destroy(struct log_target *tgt) { + auto f = (struct file_logger *)tgt; + fclose(f->f); + free(tgt); +} + +#define ANSI(x) "\033[" x "m" +const char *terminal_colorize_begin(enum log_level level) { + switch (level) { + case LOG_LEVEL_TRACE: return ANSI("30;2"); + case LOG_LEVEL_DEBUG: return ANSI("37;2"); + case LOG_LEVEL_INFO: return ANSI("92"); + case LOG_LEVEL_WARN: return ANSI("33"); + case LOG_LEVEL_ERROR: return ANSI("31;1"); + default: assert(false); + } +} + +const char *terminal_colorize_end(enum log_level level) { + return ANSI("0"); +} +#undef PREFIX + +static const struct log_ops file_logger_ops = { + .write = file_logger_write, + .destroy = file_logger_destroy, +}; + +struct log_target *file_logger_new(const char *filename) { + FILE *f = fopen(filename, "w+"); + if (!f) { + return NULL; + } + + auto ret = cmalloc(struct file_logger); + ret->tgt.ops = &ret->ops; + ret->f = f; + + // Always assume a file is not a terminal + ret->ops = file_logger_ops; + + return &ret->tgt; +} + +struct log_target *stderr_logger_new(void) { + int fd = dup(STDERR_FILENO); + if (fd < 0) { + return NULL; + } + + FILE *f = fdopen(fd, "w"); + if (!f) { + return NULL; + } + + auto ret = cmalloc(struct file_logger); + ret->tgt.ops = &ret->ops; + ret->f = f; + ret->ops = file_logger_ops; + + if (isatty(fd)) { + ret->ops.colorize_begin = terminal_colorize_begin; + ret->ops.colorize_end = terminal_colorize_end; + } + return &ret->tgt; +} + +#ifdef CONFIG_OPENGL +/// An opengl logger that can be used for logging into opengl debugging tools, +/// such as apitrace +struct glx_string_marker_logger { + struct log_target tgt; + void (*glx_string_marker)(GLsizei len, const char *); +}; + +void glx_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { + auto g = (struct glx_string_marker_logger *)tgt; + g->glx_string_marker(len, str); +} + +static const struct log_ops glx_string_marker_logger_ops = { + .write = glx_string_marker_logger_write, + .destroy = logger_trivial_destroy, +}; + +struct log_target *glx_string_marker_logger_new(void) { + void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY"); + if (!fnptr) + return NULL; + + auto ret = cmalloc(struct glx_string_marker_logger); + ret->tgt.ops = &glx_string_marker_logger_ops; + ret->glx_string_marker = fnptr; + return &ret->tgt; +} + +#else +struct log_target *glx_string_marker_logger_new(void) { + return null_logger_new(); +} +#endif + +// vim: set noet sw=8 ts=8: diff --git a/src/log.h b/src/log.h index 6b6a2169..a60fd493 100644 --- a/src/log.h +++ b/src/log.h @@ -2,28 +2,84 @@ // Copyright (c) 2018 Yuxuan Shui #pragma once +#include #include +#include "compiler.h" + +enum log_level { + LOG_LEVEL_TRACE = 0, + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR, + LOG_LEVEL_INVALID +}; + +#define LOG(level, x, ...) \ + log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__) +#define log_trace(x, ...) LOG(TRACE, x, ##__VA_ARGS__) +#define log_debug(x, ...) LOG(DEBUG, x, ##__VA_ARGS__) +#define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__) +#define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__) +#define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__) + /// Print out an error message. -#define printf_err(format, ...) \ - fprintf(stderr, format "\n", ## __VA_ARGS__) +#define printf_err(format, ...) log_error(format, ##__VA_ARGS__) /// Print out an error message with function name. -#define printf_errf(format, ...) \ - printf_err("%s" format, __func__, ## __VA_ARGS__) +#define printf_errf(format, ...) log_error(format, ##__VA_ARGS__) /// Print out an error message with function name, and quit with a /// specific exit code. -#define printf_errfq(code, format, ...) { \ - printf_err("%s" format, __func__, ## __VA_ARGS__); \ - exit(code); \ -} +#define printf_errfq(code, format, ...) \ + { \ + log_error(format, ##__VA_ARGS__); \ + exit(code); \ + } /// Print out a debug message. -#define printf_dbg(format, ...) \ - printf(format, ## __VA_ARGS__); \ - fflush(stdout) +#define printf_dbg(format, ...) log_debug(format, ##__VA_ARGS__) /// Print out a debug message with function name. -#define printf_dbgf(format, ...) \ - printf_dbg("%s" format, __func__, ## __VA_ARGS__) +#define printf_dbgf(format, ...) log_debug(format, ##__VA_ARGS__) + +struct log; +struct log_target; + +attr_printf(4, 5) void log_printf(struct log *, int level, const char *func, + const char *fmt, ...); + +attr_malloc struct log *log_new(void); +attr_nonnull_all void log_destroy(struct log *); +attr_nonnull(1) void log_set_level(struct log *l, int level); +attr_nonnull_all void log_add_target(struct log *, struct log_target *); + +extern thread_local struct log *tls_logger; + +/// Create a thread local logger +static inline void log_init_tls(void) { + tls_logger = log_new(); +} +/// Set thread local logger log level +static inline void log_set_level_tls(int level) { + assert(tls_logger); + log_set_level(tls_logger, level); +} +static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) { + assert(tls_logger); + log_add_target(tls_logger, tgt); +} + +static inline void log_deinit_tls(void) { + assert(tls_logger); + log_destroy(tls_logger); + tls_logger = NULL; +} + +attr_malloc struct log_target *stderr_logger_new(void); +attr_malloc struct log_target *file_logger_new(const char *file); +attr_malloc struct log_target *null_logger_new(void); +attr_malloc struct log_target *glx_string_marker_logger_new(void); + +// vim: set noet sw=8 ts=8: diff --git a/src/meson.build b/src/meson.build index 5404cf3d..7e24251c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,7 +5,7 @@ deps = [ ] srcs = [ files('compton.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', - 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c')] + 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c')] cflags = []