/* * Copyright (C) 2019 by Sukchan Lee * * This file is part of Open5GS. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "core-config-private.h" #if HAVE_CTYPE_H #include #endif #if HAVE_STDARG_H #include #endif #include "ogs-core.h" #define TA_NOR "\033[0m" /* all off */ #define TA_FGC_BLACK "\033[30m" /* Black */ #define TA_FGC_RED "\033[31m" /* Red */ #define TA_FGC_BOLD_RED "\033[1;31m" /* Bold Red */ #define TA_FGC_GREEN "\033[32m" /* Green */ #define TA_FGC_BOLD_GREEN "\033[1;32m" /* Bold Green */ #define TA_FGC_YELLOW "\033[33m" /* Yellow */ #define TA_FGC_BOLD_YELLOW "\033[1;33m" /* Bold Yellow */ #define TA_FGC_BOLD_BLUE "\033[1;34m" /* Bold Blue */ #define TA_FGC_BOLD_MAGENTA "\033[1;35m" /* Bold Magenta */ #define TA_FGC_BOLD_CYAN "\033[1;36m" /* Bold Cyan */ #define TA_FGC_WHITE "\033[37m" /* White */ #define TA_FGC_BOLD_WHITE "\033[1;37m" /* Bold White */ #define TA_FGC_DEFAULT "\033[39m" /* default */ typedef enum { OGS_LOG_STDERR_TYPE, OGS_LOG_FILE_TYPE, } ogs_log_type_e; typedef struct ogs_log_s { ogs_lnode_t node; ogs_log_type_e type; union { struct { FILE *out; const char *name; } file; }; struct { ED7(uint8_t color:1;, uint8_t timestamp:1;, uint8_t domain:1;, uint8_t level:1;, uint8_t fileline:1;, uint8_t function:1;, uint8_t linefeed:1;) } print; void (*writer)(ogs_log_t *log, ogs_log_level_e level, const char *string); } ogs_log_t; typedef struct ogs_log_domain_s { ogs_lnode_t node; int id; ogs_log_level_e level; const char *name; } ogs_log_domain_t; const char *level_strings[] = { NULL, "FATAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE", }; static OGS_POOL(log_pool, ogs_log_t); static OGS_LIST(log_list); static OGS_POOL(domain_pool, ogs_log_domain_t); static OGS_LIST(domain_list); static ogs_log_t *add_log(ogs_log_type_e type); static int file_cycle(ogs_log_t *log); static char *log_timestamp(char *buf, char *last, int use_color); static char *log_domain(char *buf, char *last, const char *name, int use_color); static char *log_content(char *buf, char *last, const char *format, va_list ap); static char *log_level(char *buf, char *last, ogs_log_level_e level, int use_color); static char *log_linefeed(char *buf, char *last); static void file_writer( ogs_log_t *log, ogs_log_level_e level, const char *string); void ogs_log_init(void) { ogs_pool_init(&log_pool, ogs_core()->log.pool); ogs_pool_init(&domain_pool, ogs_core()->log.domain_pool); ogs_log_add_domain("core", ogs_core()->log.level); ogs_log_add_stderr(); } void ogs_log_final(void) { ogs_log_t *log, *saved_log; ogs_log_domain_t *domain, *saved_domain; ogs_list_for_each_safe(&log_list, saved_log, log) ogs_log_remove(log); ogs_pool_final(&log_pool); ogs_list_for_each_safe(&domain_list, saved_domain, domain) ogs_log_remove_domain(domain); ogs_pool_final(&domain_pool); } void ogs_log_cycle(void) { ogs_log_t *log = NULL; ogs_list_for_each(&log_list, log) { switch(log->type) { case OGS_LOG_FILE_TYPE: file_cycle(log); default: break; } } } ogs_log_t *ogs_log_add_stderr(void) { ogs_log_t *log = NULL; log = add_log(OGS_LOG_STDERR_TYPE); ogs_assert(log); log->file.out = stderr; log->writer = file_writer; #if !defined(_WIN32) log->print.color = 1; #endif return log; } ogs_log_t *ogs_log_add_file(const char *name) { FILE *out = NULL; ogs_log_t *log = NULL; out = fopen(name, "a"); if (!out) return NULL; log = add_log(OGS_LOG_FILE_TYPE); ogs_assert(log); log->file.name = name; log->file.out = out; log->writer = file_writer; return log; } void ogs_log_remove(ogs_log_t *log) { ogs_assert(log); ogs_list_remove(&log_list, log); if (log->type == OGS_LOG_FILE_TYPE) { ogs_assert(log->file.out); fclose(log->file.out); log->file.out = NULL; } ogs_pool_free(&log_pool, log); } ogs_log_domain_t *ogs_log_add_domain(const char *name, ogs_log_level_e level) { ogs_log_domain_t *domain = NULL; ogs_assert(name); ogs_pool_alloc(&domain_pool, &domain); ogs_assert(domain); domain->name = name; domain->id = ogs_pool_index(&domain_pool, domain); domain->level = level; ogs_list_add(&domain_list, domain); return domain; } ogs_log_domain_t *ogs_log_find_domain(const char *name) { ogs_log_domain_t *domain = NULL; ogs_assert(name); ogs_list_for_each(&domain_list, domain) if (!ogs_strcasecmp(domain->name, name)) return domain; return NULL; } void ogs_log_remove_domain(ogs_log_domain_t *domain) { ogs_assert(domain); ogs_list_remove(&domain_list, domain); ogs_pool_free(&domain_pool, domain); } void ogs_log_set_domain_level(int id, ogs_log_level_e level) { ogs_log_domain_t *domain = NULL; ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool); domain = ogs_pool_find(&domain_pool, id); ogs_assert(domain); domain->level = level; } ogs_log_level_e ogs_log_get_domain_level(int id) { ogs_log_domain_t *domain = NULL; ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool); domain = ogs_pool_find(&domain_pool, id); ogs_assert(domain); return domain->level; } const char *ogs_log_get_domain_name(int id) { ogs_log_domain_t *domain = NULL; ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool); domain = ogs_pool_find(&domain_pool, id); ogs_assert(domain); return domain->name; } int ogs_log_get_domain_id(const char *name) { ogs_log_domain_t *domain = NULL; ogs_assert(name); domain = ogs_log_find_domain(name); ogs_assert(domain); return domain->id; } void ogs_log_install_domain(int *domain_id, const char *name, ogs_log_level_e level) { ogs_log_domain_t *domain = NULL; ogs_assert(domain_id); ogs_assert(name); domain = ogs_log_find_domain(name); if (domain) { ogs_warn("`%s` log-domain duplicated", name); if (level != domain->level) { ogs_warn("[%s]->[%s] log-level changed", level_strings[domain->level], level_strings[level]); ogs_log_set_domain_level(domain->id, level); } } else { domain = ogs_log_add_domain(name, level); ogs_assert(domain); } *domain_id = domain->id; } void ogs_log_set_mask_level(const char *_mask, ogs_log_level_e level) { ogs_log_domain_t *domain = NULL; if (_mask) { const char *delim = " \t\n,:"; char *mask = NULL; char *saveptr; char *name; mask = ogs_strdup(_mask); ogs_assert(mask); for (name = ogs_strtok_r(mask, delim, &saveptr); name != NULL; name = ogs_strtok_r(NULL, delim, &saveptr)) { domain = ogs_log_find_domain(name); if (domain) domain->level = level; } ogs_free(mask); } else { ogs_list_for_each(&domain_list, domain) domain->level = level; } } void ogs_log_set_timestamp(ogs_log_ts_e ts_default, ogs_log_ts_e ts_file) { ogs_log_t *log; if (ts_default == OGS_LOG_TS_UNSET) ts_default = OGS_LOG_TS_ENABLED; if (ts_file == OGS_LOG_TS_UNSET) ts_file = ts_default; ogs_list_for_each(&log_list, log) { switch (log->type) { case OGS_LOG_FILE_TYPE: log->print.timestamp = (ts_file == OGS_LOG_TS_ENABLED); break; default: log->print.timestamp = (ts_default == OGS_LOG_TS_ENABLED); break; } } } static ogs_log_level_e ogs_log_level_from_string(const char *string) { ogs_log_level_e level = OGS_ERROR; if (!strcasecmp(string, "none")) level = OGS_LOG_NONE; else if (!strcasecmp(string, "fatal")) level = OGS_LOG_FATAL; else if (!strcasecmp(string, "error")) level = OGS_LOG_ERROR; else if (!strcasecmp(string, "warn")) level = OGS_LOG_WARN; else if (!strcasecmp(string, "info")) level = OGS_LOG_INFO; else if (!strcasecmp(string, "debug")) level = OGS_LOG_DEBUG; else if (!strcasecmp(string, "trace")) level = OGS_LOG_TRACE; return level; } int ogs_log_config_domain(const char *domain, const char *level) { if (domain || level) { int l = ogs_core()->log.level; if (level) { l = ogs_log_level_from_string(level); if (l == OGS_ERROR) { ogs_error("Invalid LOG-LEVEL " "[none:fatal|error|warn|info|debug|trace]: %s\n", level); return OGS_ERROR; } } ogs_log_set_mask_level(domain, l); } return OGS_OK; } void ogs_log_vprintf(ogs_log_level_e level, int id, ogs_err_t err, const char *file, int line, const char *func, int content_only, const char *format, va_list ap) { ogs_log_t *log = NULL; ogs_log_domain_t *domain = NULL; char logstr[OGS_HUGE_LEN]; char *p, *last; int wrote_stderr = 0; ogs_list_for_each(&log_list, log) { domain = ogs_pool_find(&domain_pool, id); if (!domain) { fprintf(stderr, "No LogDomain[id:%d] in %s:%d", id, file, line); ogs_assert_if_reached(); } if (domain->level < level) return; p = logstr; last = logstr + OGS_HUGE_LEN; if (!content_only) { if (log->print.timestamp) p = log_timestamp(p, last, log->print.color); if (log->print.domain) p = log_domain(p, last, domain->name, log->print.color); if (log->print.level) p = log_level(p, last, level, log->print.color); } p = log_content(p, last, format, ap); if (err) { char errbuf[OGS_HUGE_LEN]; p = ogs_slprintf(p, last, " (%d:%s)", (int)err, ogs_strerror(err, errbuf, OGS_HUGE_LEN)); } if (!content_only) { if (log->print.fileline) p = ogs_slprintf(p, last, " (%s:%d)", file, line); if (log->print.function) p = ogs_slprintf(p, last, " %s()", func); if (log->print.linefeed) p = log_linefeed(p, last); } log->writer(log, level, logstr); if (log->type == OGS_LOG_STDERR_TYPE) wrote_stderr = 1; } if (!wrote_stderr) { int use_color = 0; #if !defined(_WIN32) use_color = 1; #endif p = logstr; last = logstr + OGS_HUGE_LEN; if (!content_only) { p = log_timestamp(p, last, use_color); p = log_level(p, last, level, use_color); } p = log_content(p, last, format, ap); if (!content_only) { p = ogs_slprintf(p, last, " (%s:%d)", file, line); p = ogs_slprintf(p, last, " %s()", func); p = log_linefeed(p, last); } fprintf(stderr, "%s", logstr); fflush(stderr); } } void ogs_log_printf(ogs_log_level_e level, int id, ogs_err_t err, const char *file, int line, const char *func, int content_only, const char *format, ...) { va_list args; va_start(args, format); ogs_log_vprintf(level, id, err, file, line, func, content_only, format, args); va_end(args); } void ogs_log_hexdump_func(ogs_log_level_e level, int id, const unsigned char *data, size_t len) { size_t n, m; char dumpstr[OGS_HUGE_LEN]; char *p, *last; last = dumpstr + OGS_HUGE_LEN; p = dumpstr; for (n = 0; n < len; n += 16) { p = ogs_slprintf(p, last, "%04x: ", (int)n); for (m = n; m < n + 16; m++) { if (m > n && (m % 4) == 0) p = ogs_slprintf(p, last, " "); if (m < len) p = ogs_slprintf(p, last, "%02x", data[m]); else p = ogs_slprintf(p, last, " "); } p = ogs_slprintf(p, last, " "); for (m = n; m < len && m < n + 16; m++) p = ogs_slprintf(p, last, "%c", isprint(data[m]) ? data[m] : '.'); p = ogs_slprintf(p, last, "\n"); } ogs_log_print(level, "%s", dumpstr); } static ogs_log_t *add_log(ogs_log_type_e type) { ogs_log_t *log = NULL; ogs_pool_alloc(&log_pool, &log); ogs_assert(log); memset(log, 0, sizeof *log); log->type = type; log->print.timestamp = 1; log->print.domain = 1; log->print.level = 1; log->print.fileline = 1; log->print.linefeed = 1; ogs_list_add(&log_list, log); return log; } static int file_cycle(ogs_log_t *log) { ogs_assert(log); ogs_assert(log->file.out); ogs_assert(log->file.name); fclose(log->file.out); log->file.out = fopen(log->file.name, "a"); ogs_assert(log->file.out); return 0; } static char *log_timestamp(char *buf, char *last, int use_color) { struct timeval tv; struct tm tm; char nowstr[32]; ogs_gettimeofday(&tv); ogs_localtime(tv.tv_sec, &tm); strftime(nowstr, sizeof nowstr, "%m/%d %H:%M:%S", &tm); buf = ogs_slprintf(buf, last, "%s%s.%03d%s: ", use_color ? TA_FGC_GREEN : "", nowstr, (int)(tv.tv_usec/1000), use_color ? TA_NOR : ""); return buf; } static char *log_domain(char *buf, char *last, const char *name, int use_color) { buf = ogs_slprintf(buf, last, "[%s%s%s] ", use_color ? TA_FGC_YELLOW : "", name, use_color ? TA_NOR : ""); return buf; } static char *log_level(char *buf, char *last, ogs_log_level_e level, int use_color) { const char *colors[] = { TA_NOR, TA_FGC_BOLD_RED, TA_FGC_BOLD_YELLOW, TA_FGC_BOLD_CYAN, TA_FGC_BOLD_GREEN, TA_FGC_BOLD_WHITE, TA_FGC_WHITE, }; buf = ogs_slprintf(buf, last, "%s%s%s: ", use_color ? colors[level] : "", level_strings[level], use_color ? TA_NOR : ""); return buf; } static char *log_content(char *buf, char *last, const char *format, va_list ap) { va_list bp; va_copy(bp, ap); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" buf = ogs_vslprintf(buf, last, format, bp); #pragma GCC diagnostic pop va_end(bp); return buf; } static char *log_linefeed(char *buf, char *last) { #if defined(_WIN32) if (buf > last - 3) buf = last - 3; buf = ogs_slprintf(buf, last, "\r\n"); #else if (buf > last - 2) buf = last - 2; buf = ogs_slprintf(buf, last, "\n"); #endif return buf; } static void file_writer( ogs_log_t *log, ogs_log_level_e level, const char *string) { fprintf(log->file.out, "%s", string); fflush(log->file.out); }