【问题标题】:How to write log files correctly?如何正确写入日志文件?
【发布时间】:2020-10-23 20:54:49
【问题描述】:

如何可靠地记录日志以确保多线程应用程序中条目的原子性?此外,我希望能够使用 logrotate 实用程序轮换日志。

下一个最简单的写日志变体:

  1. 打开/重新打开日志文件
  2. 写条目printf()
  3. 退出时关闭日志文件

这是我的例子:

// default log level
static Cl_loglevl loglevel = LOGLEVEL_NONE;
// log file descriptor (open with Cl_openlog)
static FILE *logfd = NULL;

/**
 * @brief Cl_openlog - open log file
 * @param logfile - file name
 * @return FILE struct or NULL if failed
 */
FILE *Cl_openlog(const char *logfile, Cl_loglevl loglvl){
    if(logfd){
        Cl_putlog(LOGLEVEL_ERROR, "Reopen log file\n");
        fclose(logfd);
        logfd = NULL;
        char newname[PATH_MAX];
        snprintf(newname, PATH_MAX, "%s.old", logfile);
        if(rename(logfile, newname)) WARN("Can't rename old log file");
    }
    if(loglvl < LOGLEVEL_CNT) loglevel = loglvl;
    if(!logfile) return NULL;
    if(!(logfd = fopen(logfile, "w"))) WARN(_("Can't open log file"));
    return logfd;
}

/**
 * @brief Cl_putlog - put message to log file
 * @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
 * @param fmt - format and the rest part of message
 * @return amount of symbols saved in file
 */
int Cl_putlog(Cl_loglevl lvl, const char *fmt, ...){
    if(lvl > loglevel || !logfd) return 0;
    char strtm[128];
    time_t t = time(NULL);
    struct tm *curtm = localtime(&t);
    strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
    int i = fprintf(logfd, "%s\t", strtm);
    va_list ar;
    va_start(ar, fmt);
    i += vfprintf(logfd, fmt, ar);
    va_end(ar);
    fflush(logfd);
    return i;
}

Cl_openlog 的调用允许轮换日志一次。我可以在SIG_USR1 处理程序中调用这个函数并通过logrotate 发送这个信号。但目前还不清楚如何正确写入文件以实现记录的原子性。

我不想使用像 log4c 这样的外部库来解决这种简单的问题。

【问题讨论】:

  • 无论何时访问日志文件(包括轮换时),您都可以使用互斥锁。或者您应该让每个日志文件写入事件打开并关闭日志文件(仍然使用互斥锁以防止同时访问),但是如果您执行大量日志记录,也可能会对性能产生影响。无论哪种方式,您都应该只在关键任务之后登录每个线程,这样日志互斥锁就不会减慢您的实际功能。此外,您还想查看现有的库(例如 log4c.sourceforge.net)。

标签: c linux logging


【解决方案1】:

您可以确保所有日志记录都是通过单个线程完成的,或者您可以使用互斥锁保护日志记录功能。
对于第一种情况,您可以有一个工作线程来轮询管道的读取端;然后每个线程将使用管道的写端来唤醒工作线程并让它管理其日志(分配在堆上的某处并通过管道通过地址传递)。
您的 SIGUSR1 处理程序 (logrotate) 也可以这样做。

请注意,向 pipe() 写入少于 PIPE_BUF 的内容保证不会被交错(因此它是原子的)。
因此,只写一个堆存储地址将始终是原子的。

最后但同样重要的是,您可以为每个线程使用不同的日志文件。

【讨论】:

    【解决方案2】:

    好吧,我终于做到了:

    // array with all opened logs - for error/warning messages
    static Cl_log *errlogs = NULL;
    static int errlogsnum = 0;
    
    /**
     * @brief Cl_createlog - create log file: init mutex, test file open ability
     * @param log - log structure
     * @return 0 if all OK
     */
    int Cl_createlog(Cl_log *log){
        if(!log || !log->logpath || log->loglevel <= LOGLEVEL_NONE || log->loglevel >= LOGLEVEL_CNT) return 1;
        FILE *logfd = fopen(log->logpath, "a");
        if(!logfd){
            WARN("Can't open log file");
            return 2;
        }
        fclose(logfd);
        pthread_mutex_init(&log->mutex, NULL);
        errlogs = realloc(errlogs, (++errlogsnum) *sizeof(Cl_log));
        if(!errlogs) errlogsnum = 0;
        else memcpy(&errlogs[errlogsnum-1], log, sizeof(Cl_log));
        return 0;
    }
    
    /**
     * @brief Cl_putlog - put message to log file with/without timestamp
     * @param timest - ==1 to put timestamp
     * @param log - pointer to log structure
     * @param lvl - message loglevel (if lvl > loglevel, message won't be printed)
     * @param fmt - format and the rest part of message
     * @return amount of symbols saved in file
     */
    int Cl_putlogt(int timest, Cl_log *log, Cl_loglevl lvl, const char *fmt, ...){
        if(!log || !log->logpath || log->loglevel < 0 || log->loglevel >= LOGLEVEL_CNT) return 0;
        if(lvl > log->loglevel) return 0;
        if(pthread_mutex_lock(&log->mutex)) return 0;
        int i = 0;
        FILE *logfd = fopen(log->logpath, "a");
        if(!logfd) goto rtn;
        if(timest){
            char strtm[128];
            time_t t = time(NULL);
            struct tm *curtm = localtime(&t);
            strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
            i = fprintf(logfd, "%s\t", strtm);
        }
        va_list ar;
        va_start(ar, fmt);
        i += vfprintf(logfd, fmt, ar);
        va_end(ar);
        fclose(logfd);
    rtn:
        pthread_mutex_unlock(&log->mutex);
        return i;
    }
    

    Cl_createlog() 我只是测试打开给定文件和初始化互斥锁的能力。在Cl_putlogt() 我打开给定的文件,写入并关闭它。用于使任何线程的写入原子化的互斥锁。

    数组errlogs 包含所有打开的用于写入关键消息的日志。

    【讨论】:

      猜你喜欢
      • 2019-11-02
      • 2013-11-26
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-02
      相关资源
      最近更新 更多