Linux下自定义C++应用日志轮转问题:无需重启修复方案咨询
解决方案
一、Linux系统层面无需重启进程的处理方法
方法1:通过进程文件描述符直接清空日志
- 先获取
db_net进程的PID:pgrep db_net - 执行以下命令清空该进程的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; }
使用方式
- 编译程序:
gcc -o db_net db_net.c - 启动程序:
./db_net 2> log_file - 需要刷新日志时,发送SIGUSR1信号:
kill -USR1 <PID>
注意事项:
- 系统必须挂载
/proc文件系统(默认Linux系统均已挂载),否则无法通过readlink获取日志文件名。 - 进程需要具备读取
/proc/self/fd/2和写入日志文件的权限。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

