#include "hlog.h" #include #include #include #include #include //#include "hmutex.h" #ifdef _WIN32 #pragma warning (disable: 4244) // conversion loss of data #include #define hmutex_t CRITICAL_SECTION #define hmutex_init InitializeCriticalSection #define hmutex_destroy DeleteCriticalSection #define hmutex_lock EnterCriticalSection #define hmutex_unlock LeaveCriticalSection #else #include // for gettimeofday #include #define hmutex_t pthread_mutex_t #define hmutex_init(mutex) pthread_mutex_init(mutex, NULL) #define hmutex_destroy pthread_mutex_destroy #define hmutex_lock pthread_mutex_lock #define hmutex_unlock pthread_mutex_unlock #endif //#include "htime.h" #define SECONDS_PER_HOUR 3600 #define SECONDS_PER_DAY 86400 // 24*3600 #define SECONDS_PER_WEEK 604800 // 7*24*3600; static int s_gmtoff = 28800; // 8*3600 struct logger_s { logger_handler handler; unsigned int bufsize; char* buf; int level; int enable_color; char format[64]; // for file logger char filepath[256]; unsigned long long max_filesize; int remain_days; int enable_fsync; FILE* fp_; char cur_logfile[256]; time_t last_logfile_ts; int can_write_cnt; hmutex_t mutex_; // thread-safe }; static void logger_init(logger_t* logger) { logger->handler = NULL; logger->bufsize = DEFAULT_LOG_MAX_BUFSIZE; logger->buf = (char*)malloc(logger->bufsize); logger->level = DEFAULT_LOG_LEVEL; logger->enable_color = 0; // NOTE: format is faster 6% than snprintf // logger->format[0] = '\0'; strncpy(logger->format, DEFAULT_LOG_FORMAT, sizeof(logger->format) - 1); logger->fp_ = NULL; logger->max_filesize = DEFAULT_LOG_MAX_FILESIZE; logger->remain_days = DEFAULT_LOG_REMAIN_DAYS; logger->enable_fsync = 1; logger_set_file(logger, DEFAULT_LOG_FILE); logger->last_logfile_ts = 0; logger->can_write_cnt = -1; hmutex_init(&logger->mutex_); } logger_t* logger_create() { // init gmtoff here time_t ts = time(NULL); struct tm* local_tm = localtime(&ts); int local_hour = local_tm->tm_hour; struct tm* gmt_tm = gmtime(&ts); int gmt_hour = gmt_tm->tm_hour; s_gmtoff = (local_hour - gmt_hour) * SECONDS_PER_HOUR; logger_t* logger = (logger_t*)malloc(sizeof(logger_t)); logger_init(logger); return logger; } void logger_destroy(logger_t* logger) { if (logger) { if (logger->buf) { free(logger->buf); logger->buf = NULL; } if (logger->fp_) { fclose(logger->fp_); logger->fp_ = NULL; } hmutex_destroy(&logger->mutex_); free(logger); } } void logger_set_handler(logger_t* logger, logger_handler fn) { logger->handler = fn; } void logger_set_level(logger_t* logger, int level) { logger->level = level; } void logger_set_level_by_str(logger_t* logger, const char* szLoglevel) { int loglevel = DEFAULT_LOG_LEVEL; if (strcmp(szLoglevel, "VERBOSE") == 0) { loglevel = LOG_LEVEL_VERBOSE; } else if (strcmp(szLoglevel, "DEBUG") == 0) { loglevel = LOG_LEVEL_DEBUG; } else if (strcmp(szLoglevel, "INFO") == 0) { loglevel = LOG_LEVEL_INFO; } else if (strcmp(szLoglevel, "WARN") == 0) { loglevel = LOG_LEVEL_WARN; } else if (strcmp(szLoglevel, "ERROR") == 0) { loglevel = LOG_LEVEL_ERROR; } else if (strcmp(szLoglevel, "FATAL") == 0) { loglevel = LOG_LEVEL_FATAL; } else if (strcmp(szLoglevel, "SILENT") == 0) { loglevel = LOG_LEVEL_SILENT; } else { loglevel = DEFAULT_LOG_LEVEL; } logger->level = loglevel; } void logger_set_format(logger_t* logger, const char* format) { if (format) { strncpy(logger->format, format, sizeof(logger->format) - 1); } else { logger->format[0] = '\0'; } } void logger_set_remain_days(logger_t* logger, int days) { logger->remain_days = days; } void logger_set_max_bufsize(logger_t* logger, unsigned int bufsize) { logger->bufsize = bufsize; logger->buf = (char*)realloc(logger->buf, bufsize); } void logger_enable_color(logger_t* logger, int on) { logger->enable_color = on; } void logger_set_file(logger_t* logger, const char* filepath) { strncpy(logger->filepath, filepath, sizeof(logger->filepath) - 1); // remove suffix .log char* suffix = strrchr(logger->filepath, '.'); if (suffix && strcmp(suffix, ".log") == 0) { *suffix = '\0'; } } void logger_set_max_filesize(logger_t* logger, unsigned long long filesize) { logger->max_filesize = filesize; } void logger_set_max_filesize_by_str(logger_t* logger, const char* str) { int num = atoi(str); if (num <= 0) return; // 16 16M 16MB const char* e = str; while (*e != '\0') ++e; --e; char unit; if (*e >= '0' && *e <= '9') unit = 'M'; else if (*e == 'B') unit = *(e-1); else unit = *e; unsigned long long filesize = num; switch (unit) { case 'K': filesize <<= 10; break; case 'M': filesize <<= 20; break; case 'G': filesize <<= 30; break; default: filesize <<= 20; break; } logger->max_filesize = filesize; } void logger_enable_fsync(logger_t* logger, int on) { logger->enable_fsync = on; } void logger_fsync(logger_t* logger) { hmutex_lock(&logger->mutex_); if (logger->fp_) { fflush(logger->fp_); } hmutex_unlock(&logger->mutex_); } const char* logger_get_cur_file(logger_t* logger) { return logger->cur_logfile; } static void logfile_name(const char* filepath, time_t ts, char* buf, int len) { struct tm* tm = localtime(&ts); snprintf(buf, len, "%s.%04d%02d%02d.log", filepath, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday); } static FILE* logfile_shift(logger_t* logger) { time_t ts_now = time(NULL); int interval_days = logger->last_logfile_ts == 0 ? 0 : (ts_now+s_gmtoff) / SECONDS_PER_DAY - (logger->last_logfile_ts+s_gmtoff) / SECONDS_PER_DAY; if (logger->fp_ == NULL || interval_days > 0) { // close old logfile if (logger->fp_) { fclose(logger->fp_); logger->fp_ = NULL; } else { interval_days = 30; } if (logger->remain_days >= 0) { char rm_logfile[256] = {0}; if (interval_days >= logger->remain_days) { // remove [today-interval_days, today-remain_days] logfile for (int i = interval_days; i >= logger->remain_days; --i) { time_t ts_rm = ts_now - i * SECONDS_PER_DAY; logfile_name(logger->filepath, ts_rm, rm_logfile, sizeof(rm_logfile)); remove(rm_logfile); } } else { // remove today-remain_days logfile time_t ts_rm = ts_now - logger->remain_days * SECONDS_PER_DAY; logfile_name(logger->filepath, ts_rm, rm_logfile, sizeof(rm_logfile)); remove(rm_logfile); } } } // open today logfile if (logger->fp_ == NULL) { logfile_name(logger->filepath, ts_now, logger->cur_logfile, sizeof(logger->cur_logfile)); logger->fp_ = fopen(logger->cur_logfile, "a"); logger->last_logfile_ts = ts_now; } // NOTE: estimate can_write_cnt to avoid frequent fseek/ftell if (logger->fp_ && --logger->can_write_cnt < 0) { fseek(logger->fp_, 0, SEEK_END); long filesize = ftell(logger->fp_); if (filesize > logger->max_filesize) { fclose(logger->fp_); logger->fp_ = NULL; // ftruncate logger->fp_ = fopen(logger->cur_logfile, "w"); // reopen with O_APPEND for multi-processes if (logger->fp_) { fclose(logger->fp_); logger->fp_ = fopen(logger->cur_logfile, "a"); } } else { logger->can_write_cnt = (logger->max_filesize - filesize) / logger->bufsize; } } return logger->fp_; } static void logfile_write(logger_t* logger, const char* buf, int len) { FILE* fp = logfile_shift(logger); if (fp) { fwrite(buf, 1, len, fp); if (logger->enable_fsync) { fflush(fp); } } } static int i2a(int i, char* buf, int len) { for (int l = len - 1; l >= 0; --l) { if (i == 0) { buf[l] = '0'; } else { buf[l] = i % 10 + '0'; i /= 10; } } return len; } int logger_print(logger_t* logger, int level, const char* fmt, ...) { if (level < logger->level) return -10; int year,month,day,hour,min,sec,us; #ifdef _WIN32 SYSTEMTIME tm; GetLocalTime(&tm); year = tm.wYear; month = tm.wMonth; day = tm.wDay; hour = tm.wHour; min = tm.wMinute; sec = tm.wSecond; us = tm.wMilliseconds * 1000; #else struct timeval tv; struct tm* tm = NULL; gettimeofday(&tv, NULL); time_t tt = tv.tv_sec; tm = localtime(&tt); year = tm->tm_year + 1900; month = tm->tm_mon + 1; day = tm->tm_mday; hour = tm->tm_hour; min = tm->tm_min; sec = tm->tm_sec; us = tv.tv_usec; #endif const char* pcolor = ""; const char* plevel = ""; #define XXX(id, str, clr) \ case id: plevel = str; pcolor = clr; break; switch (level) { LOG_LEVEL_MAP(XXX) } #undef XXX // lock logger->buf hmutex_lock(&logger->mutex_); char* buf = logger->buf; int bufsize = logger->bufsize; int len = 0; if (logger->enable_color) { len = snprintf(buf, bufsize, "%s", pcolor); } const char* p = logger->format; if (*p) { while (*p) { if (*p == '%') { switch(*++p) { case 'y': len += i2a(year, buf + len, 4); break; case 'm': len += i2a(month, buf + len, 2); break; case 'd': len += i2a(day, buf + len, 2); break; case 'H': len += i2a(hour, buf + len, 2); break; case 'M': len += i2a(min, buf + len, 2); break; case 'S': len += i2a(sec, buf + len, 2); break; case 'z': len += i2a(us/1000, buf + len, 3); break; case 'Z': len += i2a(us, buf + len, 6); break; case 'l': buf[len++] = *plevel; break; case 'L': for (int i = 0; i < 5; ++i) { buf[len++] = plevel[i]; } break; case 's': { va_list ap; va_start(ap, fmt); len += vsnprintf(buf + len, bufsize - len, fmt, ap); va_end(ap); } break; case '%': buf[len++] = '%'; break; default: break; } } else { buf[len++] = *p; } ++p; } } else { len += snprintf(buf + len, bufsize - len, "%04d-%02d-%02d %02d:%02d:%02d.%03d %s ", year, month, day, hour, min, sec, us/1000, plevel); va_list ap; va_start(ap, fmt); len += vsnprintf(buf + len, bufsize - len, fmt, ap); va_end(ap); } if (logger->enable_color) { len += snprintf(buf + len, bufsize - len, "%s", CLR_CLR); } if(lenhandler) { logger->handler(level, buf, len); } else { logfile_write(logger, buf, len); } hmutex_unlock(&logger->mutex_); return len; } static logger_t* s_logger = NULL; logger_t* hv_default_logger() { if (s_logger == NULL) { s_logger = logger_create(); atexit(hv_destroy_default_logger); } return s_logger; } void hv_destroy_default_logger(void) { if (s_logger) { logger_fsync(s_logger); logger_destroy(s_logger); s_logger = NULL; } } void stdout_logger(int loglevel, const char* buf, int len) { fprintf(stdout, "%.*s", len, buf); } void stderr_logger(int loglevel, const char* buf, int len) { fprintf(stderr, "%.*s", len, buf); } void file_logger(int loglevel, const char* buf, int len) { logfile_write(hv_default_logger(), buf, len); }