/* * UCW Library -- Logging: Configuration of Log Streams * * (c) 2009 Martin Mares * * This software may be freely distributed and used according to the terms * of the GNU Lesser General Public License. */ #include #include #include #include #include #include #include #include #include #include #include #ifndef TEST /*** Configuration of streams ***/ struct stream_config { cnode n; char *name; char *file_name; int file_desc; char *syslog_facility; u32 levels; clist types; // simple_list of names clist substreams; // simple_list of names clist limits; // of struct limit_config's int microseconds; // Enable logging of precise timestamps int show_types; int syslog_pids; int errors_fatal; int stderr_follows; struct log_stream *ls; int mark; // Used temporarily in log_config_commit() }; struct limit_config { cnode n; clist types; // simple_list of names double rate; uint burst; }; static char * stream_init(void *ptr) { struct stream_config *c = ptr; c->levels = ~0U; c->file_desc = -1; return NULL; } static char * stream_commit(void *ptr) { struct stream_config *c = ptr; if (c->syslog_facility) { if (!log_syslog_facility_exists(c->syslog_facility)) return cf_printf("SyslogFacility `%s' is not recognized", c->syslog_facility); if (c->file_name) return "Both FileName and SyslogFacility selected"; if (c->microseconds) return "Syslog streams do not support microsecond precision"; } if (c->stderr_follows && !c->file_name) return "StdErrFollows requires a file-based stream"; return NULL; } static const char * const level_names[] = { #define P(x) #x, LOG_LEVEL_NAMES #undef P NULL }; static struct cf_section limit_config = { CF_TYPE(struct limit_config), CF_ITEMS { #define P(x) PTR_TO(struct limit_config, x) CF_LIST("Types", P(types), &cf_string_list_config), CF_DOUBLE("Rate", P(rate)), CF_UINT("Burst", P(burst)), #undef P CF_END } }; static struct cf_section stream_config = { CF_TYPE(struct stream_config), CF_INIT(stream_init), CF_COMMIT(stream_commit), CF_ITEMS { #define P(x) PTR_TO(struct stream_config, x) CF_STRING("Name", P(name)), CF_STRING("FileName", P(file_name)), CF_INT("FileDesc", P(file_desc)), CF_STRING("SyslogFacility", P(syslog_facility)), CF_BITMAP_LOOKUP("Levels", P(levels), level_names), CF_LIST("Types", P(types), &cf_string_list_config), CF_LIST("Substream", P(substreams), &cf_string_list_config), CF_LIST("Limit", P(limits), &limit_config), CF_INT("Microseconds", P(microseconds)), CF_INT("ShowTypes", P(show_types)), CF_INT("SyslogPID", P(syslog_pids)), CF_INT("ErrorsFatal", P(errors_fatal)), CF_INT("StdErrFollows", P(stderr_follows)), #undef P CF_END } }; static clist log_stream_confs; static struct stream_config * stream_find(const char *name) { CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs) if (!strcmp(c->name, name)) return c; return NULL; } static char * stream_resolve(struct stream_config *c) { if (c->mark == 2) return NULL; if (c->mark == 1) return cf_printf("Log stream `%s' has substreams which refer to itself", c->name); c->mark = 1; char *err; CLIST_FOR_EACH(simp_node *, s, c->substreams) { struct stream_config *d = stream_find(s->s); if (!d) return cf_printf("Log stream `%s' refers to unknown substream `%s'", c->name, s->s); if (err = stream_resolve(d)) return err; } c->mark = 2; return NULL; } static char * log_config_commit(void *ptr UNUSED) { // Verify uniqueness of names CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs) if (stream_find(c->name) != c) return cf_printf("Log stream `%s' defined twice", c->name); // Check that all substreams resolve and that there are no cycles char *err; CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs) if (err = stream_resolve(c)) return err; return NULL; } static struct cf_section log_config = { CF_COMMIT(log_config_commit), CF_ITEMS { CF_LIST("Stream", &log_stream_confs, &stream_config), CF_END } }; static void CONSTRUCTOR log_config_init(void) { cf_declare_section("Logging", &log_config, 0); } /*** Type sets ***/ static uint log_type_mask(clist *l) { if (clist_empty(l)) return ~0U; uint types = 0; CLIST_FOR_EACH(simp_node *, s, *l) if (!strcmp(s->s, "all")) return ~0U; else { /* * We intentionally ignore unknown types as not all types are known * to all programs sharing a common configuration file. This is also * the reason why Types is a list and not a bitmap. */ int type = log_find_type(s->s); if (type >= 0) types |= 1 << LS_GET_TYPE(type); } return types; } /*** Generating limiters ***/ /* * When limiting is enabled, we let log_stream->filter point to this function * and log_stream->user_data point to an array of pointers to token bucket * filters for individual message types. */ static int log_limiter(struct log_stream *ls, struct log_msg *m) { struct token_bucket_filter **limits = ls->user_data; if (!limits) return 0; struct token_bucket_filter *tbf = limits[LS_GET_TYPE(m->flags)]; if (!tbf) return 0; ASSERT(!(m->flags & L_SIGHANDLER)); if (m->flags & L_LOGGER_ERR) return 0; timestamp_t now = ((timestamp_t) m->tv->tv_sec * 1000) + (m->tv->tv_usec / 1000); ucwlib_lock(); int res = tbf_limit(tbf, now); ucwlib_unlock(); if (res < 0) { if (res == -1) { struct log_msg mm = *m; mm.flags |= L_LOGGER_ERR; mm.raw_msg = "(maximum logging rate exceeded, some messages will be suppressed)"; log_pass_msg(ls, &mm); } return 1; } else return 0; } static void log_apply_limits(struct log_stream *ls, struct limit_config *lim) { uint mask = log_type_mask(&lim->types); if (!mask) return; if (!ls->user_data) { ls->user_data = cf_malloc_zero(LS_NUM_TYPES * sizeof(struct token_bucket_filter *)); ls->filter = log_limiter; } struct token_bucket_filter **limits = ls->user_data; struct token_bucket_filter *tbf = cf_malloc_zero(sizeof(*lim)); tbf->rate = lim->rate; tbf->burst = lim->burst; tbf_init(tbf); for (uint i=0; i < LS_NUM_TYPES; i++) if (mask & (1 << i)) limits[i] = tbf; } /*** Generating streams ***/ char * log_check_configured(const char *name) { if (stream_find(name)) return NULL; else return cf_printf("Log stream `%s' not found", name); } static struct log_stream * do_new_configured(struct stream_config *c) { struct log_stream *ls; ASSERT(c); if (c->ls) return c->ls; if (c->file_name) ls = log_new_file(c->file_name, (c->stderr_follows ? FF_FD2_FOLLOWS : 0)); else if (c->file_desc >= 0) ls = log_new_fd(c->file_desc, (c->stderr_follows ? FF_FD2_FOLLOWS : 0)); else if (c->syslog_facility) ls = log_new_syslog(c->syslog_facility, (c->syslog_pids ? LOG_PID : 0)); else ls = log_new_stream(sizeof(*ls)); CLIST_FOR_EACH(simp_node *, s, c->substreams) log_add_substream(ls, do_new_configured(stream_find(s->s))); ls->levels = c->levels; if (c->microseconds) ls->msgfmt |= LSFMT_USEC; if (c->show_types) ls->msgfmt |= LSFMT_TYPE; if (c->errors_fatal) ls->stream_flags |= LSFLAG_ERR_IS_FATAL; ls->types = log_type_mask(&c->types); CLIST_FOR_EACH(struct limit_config *, lim, c->limits) log_apply_limits(ls, lim); c->ls = ls; return ls; } struct log_stream * log_new_configured(const char *name) { struct stream_config *c = stream_find(name); if (!c) die("Unable to find log stream %s", name); if (c->ls) return log_ref_stream(c->ls); return do_new_configured(c); } void log_configured(const char *name) { log_set_default_stream(log_new_configured(name)); } #else /* TEST */ #include #include int main(int argc, char **argv) { log_init(argv[0]); int c; while ((c = cf_getopt(argc, argv, CF_SHORT_OPTS, CF_NO_LONG_OPTS, NULL)) >= 0) die("No options here."); int type = log_register_type("foo"); struct log_stream *ls = log_new_configured("combined"); log_drop_stderr(); for (uint i=0; i<10; i++) { msg(L_INFO | ls->regnum | type, "Hello, universe!"); usleep(200000); } fprintf(stderr, "Alas, this was printed to stderr.\n"); log_close_all(); return 0; } #endif