【问题标题】:Logging compatibly with logrotate与 logrotate 兼容记录
【发布时间】:2019-04-10 20:08:56
【问题描述】:

我正在编写一个写入日志的 Linux 守护程序。我希望日志由 logrotate 旋转。该程序是用 C 语言编写的。

通常,我的程序会在启动时打开日志文件,然后根据需要写入条目,最后在退出时关闭日志文件。

为了使用 logrotate 支持日志轮换,我需要做些什么不同的事情?据我了解,我的程序应该能够在每次 logrotate 完成工作时重新打开日志文件。但是,我搜索的来源没有指定重新打开日志文件的确切含义。我需要对旧文件做些什么吗?我可以创建另一个同名的文件吗?我更喜欢非常具体的说明,比如一些简单的示例代码。

我也明白应该有办法告诉我的程序何时该重新打开。我的程序已经有一个 D-Bus 接口,我想将它用于那些通知。

注意:我不需要关于如何配置 logrotate 的说明。这个问题只是关于如何让我自己的软件与之兼容。

【问题讨论】:

    标签: c linux logrotate


    【解决方案1】:

    通常,我的程序会在启动时打开日志文件,然后 根据需要写入条目,最后在退出时关闭日志文件。

    为了支持日志轮换,我需要做些什么不同的事情 使用 logrotate?

    不,您的程序应该像对 logrotate 不了解一样工作。

    我需要对旧文件做些什么吗?我可以创建另一个同名的文件吗?

    没有。应该只有一个日志文件可以打开和写入。 Logrotate 将检查该文件,如果它变得太大,它会复制/保存旧部分,并截断当前日志文件。因此,您的程序应该完全透明地运行——它不需要了解任何有关 logrotate 的信息。

    【讨论】:

    • 请注意,这需要 logrotate 的“copytruncate”配置 - 如果无法告知日志记录程序重新打开其日志文件,则可以使用备用解决方案。该程序必须以附加模式打开日志文件才能工作 - 它可能会丢失日志消息。更好的方法是让 logrotate 与程序交互(例如,向其发送 unix 信号,或使用其他程序细节)以关闭程序并在旋转时重新打开日志文件。
    【解决方案2】:

    有几种常见的方式:

    1. 您使用logrotate 并且您的程序应该能够捕获一个信号(通常是SIGHUP)作为关闭和重新打开其日志文件的请求。然后logrotate 在 postrotate 脚本中发送信号
    2. 您使用logrotate 并且您的程序不知道它,但可以重新启动。然后logrotate 在 postrotate 脚本中重新启动您的程序。缺点:如果程序的启动成本很高,这可能不是最理想的
    3. 您使用logrotate 并且您的程序不知道它,但是您将copytruncate 选项传递给logrotate。然后logrotate 复制文件然后截断它。缺点:在竞争条件下,您可能会丢失消息。来自rotatelog.conf手册页

      ...请注意,复制文件和截断文件之间的时间片非常短,因此可能会丢失一些日志记录数据...

    4. 您使用rotatelogs,这是一个用于 httpd Apache 的实用程序。您的程序不是直接写入文件,而是将其日志通过管道传输到rotatelogs。然后rotatelogs 管理不同的日志文件。缺点:您的程序应该能够登录到管道,否则您将需要安装一个命名的 fifo。

    但请注意,对于关键日志,在每条消息之后关闭文件可能会很有趣,因为它可以确保在应用程序崩溃的情况下所有内容都已到达磁盘。

    【讨论】:

    • 回复:“但请注意,对于关键日志,在每条消息后关闭文件可能会很有趣,因为它可以确保在应用程序崩溃的情况下所有内容都已到达磁盘”。这是一个糟糕的建议。关闭文件与确保数据在磁盘上无关。
    • @navin:有一点是对的:关闭文件会将数据带到磁盘的一半,因为它只能保证不在用户级缓冲区或任何文件级系统缓冲区中。当然,文件系统仍然可以缓冲它。这意味着您不会受到程序崩溃的影响,但不会受到物理磁盘崩溃或系统崩溃的影响。但老实说,不受程序崩溃的影响通常是唯一的要求......
    【解决方案3】:

    虽然man logrotate 示例使用 HUP 信号,但我建议使用USR1USR2,因为通常使用 HUP 进行“重新加载配置”。因此,在 logrotate 配置文件中,例如

    /var/log/yourapp/log {
        rotate 7
        weekly
        postrotate
            /usr/bin/killall -USR1 yourapp
        endscript
    }
    

    棘手的一点是处理信号在记录过程中到达的情况。事实上,没有任何锁定原语(sem_post() 除外,这在此处没有帮助)是 async-signal safe,这一事实使其成为一个有趣的问题。

    最简单的方法是使用专用线程,在sigwaitinfo() 中等待,并在所有线程中阻塞信号。在退出时,进程自己发送信号,并加入专用线程。例如,

    #define  ROTATE_SIGNAL  SIGUSR1
    
    static pthread_t        log_thread;
    static pthread_mutex_t  log_lock = PTHREAD_MUTEX_INITIALIZER;
    static char            *log_path = NULL;
    static FILE *volatile   log_file = NULL;
    
    int log(const char *format, ...)
    {
        va_list  args;
        int      retval;
    
        if (!format)
            return -1;
        if (!*format)
            return 0;
    
        va_start(args, format);
        pthread_mutex_lock(&log_lock);
        if (!log_file)
            return -1;
        retval = vfprintf(log_file, format, args);
        pthread_mutex_unlock(&log_lock);
        va_end(args);
    
        return retval;
    }
    
    void *log_sighandler(void *unused)
    {
        siginfo_t info;
        sigset_t  sigs;
        int       signum;
    
        sigemptyset(&sigs);
        sigaddset(&sigs, ROTATE_SIGNAL);
    
        while (1) {
    
            signum = sigwaitinfo(&sigs, &info);
            if (signum != ROTATE_SIGNAL)
                continue;
    
            /* Sent by this process itself, for exiting? */
            if (info.si_pid == getpid())
                break;
    
            pthread_mutex_lock(&log_lock);
            if (log_file) {
                fflush(log_file);
                fclose(log_file);
                log_file = NULL;
            }
            if (log_path) {
                log_file = fopen(log_path, "a");
            }
            pthread_mutex_unlock(&log_lock);
        }
    
        /* Close time. */
        pthread_mutex_lock(&log_lock);
        if (log_file) {
            fflush(log_file);
            fclose(log_file);
            log_file = NULL;
        }
        pthread_mutex_unlock(&log_lock);
    
        return NULL;
    }
    
    /* Initialize logging to the specified path.
       Returns 0 if successful, errno otherwise. */
    int log_init(const char *path)
    {
        sigset_t          sigs;
        pthread_attr_t    attrs;
        int               retval;
    
        /* Block the rotate signal in all threads. */
        sigemptyset(&sigs);
        sigaddset(&sigs, ROTATE_SIGNAL);
        pthread_sigmask(SIG_BLOCK, &sigs, NULL);
    
        /* Open the log file. Since this is in the main thread,
           before the rotate signal thread, no need to use log_lock. */
        if (log_file) {
            /* You're using this wrong. */
            fflush(log_file);
            fclose(log_file);
        }
        log_file = fopen(path, "a");
        if (!log_file)
            return errno;
    
        log_path = strdup(path);
    
        /* Create a thread to handle the rotate signal, with a tiny stack. */
        pthread_attr_init(&attrs);
        pthread_attr_setstacksize(65536);
        retval = pthread_create(&log_thread, &attrs, log_sighandler, NULL);
        pthread_attr_destroy(&attrs);
        if (retval)
            return errno = retval;
    
        return 0;       
    }
    
    void log_done(void)
    {
        pthread_kill(log_thread, ROTATE_SIGNAL);
        pthread_join(log_thread, NULL);
        free(log_path);
        log_path = NULL;
    }
    

    想法是在main() 中,在记录或创建任何其他线程之前,调用log_init(path-to-log-file),注意保存日志文件路径的副本。它设置信号掩码(由您可能创建的任何线程继承),并创建辅助线程。在退出之前,您拨打log_done()。要将某些内容记录到日志文件中,请使用 log(),就像使用 printf() 一样。

    我个人还会在vfprintf() 行之前自动添加一个时间戳:

        struct timespec  ts;
        struct tm        tm;
    
        if (clock_gettime(CLOCK_REALTIME, &ts) == 0 &&
            localtime_r(&(ts.tv_sec), &tm) == &tm)
            fprintf(log_file, "%04d-%02d-%02d %02d:%02d:%02d.%03ld: ",
                              tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                              tm.tm_hour, tm.tm_min, tm.tm_sec,
                              ts.tv_nsec / 1000000L);
    

    这种YYYY-MM-DD HH:MM:SS.sss 格式的好处是它接近全球标准 (ISO 8601) 并按正确的顺序排序。

    【讨论】:

      猜你喜欢
      • 2014-06-02
      • 1970-01-01
      • 2021-10-10
      • 2023-03-28
      • 1970-01-01
      • 2015-07-17
      • 2018-07-04
      • 2017-01-20
      • 1970-01-01
      相关资源
      最近更新 更多