3. 使用syslog记录日志

使用syslog记录日志 学习卡片

任务说明

本案例要求将守护进程的日志输出接入系统syslog服务,并演示不同日志设施(facility)与级别(level)的配置方式。完成目标是:编写一个可独立运行的守护进程程序,它通过openlog/syslog/closelog接口记录启动、运行和退出事件,日志同时包含进程标识与时间信息,并可通过修改代码中的设施常量切换日志分类。

实现方案

程序采用标准daemon化流程(fork、setsid、chdir、umask、重定向标准描述符),随后调用openlog设置进程名与日志选项。日志记录点覆盖三种典型场景:启动成功(LOG_INFO)、周期性心跳(LOG_DEBUG,默认不输出)、收到终止信号(LOG_NOTICE)。设施通过LOG_DAEMON定义,如需切换为LOG_LOCAL0~LOG_LOCAL7或LOG_USER,仅需修改一处宏。

代码实现


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

static volatile sig_atomic_t g_running = 1;

static void handle_signal(int sig) {
    (void)sig;
    g_running = 0;
}

static void daemonize(void) {
    pid_t pid = fork();
    if (pid < 0) { exit(EXIT_FAILURE); }
    if (pid > 0) { exit(EXIT_SUCCESS); }

    if (setsid() < 0) { exit(EXIT_FAILURE); }

    pid = fork();
    if (pid < 0) { exit(EXIT_FAILURE); }
    if (pid > 0) { exit(EXIT_SUCCESS); }

    umask(0);
    if (chdir("/") < 0) { exit(EXIT_FAILURE); }

    int fd = open("/dev/null", O_RDWR);
    if (fd < 0) { exit(EXIT_FAILURE); }
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    if (fd > STDERR_FILENO) { close(fd); }
}

int main(void) {
    daemonize();

    /* 设置日志设施与选项 */
    openlog("syslog_demo", LOG_PID | LOG_NDELAY, LOG_DAEMON);

    /* 注册信号处理 */
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handle_signal;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);

    /* 启动日志 */
    syslog(LOG_INFO, "daemon started, pid=%d", getpid());

    int heartbeat = 0;
    while (g_running) {
        sleep(5);
        heartbeat++;
        /* 心跳日志默认级别为DEBUG,需在syslog配置中开启debug才能看到 */
        syslog(LOG_DEBUG, "heartbeat #%d", heartbeat);
    }

    /* 退出日志 */
    syslog(LOG_NOTICE, "daemon stopped, total heartbeats=%d", heartbeat);
    closelog();

    return EXIT_SUCCESS;
}

编译与运行


# 编译
gcc -Wall -O2 -o syslog_demo syslog_demo.c

# 启动守护进程
./syslog_demo

# 查看日志(不同发行版路径可能不同)
tail -f /var/log/syslog        # Debian/Ubuntu
tail -f /var/log/messages      # RHEL/CentOS

# 停止守护进程
pkill -TERM syslog_demo

验证方法与判定标准

验证步骤:

  1. 启动程序后,执行ps -ef | grep syslog_demo确认进程已daemon化(PPID为1,且无终端关联)。
  2. 查看系统日志文件,应出现一条包含syslog_demo、进程PID和daemon started的INFO级别记录。
  3. 执行pkill -TERM syslog_demo停止进程,日志中应新增一条包含daemon stopped的NOTICE级别记录。
  4. 默认情况下LOG_DEBUG心跳日志不会写入(取决于/etc/rsyslog.conf或/etc/syslog.conf中*.debug规则)。如需验证,可临时将守护进程的LOG_DEBUG改为LOG_INFO重新编译运行,观察心跳记录是否周期性出现。

判定标准:启动与停止两条日志均出现在系统日志中,且包含正确的进程名、PID和事件描述,即视为通过。若需切换设施,将代码中LOG_DAEMON改为LOG_LOCAL0并重新编译,日志将归属local0设施,可通过logger -p local0.info "test"对比验证分类效果。

日志设施与级别对照

syslog 设施与级别映射 设施 (Facility) LOG_DAEMON — 系统守护进程(本案例使用) LOG_USER — 用户进程(默认) LOG_LOCAL0~LOG_LOCAL7 — 本地自定义 级别 (Level) LOG_EMERG / LOG_ALERT / LOG_CRIT — 紧急 LOG_ERR — 错误 LOG_WARNING — 警告 LOG_NOTICE / LOG_INFO / LOG_DEBUG — 普通 本案例代码中的映射关系 openlog("syslog_demo", LOG_PID | LOG_NDELAY, LOG_DAEMON) ├─ LOG_PID → 日志中自动附加进程PID ├─ LOG_NDELAY → 立即连接syslogd(不延迟到首次syslog调用) └─ LOG_DAEMON → 日志归属daemon设施,可用logger -p daemon.info验证

关键说明

本案例代码完整实现了daemon化与syslog日志记录闭环。日志级别过滤由系统syslog服务配置决定(如/etc/rsyslog.conf中的规则),程序本身不做过滤。若需调整输出详细程度,可修改syslog()调用中的级别参数,或通过setlogmask()在程序内屏蔽低级别日志。验证时注意系统日志写入可能有数秒延迟,属正常现象。