/* Icecast * * This program is distributed under the GNU General Public License, version 2. * A copy of this license is included with this source. * * Copyright 2019, Philipp "ph3-der-loewe" Schafft , */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "private.h" #define LOG_MAXLINELEN 1024 struct igloo_logmsg_tag { igloo_ro_base_t __base; char *msgid; char *cat; char *func; char *codefile; ssize_t codeline; struct timespec ts; igloo_loglevel_t level; igloo_logmsg_opt_t options; igloo_list_t *referenced; char *string; }; static void __free(igloo_ro_t self) { igloo_logmsg_t *logmsg = igloo_RO_TO_TYPE(self, igloo_logmsg_t); free(logmsg->msgid); free(logmsg->cat); free(logmsg->func); free(logmsg->codefile); free(logmsg->string); igloo_ro_unref(logmsg->referenced); } igloo_RO_PUBLIC_TYPE(igloo_logmsg_t, igloo_RO_TYPEDECL_FREE(__free) ); igloo_logmsg_t * igloo_logmsg_new(const char *name, igloo_ro_t associated, igloo_ro_t instance, const char *msgid, const char *cat, const char *func, const char *codefile, const ssize_t codeline, const struct timespec * ts, igloo_loglevel_t level, igloo_logmsg_opt_t options, igloo_list_t *referenced, const char *format, ...) { igloo_logmsg_t *logmsg = igloo_ro_new_raw(igloo_logmsg_t, name, associated, instance); va_list ap; char string[LOG_MAXLINELEN]; if (!logmsg) return NULL; logmsg->codeline = codeline; logmsg->level = level; logmsg->options = options; va_start(ap, format); igloo_private__vsnprintf(string, sizeof(string), format, ap); va_end(ap); do { #define __set_str(x) \ if (x) { \ logmsg->x = strdup((x)); \ if (!logmsg->x) \ break; \ } __set_str(msgid); __set_str(cat); __set_str(func); __set_str(codefile); logmsg->string = strdup(string); if (!logmsg->string) break; if (ts) { logmsg->ts = *ts; } else { if (clock_gettime(CLOCK_REALTIME, &(logmsg->ts)) != 0) break; } if (referenced) { if (igloo_ro_ref(referenced) != igloo_ERROR_NONE) break; logmsg->referenced = referenced; } return logmsg; } while (0); igloo_ro_unref(logmsg); return NULL; } #define __SETSTRING(x) \ if ((x)) { \ *(x) = msg->x; \ } int igloo_logmsg_get_context(igloo_logmsg_t *msg, const char **msgid, const char **cat, const char **func, const char **codefile, ssize_t *codeline, struct timespec *ts) { if (!msg) return -1; __SETSTRING(msgid); __SETSTRING(cat); __SETSTRING(func); __SETSTRING(codefile); __SETSTRING(codeline); __SETSTRING(ts); return 0; } int igloo_logmsg_get_message(igloo_logmsg_t *msg, igloo_loglevel_t *level, const char **string) { if (!msg) return -1; if (level) *level = msg->level; __SETSTRING(string); return 0; } int igloo_logmsg_get_extra(igloo_logmsg_t *msg, igloo_logmsg_opt_t *options, igloo_list_t **referenced) { if (!msg) return -1; if (options) *options = msg->options; if (referenced) { if (msg->referenced) { if (igloo_ro_ref(msg->referenced) != igloo_ERROR_NONE) return -1; *referenced = msg->referenced; } else { *referenced = NULL; } } return 0; } static const char * __level2str(igloo_loglevel_t level) { switch (level) { case igloo_LOGLEVEL__ERROR: return "<<>>"; break; case igloo_LOGLEVEL__NONE: return "NONE"; break; case igloo_LOGLEVEL_ERROR: return "EROR"; break; case igloo_LOGLEVEL_WARN: return "WARN"; break; case igloo_LOGLEVEL_INFO: return "INFO"; break; case igloo_LOGLEVEL_DEBUG: return "DBUG"; break; } return "<<>>"; } typedef enum { igloo_FST_FULL, igloo_FST_OLD, igloo_FST_NORMAL = igloo_FST_OLD } igloo_logmsg_formarter_subtype_t; static igloo_filter_result_t __handle(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t object) { igloo_logmsg_t *msg = igloo_RO_TO_TYPE(object, igloo_logmsg_t); igloo_logmsg_formarter_subtype_t sf = *(igloo_logmsg_formarter_subtype_t*)*backend_userdata; const char *level = NULL; time_t now; char pre[256+LOG_MAXLINELEN] = ""; int datelen; char flags[3] = " "; if (!msg) return igloo_FILTER_RESULT_DROP; level = __level2str(msg->level); switch (sf) { case igloo_FST_FULL: if (msg->options & igloo_LOGMSG_OPT_DEVEL) flags[0] = 'D'; if (msg->options & igloo_LOGMSG_OPT_ASKACK) flags[1] = 'A'; now = msg->ts.tv_sec; datelen = strftime(pre, sizeof(pre), "[%Y-%m-%d %H:%M:%S UTC]", gmtime(&now)); snprintf(pre+datelen, sizeof(pre)-datelen, " (%s) %s [%s] %s/%s(%s:%zi) %s\n", (msg->msgid ? msg->msgid : ""), level, flags, msg->cat, msg->func, msg->codefile, msg->codeline, msg->string); break; case igloo_FST_OLD: now = msg->ts.tv_sec; datelen = strftime(pre, sizeof(pre), "[%Y-%m-%d %H:%M:%S]", localtime(&now)); snprintf(pre+datelen, sizeof(pre)-datelen, " %s %s/%s %s\n", level, msg->cat, msg->func, msg->string); break; } igloo_io_write(igloo_RO_TO_TYPE(*backend_object, igloo_io_t), pre, strlen(pre), NULL); return igloo_FILTER_RESULT_PASS; } static int __flush(igloo_INTERFACE_BASIC_ARGS) { igloo_io_t *io = igloo_RO_TO_TYPE(*backend_object, igloo_io_t); if (!io) return 0; return igloo_io_flush(io, igloo_IO_OPFLAG_DEFAULTS|igloo_IO_OPFLAG_FULL) == igloo_ERROR_NONE ? 0 : -1; } static int __set_backend(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t backend) { int ret; if (!igloo_RO_IS_NULL(backend)) { if (!igloo_RO_IS_VALID(backend, igloo_io_t)) return -1; ret = igloo_ro_ref(backend); if (ret != 0) return ret; } igloo_ro_unref(*backend_object); *backend_object = backend; return 0; } static const igloo_objecthandler_ifdesc_t igloo_logmsg_formarter_ifdesc = { igloo_INTERFACE_DESCRIPTION_BASE(igloo_objecthandler_ifdesc_t), .is_thread_safe = 1, .handle = __handle, .flush = __flush, .set_backend = __set_backend }; igloo_objecthandler_t * igloo_logmsg_formarter(igloo_ro_t backend, const char *subformat, const char *name, igloo_ro_t associated, igloo_ro_t instance) { igloo_logmsg_formarter_subtype_t *sf = NULL; igloo_objecthandler_t *objecthandler; if (!subformat || strcmp(subformat, "default") == 0) subformat = "normal"; sf = malloc(sizeof(*sf)); if (!sf) return NULL; if (strcmp(subformat, "normal") == 0) { *sf = igloo_FST_NORMAL; } else if (strcmp(subformat, "full") == 0) { *sf = igloo_FST_FULL; } else if (strcmp(subformat, "old") == 0) { *sf = igloo_FST_OLD; } else { free(sf); return NULL; } objecthandler = igloo_objecthandler_new(&igloo_logmsg_formarter_ifdesc, NULL, sf, name, associated, instance); if (!objecthandler) { free(sf); } if (igloo_objecthandler_set_backend(objecthandler, backend) != 0) { igloo_ro_unref(objecthandler); return NULL; } return objecthandler; } typedef struct { igloo_loglevel_t level_min; igloo_loglevel_t level_max; igloo_logmsg_opt_t options_required; igloo_logmsg_opt_t options_absent; struct timespec ts_min; struct timespec ts_max; char *cat; } igloo_logmsg_filter_mask_t; static inline int __gt_timespec(struct timespec *a, struct timespec *b) { return a->tv_sec > b->tv_sec || (a->tv_sec == b->tv_sec && a->tv_nsec > b->tv_nsec); } static igloo_filter_result_t __test(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t object) { igloo_logmsg_t *msg = igloo_RO_TO_TYPE(object, igloo_logmsg_t); igloo_logmsg_filter_mask_t *mask = *backend_userdata; if (!msg) return igloo_FILTER_RESULT_DROP; if (msg->level < mask->level_min || msg->level > mask->level_max) return igloo_FILTER_RESULT_DROP; if ((msg->options & mask->options_required) != mask->options_required || (msg->options & mask->options_absent)) return igloo_FILTER_RESULT_DROP; if (__gt_timespec(&(mask->ts_min), &(msg->ts)) || __gt_timespec(&(msg->ts), &(mask->ts_max))) return igloo_FILTER_RESULT_DROP; if (mask->cat) { if (!msg->cat) return igloo_FILTER_RESULT_DROP; if (strcmp(msg->cat, mask->cat) != 0) return igloo_FILTER_RESULT_DROP; } return igloo_FILTER_RESULT_PASS; } static const igloo_filter_ifdesc_t igloo_logmsg_filter_ifdesc = { igloo_INTERFACE_DESCRIPTION_BASE(igloo_filter_ifdesc_t), .test = __test }; igloo_filter_t * igloo_logmsg_filter(igloo_loglevel_t level_min, igloo_loglevel_t level_max, igloo_logmsg_opt_t options_required, igloo_logmsg_opt_t options_absent, const struct timespec * ts_min, const struct timespec * ts_max, const char *cat, const char *name, igloo_ro_t associated, igloo_ro_t instance) { igloo_filter_t *filter; igloo_logmsg_filter_mask_t *mask = calloc(1, sizeof(*mask)); if (!mask) return NULL; mask->level_min = level_min; mask->level_max = level_max; mask->options_required = options_required; mask->options_absent = options_absent; /* No else needed, as {0, 0} is already in the past. */ if (ts_min) mask->ts_min = *ts_min; if (ts_max) { mask->ts_max = *ts_max; } else { /* BEFORE YEAR 2038 IMPORTANT REWRITE: Update date. */ mask->ts_max.tv_sec = 2145916800; /* this is 2038-01-01 */ } if (cat) { mask->cat = strdup(cat); if (!mask->cat) { free(mask); return NULL; } } filter = igloo_filter_new(&igloo_logmsg_filter_ifdesc, igloo_RO_NULL, mask, name, associated, instance); if (!filter) { free(mask); } return filter; }