2024-01-30 18:07:08 +08:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <stdlib.h>
|
2024-01-31 14:45:50 +08:00
|
|
|
#include <pthread.h>
|
2024-01-30 18:07:08 +08:00
|
|
|
|
2024-01-26 14:41:40 +08:00
|
|
|
#include "log.h"
|
2024-01-30 18:07:08 +08:00
|
|
|
#include "toml.h"
|
|
|
|
|
|
|
|
|
|
enum log_output
|
|
|
|
|
{
|
|
|
|
|
LOG_OUTPUT_STDERR,
|
|
|
|
|
LOG_OUTPUT_FILE,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct log_config
|
|
|
|
|
{
|
|
|
|
|
enum log_output output;
|
|
|
|
|
enum log_level level;
|
2024-05-16 11:52:14 +08:00
|
|
|
char work_dir[1024];
|
|
|
|
|
char log_file[1024];
|
2024-01-30 18:07:08 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct log_context
|
|
|
|
|
{
|
|
|
|
|
struct log_config config;
|
|
|
|
|
int log_fd;
|
|
|
|
|
int log_file_reopen_day;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-21 15:06:48 +08:00
|
|
|
struct log_context g_log_context = {
|
|
|
|
|
.config = {.output = LOG_OUTPUT_STDERR, .level = LOG_DEBUG},
|
|
|
|
|
.log_fd = -1,
|
|
|
|
|
.log_file_reopen_day = 0,
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-30 18:07:08 +08:00
|
|
|
struct log_context *g_log_ctx = &g_log_context;
|
|
|
|
|
|
|
|
|
|
static unsigned char weekday_str[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
|
|
|
static unsigned char month_str[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
|
|
|
static unsigned char level_str[7][6] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "STATE"};
|
|
|
|
|
|
|
|
|
|
static inline void local_time(struct tm *local)
|
|
|
|
|
{
|
|
|
|
|
time_t t;
|
|
|
|
|
time(&t);
|
|
|
|
|
localtime_r(&t, local);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline enum log_level check_level(const char *level)
|
|
|
|
|
{
|
|
|
|
|
if (level == NULL)
|
|
|
|
|
{
|
|
|
|
|
return LOG_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(level, "TRACE") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_TRACE;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "DEBUG") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_DEBUG;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "INFO") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_INFO;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "WARN") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_WARN;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "ERROR") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_ERROR;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "FATAL") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_FATAL;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(level, "STATE") == 0)
|
|
|
|
|
{
|
|
|
|
|
return LOG_STATE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return LOG_NONE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return 0: success
|
|
|
|
|
// return -1: failed
|
|
|
|
|
static int parse_config(struct log_config *config, const char *cfg_file)
|
|
|
|
|
{
|
|
|
|
|
int ret = -1;
|
|
|
|
|
FILE *fp = NULL;
|
|
|
|
|
char errbuf[200];
|
|
|
|
|
const char *ptr;
|
|
|
|
|
toml_table_t *log_section = NULL;
|
|
|
|
|
toml_table_t *conf_table = NULL;
|
|
|
|
|
|
|
|
|
|
fp = fopen(cfg_file, "r");
|
|
|
|
|
if (fp == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "open config file %s failed, %s\n", cfg_file, strerror(errno));
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conf_table = toml_parse_file(fp, errbuf, sizeof(errbuf));
|
|
|
|
|
if (conf_table == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "parse config file %s failed, %s\n", cfg_file, errbuf);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log_section = toml_table_in(conf_table, "log");
|
|
|
|
|
if (log_section == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s missing log section\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// output
|
|
|
|
|
ptr = toml_raw_in(log_section, "output");
|
|
|
|
|
if (ptr == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s missing log.output\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
if (strcasecmp(ptr, "stderr") == 0)
|
|
|
|
|
{
|
|
|
|
|
config->output = LOG_OUTPUT_STDERR;
|
|
|
|
|
}
|
|
|
|
|
else if (strcasecmp(ptr, "file") == 0)
|
|
|
|
|
{
|
|
|
|
|
config->output = LOG_OUTPUT_FILE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s invalid log.output\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// file
|
|
|
|
|
if (config->output == LOG_OUTPUT_FILE)
|
|
|
|
|
{
|
|
|
|
|
ptr = toml_raw_in(log_section, "file");
|
|
|
|
|
if (ptr == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s missing log.file\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
2024-04-16 14:12:41 +08:00
|
|
|
// skip ""
|
|
|
|
|
strncpy(config->log_file, ptr + 1, strlen(ptr) - 2);
|
2024-01-30 18:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level
|
|
|
|
|
ptr = toml_raw_in(log_section, "level");
|
|
|
|
|
if (ptr == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s missing log.level\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
config->level = check_level(ptr);
|
|
|
|
|
if (config->level == LOG_NONE)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "config file %s invalid log.level\n", cfg_file);
|
|
|
|
|
goto error_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
|
|
error_out:
|
|
|
|
|
if (conf_table)
|
|
|
|
|
{
|
|
|
|
|
toml_free(conf_table);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fp)
|
|
|
|
|
{
|
|
|
|
|
fclose(fp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return 0: success
|
|
|
|
|
// return -1: failed
|
|
|
|
|
static int log_reopen()
|
|
|
|
|
{
|
|
|
|
|
int new_fd;
|
|
|
|
|
int old_fd;
|
|
|
|
|
struct tm local;
|
2024-05-16 11:52:14 +08:00
|
|
|
char buff[4096] = {0};
|
2024-01-30 18:07:08 +08:00
|
|
|
local_time(&local);
|
2024-05-16 11:52:14 +08:00
|
|
|
snprintf(buff, sizeof(buff), "%s/%s.%d-%02d-%02d",
|
|
|
|
|
g_log_ctx->config.work_dir, g_log_ctx->config.log_file,
|
|
|
|
|
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday);
|
2024-01-30 18:07:08 +08:00
|
|
|
|
|
|
|
|
new_fd = open(buff, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
|
|
|
|
if (new_fd == -1)
|
|
|
|
|
{
|
2024-04-16 14:12:41 +08:00
|
|
|
fprintf(stderr, "open() \"%s\" failed, %s\n", buff, strerror(errno));
|
2024-01-30 18:07:08 +08:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_log_ctx->log_file_reopen_day = local.tm_mday;
|
|
|
|
|
old_fd = g_log_ctx->log_fd;
|
|
|
|
|
g_log_ctx->log_fd = new_fd;
|
|
|
|
|
|
|
|
|
|
if (old_fd > 0)
|
|
|
|
|
{
|
|
|
|
|
close(old_fd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
* Public API
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
|
|
// return 0: success
|
|
|
|
|
// return -1: failed
|
|
|
|
|
int log_init(const char *config_file)
|
|
|
|
|
{
|
|
|
|
|
memset(g_log_ctx, 0, sizeof(struct log_context));
|
|
|
|
|
|
2024-05-16 11:52:14 +08:00
|
|
|
if (getcwd(g_log_ctx->config.work_dir, sizeof(g_log_ctx->config.work_dir)) == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "getcwd() failed, %s\n", strerror(errno));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 18:07:08 +08:00
|
|
|
if (parse_config(&g_log_ctx->config, config_file) != 0)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_log_context.config.output == LOG_OUTPUT_FILE)
|
|
|
|
|
{
|
|
|
|
|
if (log_reopen() != 0)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void log_free()
|
|
|
|
|
{
|
|
|
|
|
if (g_log_ctx->config.output == LOG_OUTPUT_FILE)
|
|
|
|
|
{
|
|
|
|
|
if (g_log_ctx->log_fd > 0)
|
|
|
|
|
{
|
|
|
|
|
close(g_log_ctx->log_fd);
|
|
|
|
|
g_log_ctx->log_fd = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 11:52:14 +08:00
|
|
|
int log_level_enabled(enum log_level level)
|
|
|
|
|
{
|
|
|
|
|
return level >= g_log_ctx->config.level;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 18:07:08 +08:00
|
|
|
void log_reload_level(const char *config_file)
|
|
|
|
|
{
|
|
|
|
|
struct log_config config;
|
|
|
|
|
if (parse_config(&config, config_file) != 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
g_log_context.config.level = config.level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void log_print(enum log_level level, const char *module, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
int nwrite;
|
2024-04-11 19:44:02 +08:00
|
|
|
char buf[4096] = {0};
|
2024-01-30 18:07:08 +08:00
|
|
|
char *p = buf;
|
|
|
|
|
char *end = buf + sizeof(buf);
|
|
|
|
|
va_list args;
|
|
|
|
|
struct tm local;
|
|
|
|
|
|
|
|
|
|
local_time(&local);
|
|
|
|
|
// add time
|
|
|
|
|
p += snprintf(p, end - p, "%s %s %d %02d:%02d:%02d %d ",
|
|
|
|
|
weekday_str[local.tm_wday], month_str[local.tm_mon], local.tm_mday, local.tm_hour, local.tm_min, local.tm_sec, local.tm_year + 1900);
|
2024-01-31 14:45:50 +08:00
|
|
|
// add tid
|
|
|
|
|
p += snprintf(p, end - p, "%lu ", pthread_self());
|
2024-01-30 18:07:08 +08:00
|
|
|
// add level
|
|
|
|
|
p += snprintf(p, end - p, "%s ", level_str[level]);
|
|
|
|
|
// add module
|
|
|
|
|
p += snprintf(p, end - p, "(%s), ", module);
|
|
|
|
|
// add content
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
p += vsnprintf(p, end - p, fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
// add end of line
|
|
|
|
|
p += snprintf(p, end - p, "\n");
|
|
|
|
|
|
|
|
|
|
if (g_log_ctx->config.output == LOG_OUTPUT_STDERR)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "%s", buf);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (g_log_ctx->log_file_reopen_day != local.tm_mday)
|
|
|
|
|
{
|
|
|
|
|
log_reopen();
|
|
|
|
|
}
|
2024-01-26 14:41:40 +08:00
|
|
|
|
2024-01-30 18:07:08 +08:00
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
nwrite = write(g_log_ctx->log_fd, buf, p - buf);
|
|
|
|
|
} while (nwrite == -1 && errno == EINTR);
|
|
|
|
|
}
|
|
|
|
|
}
|