首页 > 解决方案 > 如何正确写入日志文件?

问题描述

如何可靠地记录日志以确保多线程应用程序中条目的原子性?此外,我希望能够使用 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 这样的外部库。

标签: clinuxlogging

解决方案


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

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

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


推荐阅读