Logrotate 适用于从不关闭日志文件描述符的进程

syn*_*pis 7 c linux logging logrotate named-pipes

我想知道在对从不关闭文件描述符的进程进行 logrotate 时我有什么选择。我知道“重新启动服务”和复制截断选项。但是,假设重新启动进程是不可取的,应用程序不会响应 SIGHUP,并且由于潜在的数据丢失而无法接受复制截断,我不确定还剩下什么选项。

我想到的一个解决方案是在进程即将登录的地方已有一个命名管道。让另一个实用程序从该管道读取(复制到另一个日志文件)并让它对 SIGHUP 信号(来自 logrotate)做出反应。

现在我的问题是,是否已经有一个实用程序可以用于此目的?如果没有,为什么?这种方法有本质上的错误吗?

为了测试这一点,我做了 2 项测试,一项是通过复制截断确认数据丢失,另一项是测试我的“命名管道 + 实用程序”方法:

演示复制截断损失:

应用程序.c

int main(int argc, char *argv[]) {
    FILE* f = fopen("log.txt", "a");
    int cnt = 0;
    for (int i = 0; i < 1000000; i++) {
        for (int j = 0; j < 100; j++) {
                fprintf(f, "Logging line %d\n", cnt);
            cnt++;
        }
        fflush(f);
    }
    fflush(f);
    fclose(f);
    printf("Wrote %d lines\n", cnt);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

以及附带的 logrotate 配置:

$cat /etc/logrotate.d/logexp 
/home/synepis/git/logexp/log.txt {
    size 20M
    create 700 synepis users 
    rotate 4
    copytruncate
}
Run Code Online (Sandbox Code Playgroud)

最后,我运行了该应用程序,在运行期间我通过以下方式手动启动了 logrotate 几次:

logrotate --force /etc/logrotate.d/logexp
Run Code Online (Sandbox Code Playgroud)

应用程序结果:

./app
Wrote 100000000 lines
Run Code Online (Sandbox Code Playgroud)

日志行数:

$ cat log.txt* | wc -l
69091700
Run Code Online (Sandbox Code Playgroud)

日志实用方法:

我实现了一个简单的实用程序“loghup”,它创建一个命名管道“log.txt”,然后将其简单地读取到“safe_log.txt”。最后,它通过重新打开文件来响应 SIGHUP(从而开始新的日志轮换)。

loghup.c

int sighup = 0;

void sig_handler(int signo) {
    if (signo == SIGHUP)
        sighup = 1;
}

void do_piping(char *input, char *output) {
    int fi = open(input, O_RDONLY);
    int fo = open(output, O_WRONLY | O_CREAT, 0644);

    size_t ret;
    char buff[4096];
    while((ret = read(fi, buff, 4096)) != 0) {
        if(ret == -1 && errno == EINTR) { // Retry later
            continue;
        } else if (ret == -1)  {
            break; // Error occured
        }

        write(fo, buff, ret);

        if (sighup) { // Reopen output log file on SIGHUP
            close(fo);
            fo = open(output, O_WRONLY | O_CREAT, 0644);
            sighup = 0;
        }
    }
    close(fo);
    close(fi);
}

int main(int argc, char *argv[]) {
    char *input_file = argv[1];
    char *output_file = argv[2];
    signal(SIGHUP, sig_handler); // Setup signal handler
    mkfifo(input_file, S_IRUSR | S_IWUSR);  // Create named pipe
    do_piping(input_file, output_file);
}
Run Code Online (Sandbox Code Playgroud)

带 SIGHUP 的新 logrotate 配置:

$cat /etc/logrotate.d/logexp 
/home/synepis/git/logexp/safe_log.txt {
    size 20M
    create 700 synepis users 
    rotate 4
    postrotate
        /bin/kill -SIGHUP $(ps aux | grep "[l]oghup" | awk '{print $2}')
    endscript
}
Run Code Online (Sandbox Code Playgroud)

然后我跑了:

./app
./loghup log.txt safe_log.txt
Run Code Online (Sandbox Code Playgroud)

最后强制 logrotate 几次:

$ cat safe_log.txt* | wc -l
100000000
Run Code Online (Sandbox Code Playgroud)