#include #include #include #include #include #include #include #include #include #include #include "toml.h" #include "log_private.h" enum log_output { LOG_OUTPUT_STDERR, LOG_OUTPUT_FILE, LOG_OUTPUT_BOTH, }; struct log_config { enum log_output output; enum log_level level; char log_file[PATH_MAX]; }; struct logger { char config_file[PATH_MAX]; struct log_config config; int log_fd; int log_file_opened_day; }; static unsigned char level_str[6][6] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; 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"}; __thread struct logger *__thread_local_logger; /****************************************************************************** * Private API ******************************************************************************/ static void local_time(struct tm *local) { time_t t; time(&t); localtime_r(&t, local); } static int str_to_level(const char *level) { if (level == NULL) { return -1; } 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 { return -1; } } static int config_parse(struct log_config *config, const char *config_file) { int ret = -1; FILE *fp = NULL; char errbuf[200]; char *ptr_output = NULL; char *ptr_file = NULL; char *ptr_level = NULL; const char *ptr; toml_table_t *section = NULL; toml_table_t *table = NULL; fp = fopen(config_file, "r"); if (fp == NULL) { fprintf(stderr, "(logger) open config file %s failed, %s\n", config_file, strerror(errno)); goto error_out; } table = toml_parse_file(fp, errbuf, sizeof(errbuf)); if (table == NULL) { fprintf(stderr, "(logger) parse config file %s failed, %s\n", config_file, errbuf); goto error_out; } section = toml_table_in(table, "log"); if (section == NULL) { fprintf(stderr, "(logger) config file %s missing log section\n", config_file); goto error_out; } ptr = toml_raw_in(section, "output"); if (ptr == NULL || toml_rtos(ptr, &ptr_output) != 0) { fprintf(stderr, "(logger) config file %s missing log.output\n", config_file); goto error_out; } if (strcasecmp(ptr_output, "stderr") == 0) { config->output = LOG_OUTPUT_STDERR; } else if (strcasecmp(ptr_output, "file") == 0) { config->output = LOG_OUTPUT_FILE; } else if (strcasecmp(ptr_output, "both") == 0) { config->output = LOG_OUTPUT_BOTH; } else { fprintf(stderr, "(logger) config file %s invalid log.output\n", config_file); goto error_out; } if (config->output == LOG_OUTPUT_FILE || config->output == LOG_OUTPUT_BOTH) { ptr = toml_raw_in(section, "file"); if (ptr == NULL || toml_rtos(ptr, &ptr_file) != 0) { fprintf(stderr, "(logger) config file %s missing log.file\n", config_file); goto error_out; } strcpy(config->log_file, ptr_file); } ptr = toml_raw_in(section, "level"); if (ptr == NULL || toml_rtos(ptr, &ptr_level) != 0) { fprintf(stderr, "(logger) config file %s missing log.level\n", config_file); goto error_out; } config->level = (enum log_level)str_to_level(ptr_level); if ((int)config->level == -1) { fprintf(stderr, "config file %s invalid log.level\n", config_file); goto error_out; } ret = 0; error_out: if (ptr_output) { free(ptr_output); } if (ptr_file) { free(ptr_file); } if (ptr_level) { free(ptr_level); } if (table) { toml_free(table); } if (fp) { fclose(fp); } return ret; } static int log_file_reopen(struct logger *logger) { int new_fd; int old_fd; struct tm local; char buff[PATH_MAX * 2] = {0}; local_time(&local); snprintf(buff, sizeof(buff), "%s.%d-%02d-%02d", logger->config.log_file, local.tm_year + 1900, local.tm_mon + 1, local.tm_mday); new_fd = open(buff, O_WRONLY | O_APPEND | O_CREAT, 0644); if (new_fd == -1) { fprintf(stderr, "(logger) open() \"%s\" failed, %s\n", buff, strerror(errno)); return -1; } logger->log_file_opened_day = local.tm_mday; old_fd = logger->log_fd; logger->log_fd = new_fd; if (old_fd > 0) { close(old_fd); } return 0; } /****************************************************************************** * Public API ******************************************************************************/ struct logger *log_new(const char *config_file) { struct logger *logger = (struct logger *)calloc(1, sizeof(struct logger)); if (logger == NULL) { fprintf(stderr, "(logger) logger calloc() failed, %s\n", strerror(errno)); return NULL; } memcpy(&logger->config_file, config_file, strlen(config_file)); if (config_parse(&logger->config, config_file) != 0) { goto error_out; } if (logger->config.output == LOG_OUTPUT_FILE || logger->config.output == LOG_OUTPUT_BOTH) { if (log_file_reopen(logger) != 0) { goto error_out; } } return logger; error_out: log_free(logger); return NULL; } void log_free(struct logger *logger) { if (logger) { if (logger->config.output == LOG_OUTPUT_FILE || logger->config.output == LOG_OUTPUT_BOTH) { if (logger->log_fd > 0) { close(logger->log_fd); logger->log_fd = -1; } } free(logger); logger = NULL; } } int log_check_level(struct logger *logger, enum log_level level) { if (logger) { return level >= logger->config.level; } else { return 0; } } void log_reload_level(struct logger *logger) { struct log_config config = {}; if (config_parse(&config, logger->config_file) == 0) { logger->config.level = config.level; fprintf(stderr, "(logger) logger level reload to %s\n", level_str[config.level]); } else { fprintf(stderr, "(logger) logger level reload failed\n"); } } void log_print(struct logger *logger, enum log_level level, const char *module, const char *fmt, ...) { int nwrite; char buf[4096] = {0}; 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); // add tid p += snprintf(p, end - p, "%lu ", pthread_self()); // 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 (logger->config.output == LOG_OUTPUT_STDERR || logger->config.output == LOG_OUTPUT_BOTH) { fprintf(stderr, "%s", buf); } if (logger->config.output == LOG_OUTPUT_FILE || logger->config.output == LOG_OUTPUT_BOTH) { if (logger->log_file_opened_day != local.tm_mday) { log_file_reopen(logger); } do { nwrite = write(logger->log_fd, buf, p - buf); } while (nwrite == -1 && errno == EINTR); } }