2. 守护进程的日志记录
任务说明
本案例为第2个案例,目标是为守护进程添加日志记录功能。我们将基于第1个案例中创建的守护进程框架,实现一个独立的日志模块,将运行信息(如启动、退出、心跳)写入指定日志文件,每条日志包含时间戳和日志级别(INFO、WARN、ERROR)。本案例代码可独立编译运行,不依赖前文代码。
实现方案
创建一个日志模块 logger.c 和头文件 logger.h,提供以下接口:
log_init(const char *path):打开日志文件,失败返回 -1。log_write(int level, const char *fmt, ...):写入一条日志,自动添加时间戳和级别前缀。log_close(void):关闭日志文件。
同时提供一个 main.c,演示守护进程化后周期性写入心跳日志,并处理 SIGTERM 信号以记录退出日志。日志文件路径通过命令行参数传入,默认 /tmp/daemon.log。
代码实现
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#define LOG_INFO 0
#define LOG_WARN 1
#define LOG_ERROR 2
int log_init(const char *path);
void log_write(int level, const char *fmt, ...);
void log_close(void);
#endif
// logger.c
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include "logger.h"
static FILE *log_fp = NULL;
static const char *level_str(int level) {
switch (level) {
case LOG_INFO: return "INFO";
case LOG_WARN: return "WARN";
case LOG_ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
int log_init(const char *path) {
log_fp = fopen(path, "a");
if (!log_fp) return -1;
setvbuf(log_fp, NULL, _IOLBF, 0); // 行缓冲,便于实时查看
return 0;
}
void log_write(int level, const char *fmt, ...) {
if (!log_fp) return;
struct timeval tv;
struct tm tm;
char timebuf[32];
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm);
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm);
fprintf(log_fp, "[%s.%03ld] [%s] ", timebuf, tv.tv_usec / 1000, level_str(level));
va_list args;
va_start(args, fmt);
vfprintf(log_fp, fmt, args);
va_end(args);
fprintf(log_fp, "\n");
}
void log_close(void) {
if (log_fp) {
fclose(log_fp);
log_fp = NULL;
}
}
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include "logger.h"
static volatile sig_atomic_t running = 1;
static void handle_signal(int sig) {
(void)sig;
running = 0;
}
static void daemonize(void) {
pid_t pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // 父进程退出
setsid();
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS);
umask(0);
chdir("/");
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > 2) close(fd);
}
}
int main(int argc, char *argv[]) {
const char *log_path = "/tmp/daemon.log";
if (argc > 1) log_path = argv[1];
daemonize();
if (log_init(log_path) != 0) {
exit(EXIT_FAILURE);
}
signal(SIGTERM, handle_signal);
signal(SIGINT, handle_signal);
log_write(LOG_INFO, "daemon started, pid=%d", getpid());
int counter = 0;
while (running) {
sleep(5);
counter++;
log_write(LOG_INFO, "heartbeat #%d", counter);
}
log_write(LOG_INFO, "daemon stopped, total heartbeats=%d", counter);
log_close();
return 0;
}
编译与运行
gcc -Wall -o daemon_logger main.c logger.c
./daemon_logger /tmp/daemon.log
程序启动后立即守护化并返回 shell。日志写入 /tmp/daemon.log(或指定路径)。
验证方法
- 运行上述编译命令,确认无编译错误。
- 执行
./daemon_logger,shell 应立即返回,无前台输出。 - 等待约 12 秒后执行
cat /tmp/daemon.log,应看到类似以下内容(时间戳和心跳编号会随实际运行变化):
[2025-01-15 10:30:01.123] [INFO] daemon started, pid=12345
[2025-01-15 10:30:06.456] [INFO] heartbeat #1
[2025-01-15 10:30:11.789] [INFO] heartbeat #2
- 执行
kill -TERM 12345(将 12345 替换为实际 pid),再查看日志末尾,应包含daemon stopped记录。
判定标准:日志文件存在且每条记录包含 [日期 时间.毫秒] [级别] 前缀;启动后每 5 秒新增一条心跳记录;发送 SIGTERM 后进程退出并写入停止记录。