Skip to content

Commit

Permalink
Merge pull request #67 from yshui/log
Browse files Browse the repository at this point in the history
Add a logging framework
  • Loading branch information
yshui committed Dec 20, 2018
2 parents e19e78d + d9409ae commit 537831a
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 14 deletions.
3 changes: 3 additions & 0 deletions man/compton.1.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ OPTIONS
*-S*::
Enable synchronous X operation (for debugging).

*--log-level*::
Set the log level. Possible values are "TRACE", "DEBUG", "INFO", "WARN", "ERROR", in increasing level of importance. Case doesn't matter.

*--show-all-xerrors*::
Show all X errors (for debugging).

Expand Down
12 changes: 12 additions & 0 deletions src/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2018 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once

#include <stdc-predef.h>

#define auto __auto_type
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
Expand Down Expand Up @@ -79,3 +81,13 @@
#else
# define unreachable do {} while(0)
#endif

#ifndef __STDC_NO_THREADS__
# include <threads.h>
#elif __STDC_VERSION__ >= 201112L
# define thread_local _Thread_local
#elif defined(__GNUC__) || defined(__clang__)
# define thread_local __thread
#else
# define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__
#endif
15 changes: 15 additions & 0 deletions src/compton.c
Original file line number Diff line number Diff line change
Expand Up @@ -2607,6 +2607,7 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) {
{ "version", no_argument, NULL, 318 },
{ "no-x-selection", no_argument, NULL, 319 },
{ "no-name-pixmap", no_argument, NULL, 320 },
{ "log-level", required_argument, NULL, 321 },
{ "reredir-on-root-change", no_argument, NULL, 731 },
{ "glx-reinit-on-root-change", no_argument, NULL, 732 },
{ "monitor-repaint", no_argument, NULL, 800 },
Expand Down Expand Up @@ -2895,6 +2896,15 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) {
" removed in the future. If you really need this feature, please report\n"
"an issue to let us know\n");
break;
case 321: {
enum log_level tmp_level = string_to_log_level(optarg);
if (tmp_level == LOG_LEVEL_INVALID) {
log_warn("Invalid log level, defaults to WARN");
} else {
log_set_level_tls(tmp_level);
}
break;
}
P_CASEBOOL(319, no_x_selection);
P_CASEBOOL(731, reredir_on_root_change);
P_CASEBOOL(732, glx_reinit_on_root_change);
Expand Down Expand Up @@ -3568,6 +3578,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;
Expand Down Expand Up @@ -4053,6 +4066,8 @@ session_destroy(session_t *ps) {

if (ps == ps_g)
ps_g = NULL;

log_deinit_tls();
}

/*
Expand Down
9 changes: 9 additions & 0 deletions src/config_libconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ void parse_config_libconfig(session_t *ps, bool *shadow_enable,
// --backend
if (config_lookup_string(&cfg, "backend", &sval) && !parse_backend(ps, sval))
exit(1);
// --log-level
if (config_lookup_string(&cfg, "log-level", &sval)) {
auto level = string_to_log_level(sval);
if (level == LOG_LEVEL_INVALID) {
log_warn("Invalid log level, defaults to WARN");
} else {
log_set_level_tls(level);
}
}
// --sw-opti
lcfg_lookup_bool(&cfg, "sw-opti", &ps->o.sw_opti);
// --use-ewmh-active-win
Expand Down
299 changes: 299 additions & 0 deletions src/log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifdef CONFIG_OPENGL
#include <GL/glx.h>
#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);
}
}

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:
Loading

0 comments on commit 537831a

Please sign in to comment.