| | |
| | | /********************************************************************************* |
| | | * Copyright: (C) 2020 LingYun IoT System Studio |
| | | * Copyright: (C) 2023 LingYun IoT System Studio. |
| | | * All rights reserved. |
| | | * |
| | | * Filename: logger.c |
| | | * Description: This file is the linux infrastructural logger system library |
| | | * |
| | | * Version: 1.0.0(08/08/2020~) |
| | | * Description: This file is common logger API functions |
| | | * |
| | | * Version: 1.0.0(11/08/23) |
| | | * Author: Guo Wenxue <guowenxue@gmail.com> |
| | | * ChangeLog: 1, Release initial version on "08/08/2020 04:24:01 PM" |
| | | * |
| | | * ChangeLog: 1, Release initial version on "11/08/23 16:18:43" |
| | | * |
| | | ********************************************************************************/ |
| | | |
| | | #include <stdio.h> |
| | | #include <errno.h> |
| | | #include <stdlib.h> |
| | | #include <stdarg.h> |
| | | #include <string.h> |
| | | #include <time.h> |
| | | #include <unistd.h> |
| | | #include <sys/types.h> |
| | | #include <sys/time.h> |
| | | #include <pthread.h> |
| | | |
| | | #include "logger.h" |
| | | |
| | | #define PRECISE_TIME_FACTOR 1000 |
| | | typedef void (*log_LockFn)(void *udata, int lock); |
| | | |
| | | static unsigned long log_rollback_size = LOG_ROLLBACK_NONE; |
| | | static struct { |
| | | char file[32]; /* logger file name */ |
| | | FILE *fp; /* logger file pointer */ |
| | | long size; /* logger file max size */ |
| | | int level; /* logger level */ |
| | | log_LockFn lockfn; /* lock function */ |
| | | void *udata; /* lock data */ |
| | | } L; |
| | | |
| | | /* This library is not thread safe */ |
| | | static logger_t *logger = NULL; |
| | | static const char *level_names[] = { |
| | | "ERROR", |
| | | "WARN", |
| | | "INFO", |
| | | "DEBUG", |
| | | "TRACE" |
| | | }; |
| | | |
| | | char *log_str[LOG_LEVEL_MAX + 1] = { "", "F", "E", "W", "N", "D", "I", "T", "M" }; |
| | | static const char *level_colors[] = { |
| | | "\x1b[31m", |
| | | "\x1b[33m", |
| | | "\x1b[32m", |
| | | "\x1b[36m", |
| | | "\x1b[94m" |
| | | }; |
| | | |
| | | #define LOG_TIME_FMT "%Y-%m-%d %H:%M:%S" |
| | | |
| | | static void log_signal_handler(int sig) |
| | | static inline void time_to_str(char *buf) |
| | | { |
| | | if(!logger) |
| | | return ; |
| | | struct timeval tv; |
| | | struct tm *tm; |
| | | int len; |
| | | |
| | | if (sig == SIGHUP) |
| | | { |
| | | signal(SIGHUP, log_signal_handler); |
| | | log_fatal("SIGHUP received - reopenning log file [%s]", logger->file); |
| | | logger_reopen(); |
| | | } |
| | | gettimeofday(&tv, NULL); |
| | | tm = localtime(&tv.tv_sec); |
| | | |
| | | len = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%06d ", |
| | | tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, |
| | | tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec); |
| | | |
| | | buf[len] = '\0'; |
| | | } |
| | | |
| | | static void logger_banner(char *prefix) |
| | | static void mutex_lock(void *udata, int lock) |
| | | { |
| | | if(!logger) |
| | | return ; |
| | | int err; |
| | | pthread_mutex_t *l = (pthread_mutex_t *) udata; |
| | | |
| | | fprintf(logger->fp, "%s log \"%s\" on level [%s] size [%lu] KiB, log system version %s\n", |
| | | prefix, logger->file, log_str[logger->level], log_rollback_size / 1024, LOG_VERSION_STR); |
| | | #ifdef LOG_FILE_LINE |
| | | fprintf(logger->fp, " [ Date ] [ Time ] [ Level ] [ File/Line ] [ Message ]\n"); |
| | | #else |
| | | fprintf(logger->fp, " [ Date ] [ Time ] [ Level ] [ Message ]\n"); |
| | | #endif |
| | | fprintf(logger->fp, "-------------------------------------------------------------\n"); |
| | | } |
| | | |
| | | static void check_and_rollback(void) |
| | | { |
| | | if(!logger) |
| | | return ; |
| | | |
| | | if (log_rollback_size != LOG_ROLLBACK_NONE) |
| | | if (lock) |
| | | { |
| | | long _curOffset = ftell(logger->fp); |
| | | |
| | | if ((_curOffset != -1) && (_curOffset >= log_rollback_size)) |
| | | { |
| | | char cmd[512]; |
| | | |
| | | snprintf(cmd, sizeof(cmd), "cp -f %s %s.roll", logger->file, logger->file); |
| | | system(cmd); |
| | | |
| | | if (-1 == fseek(logger->fp, 0L, SEEK_SET)) |
| | | fprintf(logger->fp, "log rollback fseek failed \n"); |
| | | |
| | | rewind(logger->fp); |
| | | |
| | | truncate(logger->file, 0); |
| | | logger_banner("Already rollback"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /* log_size unit is KB */ |
| | | int logger_init(logger_t *log, char *log_file, int level, int log_size) |
| | | { |
| | | if( !log ) |
| | | { |
| | | printf("ERROR: Invalid input arguments\n"); |
| | | return -1; |
| | | } |
| | | |
| | | if( log_file ) |
| | | { |
| | | strncpy(log->file, log_file, FILENAME_LEN); |
| | | log->flag |= FLAG_LOGGER_FILE; |
| | | if ( (err = pthread_mutex_lock(l)) != 0 ) |
| | | log_error("Unable to lock log lock: %s", strerror(err)); |
| | | } |
| | | else |
| | | { |
| | | strncpy(log->file, DBG_LOG_FILE, FILENAME_LEN); |
| | | log->flag |= FLAG_LOGGER_CONSOLE; |
| | | if ( (err = pthread_mutex_unlock(l) != 0) ) |
| | | log_error("Unable to unlock log lock: %s", strerror(err)); |
| | | } |
| | | } |
| | | |
| | | int log_open(char *fname, int level, int size, int lock) |
| | | { |
| | | FILE *fp; |
| | | |
| | | L.level = level; |
| | | L.size = size*1024; |
| | | |
| | | if( !fname || !strcmp(fname, "console") || !strcmp(fname, "stderr") ) |
| | | { |
| | | strcpy(L.file, "console"); |
| | | L.fp = stderr; |
| | | L.size = 0; /* console don't need rollback */ |
| | | } |
| | | else |
| | | { |
| | | if ( !(fp = fopen(fname, "a+")) ) |
| | | { |
| | | fprintf(stderr, "%s() failed: %s\n", __func__, strerror(errno)); |
| | | return -2; |
| | | } |
| | | L.fp = fp; |
| | | strncpy(L.file, fname, sizeof(L.file)); |
| | | } |
| | | |
| | | log->level = level; |
| | | log->size = log_size; |
| | | |
| | | /* set static global $logger point to argument $log */ |
| | | logger = log; |
| | | if( lock ) |
| | | { |
| | | static pthread_mutex_t log_lock; |
| | | |
| | | pthread_mutex_init(&log_lock, NULL); |
| | | L.udata = (void *)&log_lock; |
| | | L.lockfn = mutex_lock; |
| | | } |
| | | |
| | | fprintf(L.fp, "\n"); |
| | | log_info("logger system(%s) start: file:\"%s\", level:%s, maxsize:%luKiB\n\n", |
| | | LOG_VERSION, L.file, level_names[level], size); |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | int logger_open(void) |
| | | void log_close(void) |
| | | { |
| | | struct sigaction act; |
| | | char *filemode; |
| | | if( L.fp && L.fp!=stderr ) |
| | | fclose(L.fp); |
| | | |
| | | if(!logger) |
| | | { |
| | | printf("ERROR: logger not initialise\n"); |
| | | return -1; |
| | | } |
| | | |
| | | log_rollback_size = logger->size <= 0 ? LOG_ROLLBACK_NONE : logger->size*1024; /* Unit KiB */ |
| | | |
| | | if ('\0' == logger->file[0]) |
| | | { |
| | | printf("ERROR: Logger filename not set\n"); |
| | | return -1; |
| | | } |
| | | |
| | | if (!strcmp(logger->file, DBG_LOG_FILE)) |
| | | { |
| | | logger->fp = stderr; |
| | | log_rollback_size = LOG_ROLLBACK_NONE; |
| | | logger->flag |= FLAG_LOGGER_CONSOLE; |
| | | goto OUT; |
| | | } |
| | | |
| | | filemode = (log_rollback_size==LOG_ROLLBACK_NONE) ? "a+" : "w+"; |
| | | |
| | | logger->fp = fopen(logger->file, filemode); |
| | | if (NULL == logger->fp) |
| | | { |
| | | fprintf(stderr, "Open log file \"%s\" in %s failure: %s\n", logger->file, filemode, strerror(errno)); |
| | | return -2; |
| | | } |
| | | |
| | | act.sa_handler = log_signal_handler; |
| | | sigemptyset(&act.sa_mask); |
| | | act.sa_flags = 0; |
| | | sigaction(SIGHUP, &act, NULL); |
| | | |
| | | OUT: |
| | | logger_banner("Initialize"); |
| | | |
| | | return 0; |
| | | if (L.udata ) |
| | | pthread_mutex_destroy( L.udata); |
| | | } |
| | | |
| | | void logger_close(void) |
| | | static void log_rollback(void) |
| | | { |
| | | if (!logger || !logger->fp ) |
| | | return; |
| | | char cmd[128]={0}; |
| | | long fsize; |
| | | |
| | | logger_banner("\nTerminate"); |
| | | logger_raw("\n\n\n\n"); |
| | | /* don't need rollback */ |
| | | if(L.size <= 0 ) |
| | | return ; |
| | | |
| | | fflush(logger->fp); |
| | | fsize = ftell(L.fp); |
| | | if( fsize < L.size ) |
| | | return ; |
| | | |
| | | fclose(logger->fp); |
| | | logger->fp = NULL; |
| | | /* backup current log file */ |
| | | snprintf(cmd, sizeof(cmd), "cp %s %s.bak", L.file, L.file); |
| | | system(cmd); |
| | | |
| | | /* rollback file */ |
| | | fseek(L.fp, 0, SEEK_SET); |
| | | truncate(L.file, 0); |
| | | |
| | | fprintf(L.fp, "\n"); |
| | | log_info("logger system(%s) rollback: file:\"%s\", level:%s, maxsize:%luKiB\n\n", |
| | | LOG_VERSION, L.file, level_names[L.level], L.size/1024); |
| | | |
| | | return ; |
| | | } |
| | | |
| | | int logger_reopen(void) |
| | | void _log_write(int level, const char *file, int line, const char *fmt, ...) |
| | | { |
| | | int rc = 0; |
| | | char *filemode; |
| | | va_list args; |
| | | char time_string[100]; |
| | | |
| | | if( !logger ) |
| | | return -1; |
| | | |
| | | log_rollback_size = logger->size <= 0 ? LOG_ROLLBACK_NONE : logger->size*1024; /* Unit KiB */ |
| | | |
| | | if (logger->flag & FLAG_LOGGER_CONSOLE ) |
| | | { |
| | | fflush(logger->fp); |
| | | logger->fp = stderr; |
| | | return 0; |
| | | } |
| | | |
| | | if (logger->fp) |
| | | { |
| | | logger_close(); |
| | | filemode = log_rollback_size==LOG_ROLLBACK_NONE ? "a+" : "w+"; |
| | | logger->fp = fopen(logger->file, filemode); |
| | | |
| | | if (logger->fp == NULL) |
| | | rc = -2; |
| | | } |
| | | else |
| | | { |
| | | rc = -3; |
| | | } |
| | | |
| | | if (!rc) |
| | | { |
| | | logger_banner("\nReopen"); |
| | | } |
| | | return rc; |
| | | } |
| | | |
| | | void logger_term(void) |
| | | { |
| | | if(!logger) |
| | | return ; |
| | | |
| | | logger_close(); |
| | | |
| | | logger = NULL; |
| | | } |
| | | |
| | | void logger_raw(const char *fmt, ...) |
| | | { |
| | | va_list argp; |
| | | |
| | | if (!logger || !logger->fp) |
| | | if ( !L.fp || level>L.level ) |
| | | return; |
| | | |
| | | check_and_rollback(); |
| | | /* Acquire lock */ |
| | | if ( L.lockfn ) |
| | | L.lockfn(L.udata, 1); |
| | | |
| | | va_start(argp, fmt); |
| | | vfprintf(logger->fp, fmt, argp); |
| | | va_end(argp); |
| | | } |
| | | log_rollback(); |
| | | |
| | | static void cp_printout(char *level, char *fmt, va_list argp) |
| | | { |
| | | char buf[MAX_LOG_MESSAGE_LEN]; |
| | | struct tm *local; |
| | | struct timeval now; |
| | | char timestr[256]; |
| | | /* check and rollback file */ |
| | | time_to_str(time_string); |
| | | |
| | | if(!logger) |
| | | return ; |
| | | |
| | | check_and_rollback(); |
| | | |
| | | gettimeofday(&now, NULL); |
| | | local = localtime(&now.tv_sec); |
| | | |
| | | strftime(timestr, 256, LOG_TIME_FMT, local); |
| | | vsnprintf(buf, MAX_LOG_MESSAGE_LEN, fmt, argp); |
| | | |
| | | #ifdef DUMPLICATE_OUTPUT |
| | | printf("%s.%03ld [%s] : %s", |
| | | timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, buf); |
| | | #endif |
| | | |
| | | if (logger->fp) |
| | | fprintf(logger->fp, "%s.%03ld [%s] : %s", timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, buf); |
| | | |
| | | if (logger->fp) |
| | | fflush(logger->fp); |
| | | } |
| | | |
| | | static void cp_printout_line(char *level, char *fmt, char *file, int line, va_list argp) |
| | | { |
| | | char buf[MAX_LOG_MESSAGE_LEN]; |
| | | struct tm *local; |
| | | struct timeval now; |
| | | char timestr[256]; |
| | | |
| | | if(!logger) |
| | | return ; |
| | | |
| | | check_and_rollback(); |
| | | |
| | | gettimeofday(&now, NULL); |
| | | local = localtime(&now.tv_sec); |
| | | |
| | | strftime(timestr, 256, LOG_TIME_FMT, local); |
| | | vsnprintf(buf, MAX_LOG_MESSAGE_LEN, fmt, argp); |
| | | |
| | | #ifdef DUMPLICATE_OUTPUT |
| | | printf("[%s.%03ld] <%s> <%s:%04d> : %s", |
| | | timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, file, line, buf); |
| | | #endif |
| | | |
| | | if (logger->fp) |
| | | /* Log to stderr */ |
| | | if ( L.fp == stderr ) |
| | | { |
| | | fprintf(logger->fp, "[%s.%03ld] <%s> <%s:%04d> : %s", |
| | | timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, file, line, buf); |
| | | |
| | | fflush(logger->fp); |
| | | fprintf(L.fp, "%s %s %-5s\x1b[0m \x1b[90m%s:%03d:\x1b[0m ", |
| | | time_string, level_colors[level], level_names[level], file, line); |
| | | } |
| | | } |
| | | |
| | | void logger_str(int level, const char *msg) |
| | | { |
| | | if (!logger || level>logger->level) |
| | | return; |
| | | |
| | | check_and_rollback(); |
| | | |
| | | if (logger->fp) |
| | | fwrite(msg, 1, strlen(msg), logger->fp); |
| | | |
| | | if(logger->fp) |
| | | fflush(logger->fp); |
| | | } |
| | | |
| | | void logger_min(int level, char *fmt, ...) |
| | | { |
| | | va_list argp; |
| | | |
| | | if (!logger || level>logger->level) |
| | | return; |
| | | |
| | | va_start(argp, fmt); |
| | | cp_printout(log_str[level], fmt, argp); |
| | | va_end(argp); |
| | | } |
| | | |
| | | void logger_line(int level, char *file, int line, char *fmt, ...) |
| | | { |
| | | va_list argp; |
| | | |
| | | if (!logger || level>logger->level) |
| | | return; |
| | | |
| | | va_start(argp, fmt); |
| | | cp_printout_line(log_str[level], fmt, file, line, argp); |
| | | |
| | | va_end(argp); |
| | | } |
| | | |
| | | #define LINELEN 81 |
| | | #define CHARS_PER_LINE 16 |
| | | static char *print_char = |
| | | " " |
| | | " " |
| | | " !\"#$%&'()*+,-./" |
| | | "0123456789:;<=>?" |
| | | "@ABCDEFGHIJKLMNO" |
| | | "PQRSTUVWXYZ[\\]^_" |
| | | "`abcdefghijklmno" |
| | | "pqrstuvwxyz{|}~ " |
| | | " " |
| | | " " |
| | | " ???????????????" |
| | | "????????????????" |
| | | "????????????????" |
| | | "????????????????" |
| | | "????????????????" |
| | | "????????????????"; |
| | | |
| | | void logger_dump(int level, char *buf, int len) |
| | | { |
| | | int rc; |
| | | int idx; |
| | | char prn[LINELEN]; |
| | | char lit[CHARS_PER_LINE + 2]; |
| | | char hc[4]; |
| | | short line_done = 1; |
| | | |
| | | if (!logger || level>logger->level) |
| | | return; |
| | | |
| | | rc = len; |
| | | idx = 0; |
| | | lit[CHARS_PER_LINE] = '\0'; |
| | | |
| | | while (rc > 0) |
| | | else /* Log to file */ |
| | | { |
| | | if (line_done) |
| | | snprintf(prn, LINELEN, "%08X: ", idx); |
| | | fprintf(L.fp, "%s %-5s %s:%03d: ", time_string, level_names[level], file, line); |
| | | } |
| | | |
| | | do |
| | | va_start(args, fmt); |
| | | vfprintf(L.fp, fmt, args); |
| | | va_end(args); |
| | | |
| | | fflush(L.fp); |
| | | |
| | | /* Release lock */ |
| | | if ( L.lockfn ) |
| | | L.lockfn(L.udata, 0); |
| | | } |
| | | |
| | | |
| | | void log_dump(int level, const char *prompt, char *buf, size_t len) |
| | | { |
| | | int i, j, ofset; |
| | | char line[256]; |
| | | unsigned char c; |
| | | unsigned char *buffer = (unsigned char *)buf; |
| | | |
| | | if (!L.fp || level>L.level) |
| | | return; |
| | | |
| | | if( prompt ) |
| | | _log_write(level, __FILE__, __LINE__, "%s\r\n", prompt); |
| | | |
| | | for(i=0; i<len; i+=16) |
| | | { |
| | | ofset = snprintf(line, sizeof(line), "%04x: ", i); |
| | | |
| | | /* print hex representation, and print spaces if end of buffer */ |
| | | for(j=0; j<16; j++) |
| | | { |
| | | unsigned char c = buf[idx]; |
| | | snprintf(hc, 4, "%02X ", c); |
| | | strncat(prn, hc, LINELEN); |
| | | |
| | | lit[idx % CHARS_PER_LINE] = print_char[c]; |
| | | if(i+j < len) |
| | | ofset += snprintf(line+ofset, sizeof(line)-ofset, "%02x ", buffer[i+j]); |
| | | else |
| | | ofset += snprintf(line+ofset, sizeof(line)-ofset, " "); |
| | | } |
| | | while (--rc > 0 && (++idx % CHARS_PER_LINE != 0)); |
| | | ofset += snprintf(line+ofset, sizeof(line)-ofset, " "); |
| | | |
| | | line_done = (idx % CHARS_PER_LINE) == 0; |
| | | if (line_done) |
| | | /* print ASCII representation */ |
| | | for(j=0; j<16; j++) |
| | | { |
| | | #ifdef DUMPLICATE_OUTPUT |
| | | printf("%s %s\n", prn, lit); |
| | | #endif |
| | | if (logger->fp) |
| | | fprintf(logger->fp, "%s %s\n", prn, lit); |
| | | if (i+j < len) |
| | | { |
| | | c = buffer[i+j]; |
| | | ofset += snprintf(line+ofset, sizeof(line)-ofset, "%c", (c>=32 && c<=126) ? c : '.'); |
| | | } |
| | | else |
| | | { |
| | | ofset += snprintf(line+ofset, sizeof(line)-ofset, " "); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!line_done) |
| | | { |
| | | int ldx = idx % CHARS_PER_LINE; |
| | | lit[ldx++] = print_char[(int)buf[idx]]; |
| | | lit[ldx] = '\0'; |
| | | |
| | | while ((++idx % CHARS_PER_LINE) != 0) |
| | | strncat(prn, " ", sizeof(prn)); |
| | | |
| | | #ifdef DUMPLICATE_OUTPUT |
| | | printf("%s %s\n", prn, lit); |
| | | #endif |
| | | if (logger->fp) |
| | | fprintf(logger->fp, "%s %s\n", prn, lit); |
| | | |
| | | if (L.fp) |
| | | fprintf(L.fp, "%s\r\n", line); |
| | | } |
| | | } |
| | | |