char *tag;
/** Print ANSI color codes? */
bool color;
- /** Format in which file names will be printed. */
- enum filename_format filename_format;
/* Log level */
uint8_t level;
/* Log output */
} log;
struct {
- /** Enables validation Logs **/
- bool enabled;
- bool print_times;
- /** String tag to identify validation logs **/
- char *tag;
- /** Print ANSI color codes? */
- bool color;
- /** Format in which file names will be printed. */
- enum filename_format filename_format;
- /* Log level */
- uint8_t level;
- /* Log output */
- enum log_output output;
- /** facilities for syslog if output is syslog **/
- uint32_t facility;
- } validation_log;
+ char *path;
+ } report;
struct {
/** File where the validated ROAs will be stored */
.type = >_log_facility,
.offset = offsetof(struct rpki_config, log.facility),
.doc = "Facility for syslog if output is syslog",
- }, {
- .id = 4005,
- .name = "log.file-name-format",
- .type = >_filename_format,
- .offset = offsetof(struct rpki_config, log.filename_format),
- .doc = "File name variant to print during debug/error messages",
}, {
.id = 'c',
.name = "log.color-output",
{
.id = 4010,
- .name = "validation-log.enabled",
- .type = >_bool,
- .offset = offsetof(struct rpki_config, validation_log.enabled),
- .doc = "Enables validation logs",
- }, {
- .id = 4011,
- .name = "validation-log.output",
- .type = >_log_output,
- .offset = offsetof(struct rpki_config, validation_log.output),
- .doc = "Output where validation log messages will be printed",
- }, {
- .id = 4012,
- .name = "validation-log.level",
- .type = >_log_level,
- .offset = offsetof(struct rpki_config, validation_log.level),
- .doc = "Log level to print message of equal or higher importance",
- }, {
- .id = 4017,
- .name = "validation-log.print-times",
- .type = >_bool,
- .offset = offsetof(struct rpki_config, validation_log.print_times),
- .doc = "(Console output only)",
- }, {
- .id = 4013,
- .name = "validation-log.tag",
+ .name = "report.path",
.type = >_string,
- .offset = offsetof(struct rpki_config, validation_log.tag),
- .doc = "Text tag to identify validation logs",
- .arg_doc = "<string>",
- .json_null_allowed = true,
- }, {
- .id = 4014,
- .name = "validation-log.facility",
- .type = >_log_facility,
- .offset = offsetof(struct rpki_config, validation_log.facility),
- .doc = "Facility for syslog if output is syslog",
- }, {
- .id = 4015,
- .name = "validation-log.file-name-format",
- .type = >_filename_format,
- .offset = offsetof(struct rpki_config,
- validation_log.filename_format),
- .doc = "File name variant to print during debug/error messages",
- }, {
- .id = 4016,
- .name = "validation-log.color-output",
- .type = >_bool,
- .offset = offsetof(struct rpki_config, validation_log.color),
- .doc = "Print ANSI color codes",
+ .offset = offsetof(struct rpki_config, report.path),
},
/* Incidences */
{ 0 },
};
-/**
+/*
* Returns true if @field is the descriptor of one of the members of the
* struct rpki_config structure, false otherwise.
- * (Alternatively: Returns true if @field->offset is valid, false otherwise.)
*/
static bool
is_rpki_config_field(struct option_field const *field)
rpki_config.log.enabled = true;
rpki_config.log.tag = NULL;
rpki_config.log.color = false;
- rpki_config.log.filename_format = FNF_GLOBAL;
rpki_config.log.level = LOG_WARNING;
rpki_config.log.output = CONSOLE;
rpki_config.log.facility = LOG_DAEMON;
- rpki_config.validation_log.enabled = false;
- rpki_config.validation_log.tag = pstrdup("Validation");
- rpki_config.validation_log.color = false;
- rpki_config.validation_log.filename_format = FNF_GLOBAL;
- rpki_config.validation_log.level = LOG_WARNING;
- rpki_config.validation_log.output = CONSOLE;
- rpki_config.validation_log.facility = LOG_DAEMON;
+ rpki_config.report.path = NULL;
rpki_config.output.roa = NULL;
rpki_config.output.bgpsec = NULL;
return ESRCH;
}
+static void
+become_absolute_path(char *cwd, char **_path)
+{
+ char *relative, *absolute;
+
+ relative = *_path;
+ if (relative != NULL) {
+ absolute = path_join(cwd, relative);
+ free(relative);
+ *_path = absolute;
+ }
+}
+
static int
-become_absolute_path(char **_path)
+become_absolute_paths(void)
{
- char *relative = *_path;
- char *absolute;
+ char *buf, *cwd;
+ int error;
- if (relative == NULL)
- return 0;
+ buf = pmalloc(1024);
+
+ cwd = getcwd(buf, 1024);
+ if (!cwd) {
+ error = errno;
+ pr_err("Cannot get the current directory: %s", strerror(error));
+ free(buf);
+ return error;
+ }
- absolute = realpath(relative, NULL);
- if (!absolute)
- return pr_err("Cannot resolve the absolute path of %s: %s",
- relative, strerror(errno));
+ become_absolute_path(cwd, &rpki_config.tal);
+ become_absolute_path(cwd, &rpki_config.report.path);
+ become_absolute_path(cwd, &rpki_config.slurm);
+ become_absolute_path(cwd, &rpki_config.http.ca_path);
- free(relative);
- *_path = absolute;
+ free(buf);
return 0;
}
+static void
+set_logger_syslog(void)
+{
+ struct log_listeners list = TAILQ_HEAD_INITIALIZER(list);
+ struct log_listener node = { 0 };
+
+ node.type = "syslog";
+ node.level = "info";
+ node.facility = LOG_DAEMON;
+
+ TAILQ_INSERT_TAIL(&list, &node, lh);
+ log_init(&list);
+}
+
+static void
+set_logger_console(void)
+{
+ struct log_listeners list = TAILQ_HEAD_INITIALIZER(list);
+ struct log_listener node = { 0 };
+
+ node.type = "console";
+ node.level = "info";
+ node.color = true;
+
+ TAILQ_INSERT_TAIL(&list, &node, lh);
+ log_init(&list);
+}
+
int
handle_flags_config(int argc, char **argv)
{
if (error)
goto end;
- error = become_absolute_path(&rpki_config.tal);
- if (error)
- goto end;
- error = become_absolute_path(&rpki_config.slurm);
- if (error)
- goto end;
- error = become_absolute_path(&rpki_config.http.ca_path);
+ error = become_absolute_paths();
if (error)
goto end;
if (rpki_config.daemon) {
pr_inf("Executing as daemon, all logs will be sent to syslog.");
- /* Send all logs to syslog */
- rpki_config.log.output = SYSLOG;
- rpki_config.validation_log.output = SYSLOG;
- error = daemonize(log_start);
- goto end;
+ set_logger_syslog(); /* XXX hardcoded */
+ error = daemonize();
+ } else {
+ set_logger_console(); /* XXX hardcoded */
}
- log_start();
end:
if (error) {
free_rpki_config();
free(long_opts);
free(short_opts);
return error;
-
}
struct option_field const *
return rpki_config.log.color;
}
-enum filename_format
-config_get_op_log_file_format(void)
-{
- return rpki_config.log.filename_format;
-}
-
uint8_t
config_get_op_log_level(void)
{
return rpki_config.log.facility;
}
-bool
-config_get_val_log_enabled(void)
-{
- return rpki_config.validation_log.enabled;
-}
-
-bool
-config_get_val_print_times(void)
-{
- return rpki_config.validation_log.print_times;
-}
-
-char const *
-config_get_val_log_tag(void)
-{
- return rpki_config.validation_log.tag;
-}
-
-bool
-config_get_val_log_color_output(void)
-{
- return rpki_config.validation_log.color;
-}
-
-enum filename_format
-config_get_val_log_file_format(void)
-{
- return rpki_config.validation_log.filename_format;
-}
-
-char const *
-logv_filename(char const *path)
-{
- return (rpki_config.validation_log.filename_format == FNF_NAME)
- ? path_filename(path)
- : path;
-}
-
-uint8_t
-config_get_val_log_level(void)
-{
- return rpki_config.validation_log.level;
-}
-
-enum log_output
-config_get_val_log_output(void)
-{
- return rpki_config.validation_log.output;
-}
-
-uint32_t
-config_get_val_log_facility(void)
+char *
+config_get_report(void)
{
- return rpki_config.validation_log.facility;
+ return rpki_config.report.path;
}
bool
+#define _DEFAULT_SOURCE 1
+
#include "log.h"
#include <errno.h>
#include <unistd.h>
#include "config.h"
+#include "report.h"
#include "thread_var.h"
#include "types/path.h"
struct level {
- char const *label;
+ int id;
+ char const *tag;
+ char const *name;
char const *color;
- FILE *stream;
+ char const *rst;
};
-static struct level DBG = { "DBG", PR_COLOR_DBG };
-static struct level INF = { "INF", PR_COLOR_INF };
-static struct level WRN = { "WRN", PR_COLOR_WRN };
-static struct level ERR = { "ERR", PR_COLOR_ERR };
-static struct level CRT = { "CRT", PR_COLOR_CRT };
-static struct level UNK = { "UNK", "" };
-
-struct log_config {
- bool fprintf_enabled; /* Print on the standard streams? */
- bool syslog_enabled; /* Print on syslog? */
-
- uint8_t level;
- bool print_times;
- char const *tag;
- bool color;
- int facility;
- bool rm_filepath;
+#ifdef PR_CLUTTER_ENABLED
+static struct level CLT = { LOG_DEBUG, "CLT", "clutter", CLR_CLT, CLR_RST };
+#endif
+static struct level TRC = { LOG_DEBUG, "TRC", "trace", CLR_TRC, CLR_RST };
+static struct level INF = { LOG_INFO, "INF", "info", "", "" };
+static struct level WRN = { LOG_WARNING, "WRN", "warning", CLR_WRN, CLR_RST };
+static struct level ERR = { LOG_ERR, "ERR", "error", CLR_ERR, CLR_RST };
+static struct level CRT = { LOG_CRIT, "CRT", "critical", CLR_CRT, CLR_RST };
+static struct level PNC = { LOG_EMERG, "PNC", "panic", CLR_PNC, CLR_RST };
+
+struct logger {
+ void (*cb)(struct logger *, struct level *, char const *, va_list);
+
+ struct level *lvl;
+ FILE *stream; /* file only */
+ bool print_times; /* console and file only */
+ bool color; /* console and file only */
+ int facility; /* syslog only */
+ bool free;
+
+ pthread_mutex_t *lock; /* console (except init) and file only */
+ SLIST_ENTRY(logger) lh;
};
-/* Configuration for the operation logs. */
-static struct log_config op_config;
-/* Configuration for the validation logs. */
-static struct log_config val_config;
+SLIST_HEAD(loggers, logger);
-/*
- * fprintf and syslog are rarely enabled at the same time, so I reused the
- * mutex.
- *
- * "log" + "lock" = "logck"
- */
-static pthread_mutex_t logck;
+/* Constant after init */
+static struct loggers listeners = SLIST_HEAD_INITIALIZER(listeners);
-/**
- * Important: -rdynamic needs to be enabled, otherwise this does not print
- * function names. See LDFLAGS_DEBUG in Makefile.am.
- * Also: Only non-static functions will be labeled.
- *
- * The first printed entry is probably not meaningful. (But I'm printing
- * everything anyway due to paranoia.)
- *
- * @title is allowed to be NULL. If you need locking, do it outside. (And be
- * aware that pthread_mutex_lock() can return error codes, which shouldn't
- * prevent critical stack traces from printing.)
- */
static void
-print_stack_trace(char const *title)
+vcb(struct logger *lgr, struct level *lvl, char const *fmt, ...)
{
-#ifdef BACKTRACE_ENABLED
-#define STACK_SIZE 64
+ va_list vl;
- void *array[STACK_SIZE];
- size_t size;
- char **strings;
- size_t i;
- int fp;
+ va_start(vl, fmt);
+ lgr->cb(lgr, lvl, fmt, vl);
+ va_end(vl);
+}
- size = backtrace(array, STACK_SIZE);
- strings = backtrace_symbols(array, size);
+#ifdef BACKTRACE_ENABLED
- if (op_config.fprintf_enabled) {
- if (title != NULL)
- fprintf(ERR.stream, "%s\n", title);
- fprintf(ERR.stream, "Stack trace:\n");
- for (i = 0; i < size; i++)
- fprintf(ERR.stream, " %s\n", strings[i]);
- fprintf(ERR.stream, "(End of stack trace)\n");
- }
+#define STACK_SIZE 64
- if (op_config.syslog_enabled) {
- fp = LOG_ERR | op_config.facility;
- if (title != NULL)
- syslog(fp, "%s", title);
- syslog(fp, "Stack trace:");
- for (i = 0; i < size; i++)
- syslog(fp, " %s", strings[i]);
- syslog(fp, "(End of stack trace)");
- }
+/*
+ * Needs -rdynamic, otherwise function names don't show up.
+ * Only non-static functions can be labeled.
+ */
+static void
+__pr_stack_trace(struct logger *lgr, struct level *lvl,
+ char const *title, size_t size, char **strings)
+{
+ size_t i;
- free(strings);
-#endif /* BACKTRACE_ENABLED */
+ if (title != NULL)
+ vcb(lgr, lvl, "%s", title);
+ vcb(lgr, lvl, "Stack trace:");
+ for (i = 0; i < size; i++)
+ vcb(lgr, lvl, " %s", strings[i]);
+ vcb(lgr, lvl, "(End of stack trace)");
}
-static void init_config(struct log_config *cfg)
-{
- cfg->fprintf_enabled = true;
- cfg->syslog_enabled = true;
- cfg->level = LOG_DEBUG;
- cfg->print_times = true;
- cfg->tag = NULL;
- cfg->color = false;
- cfg->facility = LOG_DAEMON;
-}
+#endif /* BACKTRACE_ENABLED */
-int
-log_setup(void)
+static void
+init_mutex(struct logger *lgr)
{
- /*
- * Remember not to use any actual logging functions until logging has
- * been properly initialized.
- */
-
int error;
- DBG.stream = stdout;
- INF.stream = stdout;
- WRN.stream = stderr;
- ERR.stream = stderr;
- CRT.stream = stderr;
- UNK.stream = stdout;
+ lgr->lock = pmalloc(sizeof(pthread_mutex_t));
- init_config(&op_config);
- init_config(&val_config);
-
- error = pthread_mutex_init(&logck, NULL);
+ error = pthread_mutex_init(lgr->lock, NULL);
if (error) {
- fprintf(ERR.stream,
- "pthread_mutex_init() failure: %s\n",
- strerror(error));
- syslog(LOG_ERR | op_config.facility,
- "pthread_mutex_init() failure: %s",
- strerror(error));
- return error;
+ pr_wrn("Cannot initialize logging mutex: %s. "
+ "Logs might overlap sometimes.", strerror(error));
+ free(lgr->lock);
+ lgr->lock = NULL;
}
-
- return 0;
}
static void
-log_disable_syslog(void)
+get_time(char *buf /* Must length 16 */)
{
- if (op_config.syslog_enabled || val_config.syslog_enabled) {
- closelog();
- op_config.syslog_enabled = false;
- val_config.syslog_enabled = false;
- }
+ time_t now;
+ struct tm components;
+
+ now = time(NULL);
+ if (now == ((time_t) -1))
+ return;
+ if (localtime_r(&now, &components) == NULL)
+ return;
+ if (strftime(buf, sizeof(buf), "%m-%d %H:%M:%S ", &components) == 0)
+ buf[0] = 0;
}
-void
-log_start(void)
+static void
+stream_print(FILE *out, struct logger *lgr, struct level *lvl,
+ char const *fmt, va_list vl)
{
- if (config_get_val_log_enabled()) {
- switch (config_get_val_log_output()) {
- case SYSLOG:
- val_config.fprintf_enabled = false;
- break;
- case CONSOLE:
- val_config.syslog_enabled = false;
- break;
- }
- } else {
- val_config.fprintf_enabled = false;
- val_config.syslog_enabled = false;
- }
+ char time[16];
- if (config_get_op_log_enabled()) {
- switch (config_get_op_log_output()) {
- case SYSLOG:
- op_config.fprintf_enabled = false;
- break;
- case CONSOLE:
- if (val_config.syslog_enabled)
- op_config.syslog_enabled = false;
- else
- log_disable_syslog();
- break;
- }
- } else {
- op_config.fprintf_enabled = false;
- if (val_config.syslog_enabled)
- op_config.syslog_enabled = false;
- else
- log_disable_syslog();
- }
+ time[0] = 0;
+ if (lgr->print_times)
+ get_time(time);
- op_config.level = config_get_op_log_level();
- op_config.print_times = config_get_op_print_times();
- op_config.tag = config_get_op_log_tag();
- op_config.color = config_get_op_log_color_output();
- op_config.facility = config_get_op_log_facility();
- op_config.rm_filepath = config_get_op_log_file_format() == FNF_NAME;
- val_config.level = config_get_val_log_level();
- val_config.print_times = config_get_val_print_times();
- val_config.tag = config_get_val_log_tag();
- val_config.color = config_get_val_log_color_output();
- val_config.facility = config_get_val_log_facility();
- val_config.rm_filepath = config_get_val_log_file_format() == FNF_NAME;
-}
+ if (lgr->lock)
+ pthread_mutex_lock(lgr->lock);
-void
-log_teardown(void)
-{
- log_disable_syslog();
- pthread_mutex_destroy(&logck);
+ fprintf(out, "%s%s%s: ", lgr->color ? lvl->color : "", time, lvl->tag);
+ vfprintf(out, fmt, vl);
+ fprintf(out, "%s\n", lgr->color ? lvl->rst : "");
+
+ if (lgr->lock)
+ pthread_mutex_unlock(lgr->lock);
}
-static struct level const *
-level2struct(int level)
+static void
+console_cb(struct logger *lgr, struct level *lvl, char const *fmt, va_list vl)
{
- switch (level) {
- case LOG_CRIT:
- return &CRT;
- case LOG_ERR:
- return &ERR;
- case LOG_WARNING:
- return &WRN;
- case LOG_INFO:
- return &INF;
- case LOG_DEBUG:
- return &DBG;
- }
+ FILE *stream;
- return &UNK;
+ stream = (lvl->id <= LOG_ERR) ? stderr : stdout;
+ stream_print(stream, lgr, lvl, fmt, vl);
+ fflush(stream);
}
static void
-lock_mutex(void)
+file_cb(struct logger *lgr, struct level *lvl, char const *fmt, va_list vl)
{
- int error;
-
- error = pthread_mutex_lock(&logck);
- if (error) {
- /*
- * Despite being supposed to be impossible, failing to lock the
- * mutex is not fatal; it just means we might log some mixed
- * messages, which is better than dying.
- *
- * Furthermore, this might have been called while logging
- * another critical. We must absolutely not get in the way of
- * that critical's print.
- */
- print_stack_trace(strerror(error));
- }
+ stream_print(lgr->stream, lgr, lvl, fmt, vl);
}
static void
-unlock_mutex(void)
+syslog_cb(struct logger *lgr, struct level *lvl, char const *fmt, va_list vl)
{
- int error;
-
- error = pthread_mutex_unlock(&logck);
- if (error)
- print_stack_trace(strerror(error)); /* Same as above. */
+ vsyslog(lvl->id | lgr->facility, fmt, vl);
}
-static void
-print_time(struct level const *lvl)
+void
+log_setup(void)
{
- time_t now;
- struct tm components;
- char str[16];
+ static struct logger init_node = { 0 };
- now = time(NULL);
- if (now == ((time_t) -1))
- return;
- if (localtime_r(&now, &components) == NULL)
- return;
- if (strftime(str, sizeof(str), "%m-%d %H:%M:%S", &components) == 0)
- return;
+ init_node.cb = console_cb;
+ init_node.lvl = &INF;
- fprintf(lvl->stream, "%s ", str);
+ SLIST_INSERT_HEAD(&listeners, &init_node, lh);
}
-static void
-__vfprintf(int level, struct log_config *cfg, char const *format, va_list args)
+static int
+add_listener(struct loggers *list, struct log_listener *new)
{
- struct level const *lvl;
- char const *file_name;
-
- lvl = level2struct(level);
-
- lock_mutex();
+ struct logger *node;
+
+ node = pzalloc(sizeof(struct logger));
+ node->free = true;
+
+ if (strcmp(new->level, ERR.name) == 0)
+ node->lvl = &ERR;
+ else if (strcmp(new->level, WRN.name) == 0)
+ node->lvl = &WRN;
+ else if (strcmp(new->level, INF.name) == 0)
+ node->lvl = &INF;
+ else if (strcmp(new->level, TRC.name) == 0)
+ node->lvl = &TRC;
+ else {
+ free(node);
+ return pr_err("Unknown log level: %s", new->level);
+ }
- if (cfg->color)
- fprintf(lvl->stream, "%s", lvl->color);
+ if (strcmp(new->type, "console") == 0) {
+ node->cb = console_cb;
+ node->print_times = new->print_times;
+ node->color = new->color;
+ init_mutex(node);
+ } else if (strcmp(new->type, "file") == 0) {
+ node->cb = file_cb;
+ node->stream = fopen(new->filename, "a");
+ node->print_times = new->print_times;
+ node->color = new->color;
+ init_mutex(node);
+ } else if (strcmp(new->type, "syslog") == 0) {
+ node->cb = syslog_cb;
+ node->facility = new->facility;
+ } else {
+ free(node);
+ return pr_err("Unknown log type: %s", new->type);
+ }
- if (cfg->print_times)
- print_time(lvl);
+ SLIST_INSERT_HEAD(list, node, lh);
+ return 0;
+}
- fprintf(lvl->stream, "%s", lvl->label);
- if (cfg->tag)
- fprintf(lvl->stream, " [%s]", cfg->tag);
- fprintf(lvl->stream, ": ");
+static void
+clear_loggers(struct loggers *list)
+{
+ struct logger *lgr;
+
+ while ((lgr = SLIST_FIRST(list)) != NULL) {
+ if (lgr->stream)
+ fclose(lgr->stream);
+ if (lgr->lock) {
+ pthread_mutex_destroy(lgr->lock);
+ free(lgr->lock);
+ }
- file_name = fnstack_peek();
- if (file_name != NULL) {
- if (cfg->rm_filepath)
- file_name = path_filename(file_name);
- fprintf(lvl->stream, "%s: ", file_name);
+ SLIST_REMOVE_HEAD(list, lh);
+ if (lgr->free)
+ free(lgr);
}
+}
- vfprintf(lvl->stream, format, args);
+int
+log_init(struct log_listeners *descriptors)
+{
+ struct loggers newlist;
+ struct log_listener *descriptor;
+ int error;
- if (cfg->color)
- fprintf(lvl->stream, PR_COLOR_RST);
- fprintf(lvl->stream, "\n");
+ SLIST_INIT(&newlist);
+ TAILQ_FOREACH(descriptor, descriptors, lh) {
+ error = add_listener(&newlist, descriptor);
+ if (error) {
+ clear_loggers(&newlist);
+ return error;
+ }
+ }
- /* Force flush */
- if (lvl->stream == stdout)
- fflush(lvl->stream);
+ clear_loggers(&listeners);
+ listeners = newlist;
+ return 0;
+}
- unlock_mutex();
+void
+log_teardown(void)
+{
+ clear_loggers(&listeners);
}
-/*
- * TODO (fine) Optimize. Notice the buffer is static, which seems to be the
- * reason why it's (probably ill-advisedly) mutexing.
- */
-#define MSG_LEN 512
+#ifdef PR_CLUTTER_ENABLED
-static void
-__syslog(int level, struct log_config *cfg, const char *format, va_list args)
+void
+pr_clutter(const char *fmt, ...)
{
- static char msg[MSG_LEN];
- char const *file;
- int res;
-
- level |= cfg->facility;
- file = fnstack_peek();
- if (file && cfg->rm_filepath)
- file = path_filename(file);
-
- lock_mutex();
-
- /* Can't use vsyslog(); it's not portable. */
- res = vsnprintf(msg, MSG_LEN, format, args);
- if (res < 0)
- goto end;
- if (res >= MSG_LEN)
- msg[MSG_LEN - 1] = '\0';
-
- if (file != NULL) {
- if (cfg->tag != NULL)
- syslog(level, "[%s] %s: %s", cfg->tag, file, msg);
- else
- syslog(level, "%s: %s", file, msg);
- } else {
- if (cfg->tag != NULL)
- syslog(level, "[%s] %s", cfg->tag, msg);
- else
- syslog(level, "%s", msg);
+ struct logger *lgr;
+ va_list args;
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (LOG_DEBUG <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, &CLT, fmt, args);
+ va_end(args);
+ }
}
-
-end: unlock_mutex();
}
-#define PR_SIMPLE(lvl, config) \
- do { \
- va_list args; \
- \
- if (lvl > config.level) \
- break; \
- \
- if (config.syslog_enabled) { \
- va_start(args, format); \
- __syslog(lvl, &config, format, args); \
- va_end(args); \
- } \
- \
- if (config.fprintf_enabled) { \
- va_start(args, format); \
- __vfprintf(lvl, &config, format, args); \
- va_end(args); \
- } \
- } while (0)
+#endif
void
-pr_trc(const char *format, ...)
+pr_trc(const char *fmt, ...)
{
- PR_SIMPLE(LOG_DEBUG, op_config);
+ struct logger *lgr;
+ va_list args;
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (LOG_DEBUG <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, &TRC, fmt, args);
+ va_end(args);
+ }
+ }
}
void
-pr_inf(const char *format, ...)
+pr_inf(const char *fmt, ...)
{
- PR_SIMPLE(LOG_INFO, op_config);
+ struct logger *lgr;
+ va_list args;
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (LOG_INFO <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, &INF, fmt, args);
+ va_end(args);
+ }
+ }
}
-/**
- * Always returs 0. (So you can interrupt whatever you're doing without failing
- * validation.)
- */
int
-pr_wrn(const char *format, ...)
+pr_wrn(const char *fmt, ...)
{
- PR_SIMPLE(LOG_WARNING, op_config);
+ struct level *lvl = &WRN;
+ struct logger *lgr;
+ va_list args;
+
+ if (report_enabled()) {
+ va_start(args, fmt);
+ report(lvl->tag, fmt, args);
+ va_end(args);
+ lvl = &TRC;
+ }
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (lvl->id <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, lvl, fmt, args);
+ va_end(args);
+ }
+ }
+
return 0;
}
int
-pr_err(const char *format, ...)
+pr_err(const char *fmt, ...)
{
- PR_SIMPLE(LOG_ERR, op_config);
- return EINVAL;
-}
+ struct level *lvl = &ERR;
+ struct logger *lgr;
+ va_list args;
+
+ if (report_enabled()) {
+ va_start(args, fmt);
+ report(lvl->tag, fmt, args);
+ va_end(args);
+ lvl = &TRC;
+ }
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (lvl->id <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, lvl, fmt, args);
+ va_end(args);
+ }
+ }
-int
-pr_crit(const char *format, ...)
-{
- PR_SIMPLE(LOG_ERR, op_config);
- lock_mutex();
- print_stack_trace(NULL);
- unlock_mutex();
return EINVAL;
}
-struct crypto_cb_arg {
- unsigned int stack_size;
- int (*error_fn)(const char *, ...);
-};
-
static int
-log_crypto_error(const char *str, size_t len, void *_arg)
+log_crypto_error(const char *str, size_t len, void *_stack_size)
{
- struct crypto_cb_arg *arg = _arg;
- arg->error_fn("-> %s", str);
- arg->stack_size++;
+ unsigned int *stack_size = _stack_size;
+ pr_err("-> %s", str);
+ (*stack_size)++;
return 1;
}
-static int
-crypto_err(struct log_config *cfg, int (*error_fn)(const char *, ...))
-{
- struct crypto_cb_arg arg;
-
- error_fn("libcrypto error stack:");
-
- arg.stack_size = 0;
- arg.error_fn = error_fn;
- ERR_print_errors_cb(log_crypto_error, &arg);
- if (arg.stack_size == 0)
- error_fn(" <Empty>");
- else
- error_fn("End of libcrypto stack.");
-
- return EINVAL;
-}
-
/**
- * This is like pr_err() and pr_errno(), except meant to log an error made
- * during a libcrypto routine.
+ * This is like pr_err(), except meant to log an error made during a libcrypto
+ * routine.
*
* This differs from usual printf-like functions:
*
* - It returns EINVAL, not bytes written.
* - It prints a newline.
* - Also prints the cryptolib's error message stack.
- *
- * Always appends a newline at the end.
*/
int
-pr_crypto_err(const char *format, ...)
+pr_crypto_err(const char *fmt, ...)
+{
+ struct level *lvl = &ERR;
+ struct logger *lgr;
+ va_list args;
+ unsigned int stack_size;
+
+ if (report_enabled()) {
+ va_start(args, fmt);
+ report(lvl->tag, fmt, args);
+ va_end(args);
+ lvl = &TRC;
+ }
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (lvl->id <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, lvl, fmt, args);
+ va_end(args);
+ }
+ }
+
+ pr_err("libcrypto error stack:");
+ stack_size = 0;
+ ERR_print_errors_cb(log_crypto_error, &stack_size);
+ if (stack_size == 0)
+ pr_err(" <Empty>");
+ else
+ pr_err("End of libcrypto stack.");
+
+ return EINVAL;
+}
+
+int
+pr_crit(const char *fmt, ...)
{
- PR_SIMPLE(LOG_ERR, op_config);
- return crypto_err(&op_config, pr_err);
+ struct logger *lgr;
+ va_list args;
+
+#ifdef BACKTRACE_ENABLED
+ void *array[STACK_SIZE];
+ size_t size;
+ char **strings;
+
+ size = backtrace(array, STACK_SIZE);
+ strings = backtrace_symbols(array, size);
+#endif
+
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (CRT.id <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, &CRT, fmt, args);
+ va_end(args);
+
+#ifdef BACKTRACE_ENABLED
+ __pr_stack_trace(lgr, &CRT, NULL, size, strings);
+#endif
+ }
+ }
+
+#ifdef BACKTRACE_ENABLED
+ free(strings);
+#endif
+
+ return EINVAL;
}
__dead void
-enomem_panic(void)
+pr_panic(const char *fmt, ...)
{
- static char const *ENOMEM_MSG = "Out of memory.\n";
- ssize_t garbage;
-
- /*
- * I'm not using PR_SIMPLE and friends, because those allocate.
- * We want to minimize allocations after a memory allocation failure.
- */
-
- if (LOG_ERR > op_config.level)
- goto done;
-
- if (op_config.fprintf_enabled) {
- lock_mutex();
- /*
- * write() is AS-Safe, which implies it doesn't allocate,
- * unlike printf().
- *
- * "garbage" prevents write()'s warn_unused_result (compiler
- * warning).
- */
- garbage = write(STDERR_FILENO, ENOMEM_MSG, strlen(ENOMEM_MSG));
- unlock_mutex();
- /* Prevents "set but not used" warning. */
- garbage += garbage;
- }
+ struct logger *lgr;
+ va_list args;
+
+#ifdef BACKTRACE_ENABLED
+ void *array[STACK_SIZE];
+ size_t size;
+ char **strings;
+
+ size = backtrace(array, STACK_SIZE);
+ strings = backtrace_symbols(array, size);
+#endif
- if (op_config.syslog_enabled) {
- lock_mutex();
- /* This allocates, but I don't think I have more options. */
- syslog(LOG_ERR | op_config.facility, "Out of memory.");
- unlock_mutex();
+ SLIST_FOREACH(lgr, &listeners, lh) {
+ if (LOG_CRIT <= lgr->lvl->id) {
+ va_start(args, fmt);
+ lgr->cb(lgr, &PNC, fmt, args);
+ va_end(args);
+
+#ifdef BACKTRACE_ENABLED
+ __pr_stack_trace(lgr, &PNC, NULL, size, strings);
+#endif
+ }
}
-done: exit(ENOMEM);
+#ifdef BACKTRACE_ENABLED
+ free(strings);
+#endif
+
+ exit(-1);
}
__dead void
-pr_panic(const char *format, ...)
+enomem_panic(void)
{
- PR_SIMPLE(LOG_ERR, op_config);
- print_stack_trace(NULL);
- exit(-1);
+ struct logger *lgr;
+
+ SLIST_FOREACH(lgr, &listeners, lh)
+ if (LOG_CRIT <= lgr->lvl->id)
+ vcb(lgr, &CRT, "Out of memory");
+
+ exit(ENOMEM);
}
#include <stdbool.h>
#include <stdio.h>
+#include <sys/queue.h>
-#define PR_COLOR_DBG "\x1B[36m" /* Cyan */
-#define PR_COLOR_CLT "\x1B[34m" /* Blue */
-#define PR_COLOR_TRC "\x1B[32m" /* Green */
-#define PR_COLOR_INF "\x1B[37m" /* White */
-#define PR_COLOR_WRN "\x1B[33m" /* Yellow */
-#define PR_COLOR_ERR "\x1B[31m" /* Red */
-#define PR_COLOR_CRT "\x1B[35m" /* Purple */
-#define PR_COLOR_RST "\x1B[0m"
+#define CLR_DBG "\x1B[36m" /* Cyan */
+#define CLR_CLT "\x1B[34m" /* Blue */
+#define CLR_TRC "\x1B[32m" /* Green */
+#define CLR_WRN "\x1B[33m" /* Yellow */
+#define CLR_ERR "\x1B[31m" /* Red */
+#define CLR_CRT "\x1B[35m" /* Magenta */
+#define CLR_PNC "\x1B[35m" /* Magenta */
+#define CLR_RST "\x1B[0m"
/*
* According to BSD style, __dead is supposed to be defined in sys/cdefs.h,
#define CHECK_FORMAT(str, args) /* Nothing */
#endif
+struct log_listener {
+ char const *type;
+ char const *level;
+ char const *filename; /* file only */
+ bool print_times; /* console and file only */
+ bool color; /* console and file only */
+ int facility; /* syslog only */
+
+ TAILQ_ENTRY(log_listener) lh;
+};
+
+TAILQ_HEAD(log_listeners, log_listener);
+
+void log_setup(void); /* Enables pr_* functions */
+int log_init(struct log_listeners *); /* Loads configuration */
+void log_teardown(void);
+
/*
- * Only call this group of functions while you know there's only one thread.
+ * DBG (Debug) = Dirty lazy (debuggerless) bug hunting prints.
+ * These should be removed after the fix, and are always purged during releases.
+ * They go straight to standard output, bypassing listeners (and their levels).
+ *
+ * CLT (Clutter) = Low-level "I'm doing this now."
+ * TRC (Trace) = High-level "I'm doing this now."
+ * TRCs contextualize errors; CLTs give detailed information once the dev
+ * understands the context.
+ * TRCs are permanent; CLTs need to be compiled in. This is because I've
+ * extremely rarely found CLTs useful, even when debugging, and I got tired of
+ * them.
+ * In syslog, both are mapped to LOG_DEBUG.
+ *
+ * INF (Info) = Rare significant benign event.
*
- * log_setup() is an incomplete initialization meant to be called when the
- * program starts. Logging can be performed after log_setup(), but it will use
- * default values.
- * log_init() finishes initialization by loading the user's intended config.
- * log_teardown() reverts initialization.
+ * WRN (Warning) = Weirdness found, object not rejected, validation recovers.
+ * Means "this doesn't look rational, but I'll humor you anyway."
+ *
+ * ERR (Error) = Error, object rejected, validation recovers.
+ * Typical nonfatal expected error. A "checked exception," if you will.
+ *
+ * CRIT (Critical) = Apalling error, object rejected, validation recovers.
+ * A somehow nonfatal programming error.
+ * These include stack traces, and are not meant for reports.
+ *
+ * PNC (Panic) = Crippling error, validation dies.
+ * This is what CRIT used to be; Fort dies on the spot. Also includes a stack
+ * trace. Mapped to LOG_EMERG.
+ * With the exception of ENOMEMs, PNCs should never happen in production.
+ *
+ * WRNs and ERRs are redirected to reports during validations.
*/
-int log_setup(void);
-void log_start(void);
-void log_teardown(void);
+#ifdef PR_CLUTTER_ENABLED
+#define pr_clutter_enabled() true
+void pr_clutter(const char *, ...) CHECK_FORMAT(1, 2);
+#else
#define pr_clutter_enabled() false
#define pr_clutter(...)
+#endif
void pr_trc(const char *, ...) CHECK_FORMAT(1, 2);
void pr_inf(const char *, ...) CHECK_FORMAT(1, 2);
__dead void pr_panic(const char *, ...) CHECK_FORMAT(1, 2);
__dead void enomem_panic(void); /* Out of memory */
-#define PR_DBG(msg, ...) \
- printf(PR_COLOR_DBG "%s:%d (%s()): " msg PR_COLOR_RST "\n", \
- __FILE__, __LINE__, __func__, ##__VA_ARGS__)
-#define PR_HELLO \
- printf(PR_COLOR_DBG "%s:%d (%s())" PR_COLOR_RST "\n", \
- __FILE__, __LINE__, __func__)
+#define PR_DBG(msg, ...) do { \
+ printf(CLR_DBG "%s:%d (%s()): " msg CLR_RST "\n", \
+ __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+#define PR_HELLO do { \
+ printf(CLR_DBG "%s:%d (%s())" CLR_RST "\n", \
+ __FILE__, __LINE__, __func__); \
+ fflush(stdout); \
+ } while (0)
#endif /* SRC_LOG_H_ */