与logrotate兼容记录

时间:2018-11-07 11:38:33

标签: c linux logrotate

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

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

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

我还理解应该有一种方法可以告诉我的程序何时该重新打开。我的程序已经有一个D-Bus界面,我考虑将其用于那些通知。

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

3 个答案:

答案 0 :(得分:3)

有几种常用方法:

  1. 您使用logrotate,并且您的程序应该能够捕获一个信号(通常为SIGHUP)作为关闭并重新打开其日志文件的请求。然后logrotate用后旋转脚本发送信号
  2. 您使用logrotate并且您的程序不知道它,但是可以重新启动。然后logrotate用postrotate脚本重新启动程序。缺点:如果程序启动费用昂贵,则可能不是最佳选择
  3. 您使用logrotate,但您的程序不知道它,但是您将copytruncate选项传递给logrotate。然后logrotate复制文件,然后将其截断。缺点:在比赛条件下,您可能会丢失消息。来自rotatelog.conf联机帮助页

      

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

  4. 您使用rotatelogs,它是httpd Apache的实用程序。无需直接写入文件,您可以将其日志通过管道传输到rotatelogs。然后rotatelogs管理不同的日志文件。缺点:您的程序应该能够登录到管道,或者您需要安装一个名为fifo的计算机。

但是要注意,对于关键日志,在每条消息后关闭文件可能会很有趣,因为这样可以确保在应用程序崩溃时所有内容都到达磁盘。

答案 1 :(得分:2)

  

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

     

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

否,您的程序应该就好像对logrotate一无所知。

  

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

不。应该只有一个日志文件可以打开和写入。 Logrotate将检查该文件,如果文件太大,它将复制/保存旧部分,并截断当前日志文件。因此,您的程序应完全透明地工作-不需要了解logrotate。

答案 2 :(得分:2)

尽管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)并以正确的顺序排序。