You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下自定义C++应用日志轮转问题:无需重启修复方案咨询

解决方案

一、Linux系统层面无需重启进程的处理方法

方法1:通过进程文件描述符直接清空日志

  1. 先获取db_net进程的PID:
    pgrep db_net
    
  2. 执行以下命令清空该进程的stderr日志文件:
    echo > /proc/<PID>/fd/2
    
    原理是/proc/<PID>/fd/2是进程打开的stderr对应的文件引用,直接操作它会同步截断原文件,进程后续写入会从文件起始位置开始,不会产生文件空洞,磁盘空间会立即释放。

方法2:用truncate命令强制截断文件

针对XFS文件系统,truncate可以直接修改文件大小,不受进程是否持有文件句柄的影响:

truncate -s 0 log_file

这个命令会将log_file的大小强制设为0,进程后续写入会覆盖文件开头,磁盘空间会被及时回收。

注:echo > log_file失效的原因是,当进程以追加模式(O_APPEND)打开文件时,echo >虽会截断文件,但进程的写入偏移量仍保留在原文件末尾,后续写入会在文件中产生大量空洞(空洞不占实际磁盘空间,但文件大小显示仍为旧值),导致看起来文件没被清空。而上述两种方法会同步更新进程的文件偏移量,避免这个问题。

二、应用侧修复方案(C语言实现)

要实现类似MySQL、Nginx的日志刷新功能,核心是在不重启进程的情况下重新打开stderr对应的文件。关键是通过/proc/self/fd/2的符号链接获取stderr指向的实际文件名:

实现代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#define FD_STDERR 2
#define PROC_FD_PATH "/proc/self/fd/2"

int flush_stderr_log() {
    char log_path[PATH_MAX];
    ssize_t link_len;
    struct stat file_stat;

    // 读取符号链接获取实际日志文件名
    link_len = readlink(PROC_FD_PATH, log_path, sizeof(log_path) - 1);
    if (link_len == -1) {
        fprintf(stderr, "Failed to read log path: %s\n", strerror(errno));
        return -1;
    }
    log_path[link_len] = '\0';

    // 获取原文件权限,避免重新打开后权限变化
    if (stat(log_path, &file_stat) == -1) {
        fprintf(stderr, "Failed to stat log file: %s\n", strerror(errno));
        return -1;
    }

    // 关闭当前stderr
    if (close(FD_STDERR) == -1) {
        fprintf(stderr, "Failed to close stderr: %s\n", strerror(errno));
        return -1;
    }

    // 以截断模式重新打开日志文件
    int new_fd = open(log_path, O_WRONLY | O_CREAT | O_TRUNC, file_stat.st_mode & 0777);
    if (new_fd == -1) {
        perror("Failed to reopen log file");
        // 异常处理:可临时重定向到/dev/null避免日志丢失
        int dev_null = open("/dev/null", O_WRONLY);
        dup2(dev_null, FD_STDERR);
        close(dev_null);
        return -1;
    }

    // 确保新文件描述符为2(恢复stderr)
    if (new_fd != FD_STDERR) {
        if (dup2(new_fd, FD_STDERR) == -1) {
            perror("Failed to dup2 to stderr");
            close(new_fd);
            return -1;
        }
        close(new_fd);
    }

    fprintf(stderr, "Log flushed successfully\n");
    return 0;
}

// 注册SIGUSR1信号,用于触发日志刷新
void sigusr1_handler(int sig) {
    flush_stderr_log();
}

int main() {
    // 绑定信号处理函数
    signal(SIGUSR1, sigusr1_handler);

    // 模拟业务日志输出
    while (1) {
        fprintf(stderr, "Business log message\n");
        sleep(1);
    }

    return 0;
}

使用方式

  1. 编译程序:gcc -o db_net db_net.c
  2. 启动程序:./db_net 2> log_file
  3. 需要刷新日志时,发送SIGUSR1信号:kill -USR1 <PID>

注意事项:

  • 系统必须挂载/proc文件系统(默认Linux系统均已挂载),否则无法通过readlink获取日志文件名。
  • 进程需要具备读取/proc/self/fd/2和写入日志文件的权限。

内容的提问来源于stack exchange,提问作者Nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:43:16