简介面向Linux/Unix系统开发者的syslog编程学习包聚焦系统日志的采集、过滤与上报场景适合正在学习系统编程或需要为应用集成日志功能的工程师。压缩包内仅有2个文件分别是C源文件与配套头文件整体容量仅5KB属于轻量级示例代码便于直接阅读和修改。目前已有270人学习下载可作为快速上手syslog接口的参考。借助syslog.h中的函数声明与syslog.c的实现示例可掌握openlog、syslog、closelog等核心调用的参数配置与优先级设置并理解如何将自定义消息按不同级别写入系统日志。同时资源也展示了与syslogd协同工作的基本模式有助于开发者根据实际需求扩展日志处理逻辑提升应用的健壮性与可维护性。1. syslog.rar_Linux/Unix编程_Unix_Linux_ 里装的到底是什么先说结论再动手如果你在一个项目交接包里见到 syslog.rar 这个名字大概率不是病毒而是一份 Linux/Unix 编程的 syslog 示例源码或讲课工程。它要解决的是个很朴素却容易翻车的问题程序里的日志怎么才能统一送进系统日志体系而不是散在各自的 stdout 文件里。syslog 在 Unix 时代就是标准通道到今天 rsyslog、systemd-journal 和各类集中日志平台都还在兼容它。下面我从一个压缩包怎么解压、怎么编译讲起把 syslog 的协议、代码、参数和坑一次说清。适合正在写 C 服务、嵌入式 Linux 程序或者要搭日志采集的读者。2. syslog 协议与编程选型先把“日志发到哪里”这件事拆清楚2.1 三种接入方式libc 封装、UDP socket 与 Unix domain socket写 Linux/Unix 日志程序第一件事不是写代码而是选入口。常见做法是三种很多新手只会用第一种等日志要跨机器时就卡住了。接入方式目标可靠性典型延迟适用场景syslog() / openlog()本地 /dev/log本地缓存满了会丢微秒级单机调试、常规服务日志UDP socket远端 rsyslog 的 514/5514尽力而为丢包不重传网络 RTT 量级跨机器集中采集Unix domain socket/var/run/log、/dev/log本机可靠但缓冲区有限微秒级高性能本地日志、代理转发TCP/TLS socket远端 6514 等可靠但可能头阻塞略高审计、支付等高价值日志syslog() 是 glibc 对 /dev/log 的封装开发者不用管 socket 生命周期调用 openlog 设置程序名然后 syslog() 一条条发。缺点是它只往本机送程序要跨机器上报日志还是得自己建 socket。嵌入式环境如果用的是 musl、uClibcsyslog() 也存在但有些裁剪版 libc 可能不带这时直接 socket 反而是最稳的。直接 socket 发 UDP 不复杂但要注意默认系统日志服务只监听 /dev/log并不监听 514 端口。你往内网某台机器的 514 塞数据对方没开 imudp 模块就静默丢弃TCP 会立刻被 resetUDP 连报错都没有这是后面排障时最典型的黑匣子。2.2 facility 与 severity消息为什么会“消失”在系统侧syslog 消息的第一部分叫 PRI由 facility 乘以 8 加 severity 得到。facility 决定消息归属哪类子系统severity 决定紧急程度。C 代码里你写LOG_USER|LOG_INFOfinally 到达 rsyslog 时规则匹配的就是这两个维度。facility 值关键字常见用途0LOG_KERN内核日志用户态不要用1LOG_USER用户进程默认归属5LOG_AUTH认证安全相关12LOG_NTP时间同步服务16~23LOG_LOCAL0~LOCAL7留给应用自定义severity 从 0 到 7 分别是 EMERG、ALERT、CRIT、ERR、WARNING、NOTICE、INFO、DEBUG。这个顺序很关键rsyslog 的*.info表示“info 及以上”如果规则写user.*user 下所有级别都收如果只写user.infodebug 和 notice 的处理方式就完全取决于下一条规则可能被丢。很多人以为 syslog() 返回 0 就等于日志落盘了其实它只代表消息进了内核 socket 缓冲区。之后 rsyslog 按配置决定写哪个文件、转给谁或者直接丢弃。我在项目里见过最隐蔽的一次程序里打LOG_LOCAL0|LOG_INFO系统默认规则把 LOCAL0 定向到一个没建好的目录日志全被 rsyslog 静默吞掉应用侧完全无感知。2.3 RFC3164 与 RFC5424日志头里藏着年份和时区的坑老式 syslog 消息长这样PRIOct 11 22:14:15 hostname tag[pid]: message这是 RFC3164 格式。问题在于没有年份、没有时区。跨年排障时日志一多你分不清去年十月和今年十月跨时区转发接收端拿到的“22:14:15”到底按哪边解释全看接收端配置。你要做日志审计或者对接集中平台我建议直接用 RFC5424 头1651 2025-10-11T22:14:15.123Z myhost myapp 1234 - - message它带版本号、ISO8601 时间戳、带时区还有结构化数据段。代价是很多老系统日志分析器不认但现代 rsyslog、journald、ELK 都兼容。自己写发送端时别偷懒用 RFC3164除非对方平台明确只支持老格式。2.4 系统日志服务rsyslog、syslog-ng 与 busybox syslogd 的配合方式应用层 syslog() 只负责把消息丢给本机日志守护进程真正落盘、转发、过滤的是那三个服务。Debian/Ubuntu 默认 rsyslogRHEL 早期常带 syslog-ng嵌入式设备多半是 busybox syslogd。它们的协议一致但配置语言完全不同。rsyslog 在 Debian 系只监听 /dev/log不监听网络端口这是近二十年安全默认。要收远端 UDP 日志得在配置里显式打开# /etc/rsyslog.d/remote.conf module(loadimudp) input(typeimudp port5514)其中imudp是 rsyslog 的 UDP 输入模块port是监听端口。注意 Linux 上进程绑定小于 1024 的端口需要 root 权限或文件 capabilities测试阶段用 5514 这类高位端口不要一上来就 sudo 跑程序。改成 514 后重启systemctl restart rsyslog用ss -lunp | grep 514确认监听成功这是最直接的验证。syslog-ng 的对应配置则是source s_net { udp(ip(0.0.0.0) port(5514)); };语法差很远。busybox 更简单syslogd -R 192.168.1.10:5514就能把日志转发出去但基本没有规则过滤能力。3. 把 syslog.rar 里的工程跑起来Linux 下最小 C 收发 Demo 与三处参数3.1 解包与工具链准备解压 rar 常用的几个 linux 命令拿到 syslog.rar先别急着改代码把环境检查做干净否则后面查问题会让你怀疑人生。解压这一步在坑里排第一因为 Windows 压缩包常带 CRLF 换行符和中文文件名。# 1. 查看压缩包内容 unrar l syslog.rar # 2. 完整解压 unrar x syslog.rar # 3. 如果发行版没带 unrar装 unar 也能解 # sudo apt install unar # unar syslog.rar # 4. 看工程结构 ls -l syslog/ # 5. 确认编译器 gcc --version make --versionunrar l只列出内容不释放文件适合先看包内目录是否混乱。unrar x解压时保留完整路径e则把所有文件摊到当前目录千万别用错。解压后第一件事是看文件行尾file src/*.c cat -v src/syslog_sender.c | head -5如果看到大量^M这样的字符说明文件是 CRLF 行尾gcc 能编过但字符串字面量如果跨行或者消息拼接处带\r日志解析会出怪问题。批量处理# 把当前工程下所有 .c/.h 的 Windows 行尾转成 Unix find syslog -name *.c -o -name *.h | xargs sed -i s/\r$//这步做完再编译。很多 Unix 上编译失败其实不是代码问题是行尾和 Makefile 的 Tab 字符被改坏Makefile 要求命令行以 Tab 开头Windows 编辑器常把 Tab 转成空格。3.2 发送端syslog() 一行接入的最小 C 代码最简发送端其实不长核心是 openlog 的三个参数。我一般会在每个工程里单独写一个 log.c避免业务代码到处裸调 syslog。#define _GNU_SOURCE #include syslog.h #include stdio.h #include string.h #include errno.h int main(void) { /* ident 是程序名LOG_PID 让 tag 带上进程号 */ openlog(myapp, LOG_PID | LOG_CONS | LOG_NDELAY, LOG_USER); syslog(LOG_INFO, hello syslog, seq%d, 1); syslog(LOG_ERR, something failed: %s, strerror(ENOENT)); closelog(); return 0; }编译运行gcc -Wall -O2 -o sender sender.c ./senderopenlog 的第二个参数里LOG_PID会把进程号打印成myapp[1234]:多实例部署时靠它区分是哪个进程。LOG_CONS表示本地 /dev/log 通路失败时把消息直接写到控制台调试期有用生产上开了可能干扰终端交互。LOG_NDELAY让 openlog 立刻连接 /dev/log否则第一次 syslog() 时才会连接那一次的延迟会略高。第三个参数是默认 facility。注意 syslog() 的第一个参数既包含 facility 又包含 severity你可以写syslog(LOG_DAEMON|LOG_ERR, ...)临时覆盖 openlog 里设定的 facility但一般建议固定一个 facility方便 rsyslog 规则归类。3.3 接收端能跑在用户态的 UDP 最小 C 实现把工程里的接收端核心逻辑抽出来一个最小 UDP receiver 就这么长#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include unistd.h #define PORT 5514 int main(void) { int fd socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in addr; memset(addr, 0, sizeof(addr)); addr.sin_family AF_INET; addr.sin_addr.s_addr htonl(INADDR_ANY); addr.sin_port htons(PORT); if (bind(fd, (struct sockaddr *)addr, sizeof(addr)) 0) { perror(bind); return 1; } char buf[4096]; /* 一次 recvfrom 只收一条 UDP 数据报 */ int n recvfrom(fd, buf, sizeof(buf) - 1, 0, NULL, NULL); if (n 0) { buf[n] \0; printf(%s\n, buf); } close(fd); return 0; }这里的SOCK_DGRAM对应 UDP收到的是完整数据报如果改成SOCK_STREAM就是 TCPrecvfrom 要换成 recv。bind 到INADDR_ANY表示监听所有网卡地址生产环境建议 bind 到业务内网 IP。端口用 5514 而不是 514原因是 1024 以下端口绑定需要特权测试阶段没必要往 sudo 火坑里跳。bind 失败最常见两个原因端口被占用可以用ss -lunp | grep 5514查另一个是权限换成 5514 基本就能避开。别在生产环境为了绑定 514 给整个程序 setcap那是给日志接收服务权限过大风险不值。验证一下./receiver printf 14Oct 11 22:14:15 myapp[1]: test\n | nc -u -w1 127.0.0.1 5514如果终端打印出消息说明网络通路通了接下来可以接系统的 rsyslog。3.4 从自定义收端换回 rsyslog把消息真正落到系统日志最小 receiver 只是给你验证协议格式真正干活的是 rsyslog。改配置# /etc/rsyslog.d/remote.conf module(loadimudp) input(typeimudp port5514)重启并确认监听systemctl restart rsyslog ss -lunp | grep 5514 logger -n 127.0.0.1 -P 5514 remote logger test然后另一个终端看日志tail -f /var/log/syslog看到 “remote logger test” 说明全链路通了。这里有三处参数要盯住imudp模块有没有加载成功rsyslog 日志里会有模块加载报错port和发送端目标端口必须一致ruleset 如果没有单独指定默认规则决定消息落哪个文件。很多系统里/var/log/messages和/var/log/syslog两个文件的规则不一样别只盯一个文件。注意 rsyslog 的 legacy 语法在老版本配置里很常见$ModLoad imudp、$UDPServerRun 514。如果接手的项目配置还是这种写法改的时候要保留原样不能混用新旧语法两套rsyslog 对混用会给出警告但行为可能不符合预期。4. 从 Demo 到可用日志服务多线程、队列与 UDP 丢包的取舍4.1 多线程程序里的 syslog线程安全不等于不丢不乱glibc 的 syslog() 内部有锁多线程调用不会崩但别高兴太早。日志风暴时全局锁会让所有业务线程排队等日志线程业务延迟突然飙升更隐蔽的是syslog() 只记录进程号多个线程写日志你分不清消息来自哪个线程。常见做法是包一层#include pthread.h #include syslog.h #include stdarg.h #include stdio.h static pthread_mutex_t log_lock PTHREAD_MUTEX_INITIALIZER; void app_log(int prio, const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); pthread_mutex_lock(log_lock); syslog(prio, %s, buf); pthread_mutex_unlock(log_lock); }这个封装把线程号带进消息里比如调用前拼一个pthread_self()进去同时用锁保证一行消息不会被其他线程截断。但锁本身是性能瓶颈所以更靠谱的结构是队列。4.2 队列设计业务线程不阻塞日志线程慢慢发生产级日志库的思路通常是业务线程把日志塞进一个有界环形队列专门的日志线程批量发送。队列满就丢丢之前计数。我常用下面这个结构#define LOG_QUEUE_SIZE 8192 #define LOG_MSG_MAX 256 struct log_msg { char data[LOG_MSG_MAX]; int len; }; static struct log_msg ring[LOG_QUEUE_SIZE]; static _Atomic unsigned int head, tail; static _Atomic unsigned long dropped; int log_enqueue(const char *data, int len) { unsigned int h atomic_load_explicit(head, memory_order_relaxed); unsigned int t atomic_load_explicit(tail, memory_order_relaxed); if (len LOG_MSG_MAX || h - t LOG_QUEUE_SIZE) { atomic_fetch_add_explicit(dropped, 1, memory_order_relaxed); return -1; } struct log_msg *slot ring[h (LOG_QUEUE_SIZE - 1)]; memcpy(slot-data, data, len); slot-len len; atomic_store_explicit(head, h 1, memory_order_relaxed); return 0; }队列参数有两个队列长度 8192 条、单条消息上限 256 字节算下来约 2MB 内存对服务器和嵌入式主控都算可控。队列满时返回 -1上层可以选择忽略也可以临时降级成把消息写到本地文件。注意这个无锁队列是单生产者多消费者才会真正无锁多线程并发写还是需要原子 CAS 或者直接退化成互斥锁别把注释里的简化当真。日志线程从 tail 取消息再用 UDP socket 批量发送。批量发送的意思是攒 10 条或 10 毫秒发一次减少系统调用。日志不能因为发送超时反过来把业务卡死所以 send 要用非阻塞模式int flags fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);这样 socket 缓冲区满时 send 返回 EAGAIN日志线程可以把这批消息继续留在队列里或者丢弃。没有这个设置UDP 在极端情况下也会把进程拖住。4.3 UDP 丢包是常态用序列号打出第一个“空洞”UDP 514 传日志默认就是丢。网上很多集中日志方案都遇到过机房间 0.1% 到 1% 的丢包率这不是网线问题是网卡缓冲、socket 缓冲和接收端处理速度三者不匹配造成。把日志都改 TCP日志流量一大TCP 的队头阻塞反而会让堆积更严重。先做个压测看清楚丢包。发送端用 bash 的 linux 脚本就行#!/bin/bash # 发送 20000 条 UDP 日志到本机 5514 total20000 start$(date %s%N) for ((i1; itotal; i)); do printf 14$(date %b %e %T) perf seq%d\n $i \ | nc -u -w1 127.0.0.1 5514 done end$(date %s%N) echo sent $total in $(( (end-start)/1000000 )) ms接收端用 python 统计序号空洞import socket import re s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((127.0.0.1, 5514)) seen set() while True: data, _ s.recvfrom(2048) m re.search(rbseq(\d), data) if m: seen.add(int(m.group(1))) if len(seen) % 1000 0: missing len(seen) and (max(seen) - min(seen) 1 - len(seen)) print(fgot {len(seen)}, missing {missing})参数里nc -u走 UDP-w1是等待收尾的超时防止循环卡死。这个脚本会把内核 socket 缓冲打满丢包率会真实暴露。看 missing 值如果稳定增长说明要么发送端太快要么接收端处理不过来。先调接收端缓冲参数sysctl -w net.core.rmem_max8388608 sysctl -w net.core.rmem_default8388608再不行就要考虑在发送端做批量合并。真正关键的高价值日志直接走 TCP 或者 RELP不要指望 UDP 重传日志场景下重传只会加剧拥塞。4.4 嵌入式 Linux 下的 syslog 差异嵌入式系统资源少rsyslog 太重常见的是 busybox syslogd。它没有 rsyslog 的规则引擎facility 和 severity 过滤能力很弱所以程序内自己做级别裁剪更重要。busybox syslogd 常用参数syslogd -n -s 64 -b 1 -R 192.168.1.10:5514-n表示不 fork 到后台适合交给 init 或 systemd 管理-s 64限制单个日志文件最大 64KB-b 1保留 1 个轮转备份-R指定远端日志服务器。嵌入式 Flash 存储有限别贪心设大日志文件否则设备跑几个月后坏块先找上门。内核日志和用户日志在嵌入式上很容易混在一起。dmesg看的是内核日志来自 /proc/kmsg用户的 printk 由 console_loglevel 控制落不落串口。程序里的 syslog 消息别往 /dev/kmsg 写那是内核专属权限要求也高。调试串口日志太多时调dmesg -n而不是去停业务程序的日志输出。5. syslog 编程避坑5 个让日志丢在路上的真实场景5.1 syslog() 返回 0系统日志里却什么都没有现象C 程序调用 syslog() 一切正常返回值是 0但 tail /var/log/syslog 看不到一条自己的消息。原因rsyslog 的规则把该 facility 定向到了别的文件*.info的默认规则根本没匹配上你写的 facility或者等级低于规则门槛比如规则只收 info你打的是 debug。日志从应用进 /dev/log 只是第一步后面 rsyslog 过滤是第二个黑匣子。解决先用 logger 做对照实验区分是应用问题还是系统配置问题。logger -p user.info probe from logger logger -p local0.info probe local0 tail -f /var/log/syslog如果 logger 的 user.info 能看见local0 看不见就去查 local0 被哪条规则接管grep -rn local0 /etc/rsyslog.conf /etc/rsyslog.d/常见处理是把规则指到业务自己的日志文件比如local0.* /var/log/myapp.log。改了配置记得systemctl restart rsyslog。5.2 跨年后时间错乱远程日志相差 8 小时现象日志里时间显示无年份跨年的两天日志翻起来顺序颠倒内网多台机器日志时间差一个时区。原因RFC3164 的头部没有年份和时区。设备时间如果是 UTC接收端按本地时间解读东八区自然差 8 小时。这个坑在 syslog.rar 这类老工程里几乎是必踩的。解决尽量让发送端直接发 RFC5424 格式时间戳带时区或者高价值日志在消息体里自己拼一个 epoch 字段。统一所有服务器系统时区为 UTC展示时再转本地时间这是集中日志平台的标准做法。不要在应用层把时间先转成字符串再发接收端格式化才是正路。5.3 日志一多业务调用跟着变慢现象日志量从每分钟几百条涨到几万条后业务接口耗时涨了三分之一strace 发现卡在 sendto 附近。原因syslog() 默认是阻塞写 /dev/log内核缓冲满后写操作等待glibc 内部还有锁日志风暴时所有调用 syslog() 的线程互相排队。rsyslog 接收端如果解析慢连锁反应又让缓冲堆积。解决先看实际缓冲压力。cat /proc/net/unix | grep /dev/log ss -lnp | grep :5514临时调内核缓冲只能缓解sysctl -w net.core.wmem_max4194304 sysctl -w net.core.wmem_default4194304根治是 4.2 的队列方案业务线程只入队发送线程批量处理发送 socket 设 O_NONBLOCK缓冲满直接丢并记 dropped 计数。记住一个原则日志可以丢业务不能卡。5.4 容器或新环境里连 /dev/log 报 Permission denied现象程序部署进容器后syslog() 打通了但系统日志目录里收不到strace 显示打开 /dev/log 时 Permission denied。原因容器里没有宿主机的 /dev/log或者挂载了但权限不对。宿主机 /dev/log 通常是 socket 文件属主 root:root权限 622普通用户能写吗很多场景是不能的。坑在容器里应用跑在非 root 用户又没有把宿主机的 /dev/log 挂进来。解决临时方案是容器启动时挂载宿主路径docker run -v /dev/log:/dev/log ...但这会把宿主日志 socket 直接暴露给容器干净的做法是容器内应用把日志写 stdout交给 docker logging driver要进系统日志就起一个轻量 syslog 代理进程把容器内 /dev/log 转发到宿主机 rsyslog 的 5514 端口。注意容器内起 syslogd 前先确认 socket 路径不同基础镜像的 /dev/log 位置不一致。5.5 代码在 Linux 编译通过换个 Unix 就链接失败现象同一个工程在 Linux 上gcc -o sender sender.c顺利产出放到 Solaris 或老 AIX 上编译链接阶段报 undefined reference tosocket、bind。原因Linux 把网络函数全塞在 libc 里传统 Unix 不是它们把 socket 相关函数放在 libsocket网络解析相关放在 libnslMakefile 里没链接这两个库。解决Makefile 里加一行。LIBS -lsocket -lnsl对应代码里syslog()本身在 libc真正需要这两个库的是 3.3 那种直接建 socket 的收发端。顺带提一个同源坑压缩包里的源码如果从 Windows 解压出来CRLF 行尾会让 syslog 消息的 tag 后面多一个\r接收端正则匹配 tag 全失败日志看起来是乱的。批量sed -i s/\r$//之后再对工程做一次 diff确认没有内容被误伤。6. 用端到端时间戳验证 syslog 链路一套能坚持到第二年的检查习惯验证 syslog 工程不能只看“消息出来了”还要看消息花了多久、有没有静默丢失。我的做法是给发送端打上本地纳秒时间戳和自增序号接收端记录差值。发送端关键行struct timespec ts; clock_gettime(CLOCK_REALTIME, ts); syslog(LOG_INFO, benchmark ts%ld.%09ld seq%d, ts.tv_sec, ts.tv_nsec, seq);接收端不要靠肉眼直接统计# 接收端命令过滤 bench 行算当前时间与 ts 的差值 tail -f /var/log/syslog | grep benchmark | \ awk {print $NF} | head -1000真正的链路延迟应该是发送端调 syslog 到接收端磁盘落盘的间隔只有同一台机器上同时跑收发端才能近似测出跨机器测的是网络延迟加接收端处理延迟中间有时间校准误差别把数字报得太精确。我现在的习惯是每改完一版日志代码先跑十分钟压测留下 CSV 记录再看 99 分位延迟而不是平均延迟。平均延迟会把定时批量发送摊得很平掩盖偶发的缓冲打满。压测脚本就是第 4 章那个 bash 循环稍微收一下速率改成每秒 5000 条跑到稳定再算 missing。另一个坚持到现在的习惯升级 rsyslog、换内核版本、调整过 socket 缓冲参数之后必然用logger -n做一次冒烟回归否则这类黑匣子会在半年后某次大促时突然发作。日志链路不值得炫技但它能决定你排障时是十分钟定位还是熬一个通宵。希望帮到你。本文还有配套的精品资源点击获取