Linux 系统编程-syslog
syslog
这里说的 syslog
仅仅是api
,man 3 syslog
有详细的说明。简单来说,如下图所示,应用通过 syslog()
这个调用将日志信息传递给 syslogd
(我们可以称为日志收集器)。syslogd
可以根据配置文件(一般位于 /etc/syslog.conf
来决定如何处理日志信息)。比如保存到某个文件、发送到远程主机、显示到控制台等。
1 | app1 app2 app3 (产生日志) |
我们要把日志信息传递给 syslogd
这个守护进程,实际上就是两个进程之间的通信,这里采用的是本地套接字 AF_UNIX
这种方式实现。可以参考 [Linux 系统编程-进程通信](./Linux 系统编程-进程通信.md)
基础用法
对使用来说,很简单✨,就只涉及到 3 个 API
调用。
虽然说
syslog
协议是可以通过网络将日志信息传递给其他网络地址,但这是syslogd
这个守护进程的工作,对应用而言,它只需要将日志信息传递给syslogd
,后者会根据配置文件来决定如何处理这些日志信息。
1 |
|
openlog
:为当前应用和system logger
建立连接!其实就是和syslogd
通过本地套接字实现进程间通信。syslog/vsyslog
:生成一条日志信息,它将被传递给日志收集器syslogd
;closelog
:断开和system logger
的连接。
openlog
建立本地套接字连接。对
musl
这个库而言,本地套接字文件位于/dev/log
。
1
2 [root@cvitek]/dev# ls -lh log
srw-rw-rw- 1 root root 0 Jan 1 08:00 log
1 | void openlog(const char *ident, int option, int facility); |
ident
:指向的字符串被添加到每条日志信息的前面,一般指定为程序名。如果ident
为NULL
,则使用程序名称。因为
syslog
服务端(一般为syslogd
)会接收到很多客户端的消息,所以日志前面一定要带有程序名或其他标识字符串,这样才能确定日志消息的来源。option
:指定这个连接的一些特性。就像
open
打开文件时可以指定一些flags
,用来控制读写权限、非阻塞等等。不过这只是个比喻,与打开文件毫无关系。facility
:用于指定记录消息的程序类型,或者说消息来源。这是下面调用syslog
时的一个默认值,不过实际调用时仍然可以改为其他类型。与
ident
细分来源哪个程序不同,facility
是按大类划分,比如来自内核的消息,或者来自用户态的消息。通过这样划分,可以为不同的大类设置不同的输出级别(通过配置文件设置)。比如内核的日志只记录Info
级别,而用户态的日志则设置为Debug
。
openlog()
的使用是可选的,如果没有调用,它将在第一次调用 syslog()
时自动调用,在这种情况下,ident
将默认为 NULL
。
option 类型
LOG_CONS
:如果发送到system logger
时出现错误,则直接写入系统控制台console
。LOG_NDELAY
:立即打开连接(通常,在记录第一条消息时打开连接)。这可能是有用的,例如,如果后续的chroot(2)
将使日志记录工具内部使用的路径名不可达。LOG_NOWAIT
:不要等待在记录消息时可能已经创建的子进程。(GNU C 库不创建子进程,所以这个选项对 Linux 没有影响。)LOG_ODELAY
:LOG_NDELAY
的逆函数;连接的打开被延迟,直到syslog()
被调用。(这是默认值,不需要指定。)LOG_PERROR
:将消息记录到stderr
。(POSIX.1-2001 或 POSIX.1-2008 中没有。)LOG_PID
:在每条消息中包含呼叫者的 PID。
facility 类型
LOG_AUTH
: security/authorization messagesLOG_AUTHPRIV
: security/authorization messages (private)LOG_CRON
: clock daemon (cron and at)LOG_DAEMON
: system daemons without separate facility valueLOG_FTP
: ftp daemonLOG_KERN
: kernel messages (these can’t be generated from user processes)LOG_LOCAL0 through LOG_LOCAL7
: reserved for local use。这个可以用户自定义。LOG_LPR
: line printer subsystemLOG_MAIL
: mail subsystemLOG_NEWS
: USENET news subsystemLOG_SYSLOG
: messages generated internally by syslogd(8)LOG_USER (default)
: generic user-level messagesLOG_UUCP
: UUCP subsystem
level 日志级别
LOG_EMERG
: system is unusableLOG_ALERT
: action must be taken immediatelyLOG_CRIT
: critical conditionsLOG_ERR
: error conditionsLOG_WARNING
: warning conditionsLOG_NOTICE
: normal, but significant, conditionLOG_INFO
: informational messageLOG_DEBUG
: debug-level message
syslog
1 | void syslog(int priority, const char *format, ...); |
priority
参数由一个facility
值和一个level
值一起组成。如果优先级中没有用到
facility
,则使用openlog()
设置的默认值,或者,如果之前没有openlog()
调用,则使用默认值LOG_USER
。format
:一个格式字符串,后面跟该格式所需的任何参数,除了%m
将被错误消息字符串strerror(errno)
取代。格式字符串不需要包含结束换行符。
比如输出一条 DEBUG
级别的日志如下:
1 | syslog(LOG_USER|LOG_DEBUG, "hello world\n"); |
配置文件
syslog
配置文件用于控制 syslogd
守护进程如何处理日志消息。该文件通常位于 /etc/syslog.conf
中。
syslog
配置文件由一行一行的配置项组成。每一行包含一个或多个关键字组成 + action
组成。关键字之间使用分号;
分割,关键字和 action
之间使用空格
分割。
action:指定消息的处理方式。常见的 action
包括:
file
:将消息写入文件。remote
:将消息发送到远程主机。console
:将消息显示在控制台。null
:丢弃消息。
配置文件示例
1 | # 将所有 info 级别和内核 debug 级别的消息写入 `/var/log/messages` 文件。 |
配置文件注意事项
syslog
配置文件是全局性的,对所有系统上的应用程序都有效。- 可以使用
-f
选项指定syslogd
守护进程使用的配置文件。
syslogd
syslogd
守护进程参数介绍:
1 | -n Run in foreground |
Output File ⬇️(必须要加引号 "-"
才能到标准输出)
1 | $ syslogd -n -l 8 -O "-" |
Smaller Output 演示结果:
1 | Sep 2 23:50:01 panda user.debug syslog-test[2438]: hello world |
封装
使用 syslog
时需要指定 facility|level
比较麻烦,在 middleware/v2/include/cvi_debug.h
对 syslog
通过宏函数进行了一层封装☠️,在日志消息的基础上添加了“文件名、行号、函数名”,本意是好的,但是名字太长了!TRACE
和 DBG
毫无意义!
1 | CVI_TRACE_LOG(CVI_DBG_INFO, "LT9611_MIPI_Input_Digtal: lt9611 set mipi port = 2\n"); |
相比之下,openvswitch
的命名更加简洁:
1 | VLOG_DBG("xxx"); |
参照此方式,加个 CVI_
前缀也比较简洁啊!CVI_VLOG_ERR("XXX");
相关资料
- rfc 5424 – The Syslog Protocol
- musl-syslog.h
- musl-syslog.c
- [Linux 系统编程-进程通信](./Linux 系统编程-进程通信.md)
middleware/v2/include/cvi_debug.h
简单宏日志
1 |
|
为什么下面的这个
"\e[0;31m"
能决定输出的颜色呢?
这段代码是使用 ANSI 转义码来控制终端文本的颜色和样式。在 ANSI 转义码中,\e
用于表示ASCII码中的Escape字符(十进制为27),它在终端中告诉终端模拟器去执行一些特定的操作,比如改变颜色或者样式。在 ANSI 转义码中,数字部分对应不同的颜色和样式。例如,0;31
表示红色文字(0
是样式代码,31
是颜色代码)。同样地,1;33
表示亮黄色文字,0;34
表示蓝色文字。样式代码和颜色代码分别是啥?
ChatGPT
在 ANSI 转义码中,用于控制文本样式和颜色的代码分别是样式码和颜色码。
样式码:
0
:重置所有文本属性,恢复默认状态。1
:粗体或者高亮(取决于终端的实现)。2
:暗色(并非所有终端都支持)。3
:斜体(并非所有终端都支持)。4
:下划线。5
:闪烁(并非所有终端都支持)。7
:反转显示,即前景色变背景色,背景色变前景色。8
:隐藏(不可见)。颜色码(前景色):
30
:黑色31
:红色32
:绿色33
:黄色34
:蓝色35
:洋红色(品红)36
:青色37
:白色这些代码可以通过组合使用来产生不同的效果。例如,
"\e[1;33m"
表示亮黄色文本(1
代表高亮,33
代表黄色);"\e[0;31m"
表示普通红色文本(0
代表默认样式,31
代表红色)。请注意,并非所有终端都支持所有的样式码和颜色码,所以在不同的终端中可能会有不同的效果。
或者更简单一点,不区分日志级别。可以用在简单库的 .c
文件中用于输出错误,警告等日志。
1 | /* LOG MACRO */ |
注意事项
printf
本身是一个不可重入函数,不能在中断、信号处理函数中使用。所以,这里的 mlog
同样不能在这些地方使用。
因为printf函数内部调用了malloc函数,而malloc之前会利用静态mutex对其进行lock。设想这样一种情况,某线程在调用printf(内部执行至对静态mutex加锁操作)时中断,而在信号处理函数中同样调用了printf,则会产生死锁。