open5gs/lib/core/ogs-log.c

626 lines
15 KiB
C

/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "core-config-private.h"
#if HAVE_CTYPE_H
#include <ctype.h>
#endif
#if HAVE_STDARG_H
#include <stdarg.h>
#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;
}
}
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);
}