关键词:rcS、start-stop-daemon、syslogd、syslog()、klogd、klogctl()、syslog.conf、/dev/log、facility/level等等。
syslog用来记录应用程序或者硬件设备的日志;通过syslogd这个进程记录系统有关事件记录,也可以记录应用程序运作事件。
syslogd日志记录器由两个守护进程(klogd,syslogd)和一个配置文件(syslog.conf)组成。
syslogd和klogd是很有意思的守护进程,syslogd是一个分发器,它将接收到的所有日志按照/etc/syslog.conf的配置策略发送到这些日志应该去的地方,当然也包括从klogd接收到的日志。
klogd首先接收内核的日志,然后将之发送给syslogd。
klogd不使用配置文件,它负责截获内核消息,它既可以独立使用也可以作为syslogd的客户端运行。
syslogd默认使用/etc/syslog.conf作为配置文件,负责截获应用程序消息,还可以截获klogd向其转发的内核消息。支持internet/unix domain sockets的特性使得这两个工具可以用于记录本地和远程的日志。
注:引用自《Linux/UNIX系统编程》第37.5.1 概述。
1.Busybox中的syslog
在busybox中执行make menuconfig,或者在buildroot中执行make busybox-menuconfig都可以对busybox进行配置。
打开menuconfig之后,进入System Logging Utilities:
syslog功能主要包括两大部分syslogd/klogd和配置文件syslog.conf。前者是daemon程序,后者是针对daemon的配置文件。
2.syslogd配置syslog.conf
syslog.conf包括对syslogd的配置,每个配置包括两个域:selector和action。
2.1 syslog.conf的selector
selector有两部分组成:facility和level,两者之间用"."分隔;多个facility或者level之间用","分隔;多个selector之间用";"分隔。
同时selector中可以使用通配符:
Name | Facility | Level |
err/warn/... | 任何一个facility | 显示大于等于此级别的log |
* | 任何一个facility | 任何一个级别 |
= | 无效 | 只有该级别log才生效 |
! | 无效 | 除了该级别,其他log生效 |
None | 无效 | 不存储该facility任何log |
2.2 syslog.conf的action
action主要是当信息满足selector时,该日志消息的输出方向。
一般分为三种:(1)存储到普通文件中;(2)写入到管道中;(3)远程转发到其它主机上。
常规文件 |
管道文件 |
远程转发 |
普通的文件名:/xx/bb |
|文件名 |
@hostname |
2.3 Facility和Level
提到syslog.con中的facility和level,需要看一下他在代码中对应关系。
相关facility和level都在syslog.h中定义,两这个共同组成一个32位数值;低3bit(0-3)表示level,其余部分(3-24)表示facility。
facilitynames[]中的名称对应syslog.conf中的facility,后面的值对应syslog()中的facility代码。
/* facility codes */ #define LOG_KERN (0<<3) /* kernel messages */ #define LOG_USER (1<<3) /* random user-level messages */ #define LOG_MAIL (2<<3) /* mail system */ #define LOG_DAEMON (3<<3) /* system daemons */ #define LOG_AUTH (4<<3) /* security/authorization messages */ #define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */ #define LOG_LPR (6<<3) /* line printer subsystem */ #define LOG_NEWS (7<<3) /* network news subsystem */ #define LOG_UUCP (8<<3) /* UUCP subsystem */ #define LOG_CRON (9<<3) /* clock daemon */ #define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */ #define LOG_FTP (11<<3) /* ftp daemon */ /* other codes through 15 reserved for system use */ #define LOG_LOCAL0 (16<<3) /* reserved for local use */ #define LOG_LOCAL1 (17<<3) /* reserved for local use */ #define LOG_LOCAL2 (18<<3) /* reserved for local use */ #define LOG_LOCAL3 (19<<3) /* reserved for local use */ #define LOG_LOCAL4 (20<<3) /* reserved for local use */ #define LOG_LOCAL5 (21<<3) /* reserved for local use */ #define LOG_LOCAL6 (22<<3) /* reserved for local use */ #define LOG_LOCAL7 (23<<3) /* reserved for local use */ #define LOG_NFACILITIES 24 /* current number of facilities */ #define LOG_FACMASK 0x03f8 /* mask to extract facility part */ /* facility of pri */ #define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) #ifdef SYSLOG_NAMES CODE facilitynames[] = { { "auth", LOG_AUTH }, { "authpriv", LOG_AUTHPRIV }, { "cron", LOG_CRON }, { "daemon", LOG_DAEMON }, { "ftp", LOG_FTP }, { "kern", LOG_KERN }, { "lpr", LOG_LPR }, { "mail", LOG_MAIL }, { "mark", INTERNAL_MARK }, /* INTERNAL */ { "news", LOG_NEWS }, { "security", LOG_AUTH }, /* DEPRECATED */ { "syslog", LOG_SYSLOG }, { "user", LOG_USER }, { "uucp", LOG_UUCP }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, { NULL, -1 } }; #endif
prioritynames定义了syslog.conf中等级和syslog()关系:
#define LOG_EMERG 0 /* system is unusable */ #define LOG_ALERT 1 /* action must be taken immediately */ #define LOG_CRIT 2 /* critical conditions */ #define LOG_ERR 3 /* error conditions */ #define LOG_WARNING 4 /* warning conditions */ #define LOG_NOTICE 5 /* normal but significant condition */ #define LOG_INFO 6 /* informational */ #define LOG_DEBUG 7 /* debug-level messages */ CODE prioritynames[] = { { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, { "debug", LOG_DEBUG }, { "emerg", LOG_EMERG }, { "err", LOG_ERR }, { "error", LOG_ERR }, /* DEPRECATED */ { "info", LOG_INFO }, { "none", INTERNAL_NOPRI }, /* INTERNAL */ { "notice", LOG_NOTICE }, { "panic", LOG_EMERG }, /* DEPRECATED */ { "warn", LOG_WARNING }, /* DEPRECATED */ { "warning", LOG_WARNING }, { NULL, -1 } };
2.4 syslog.conf实例
#syslog.conf
kern,user.* /var/log/messages #all messages of kern and user facilities
kern.!err /var/log/critical #all messages of kern facility with priorities lower than err (warn, notice ...) *.*;auth,authpriv.none /var/log/noauth #all messages except ones with auth and authpriv facilities
kern,user.*;kern.!=notice;*.err;syslog.none /var/log/OMG #some whicked rule just as an example =) *.* /dev/null #this prevents from logging to default log file (-O FILE or /var/log/messages)
3.syslogd相关API
syslogd相关API主要有如下几个,其中openlog()调用是可选的,它建立一个系统日志工具的连接,并未后续的syslog调用设置默认设置。
ident参数是一个指向字符串的指针,syslog()输出的每条消息都会包含这个字符串。其他facility和level都在facilitynames[]和prioritynames[]中有定义。
syslog()用于写入一条日志消息,priority是facility和level的OR值。
#include <syslog.h> void openlog(const char *ident, int log_options, int facility); void syslog(int priority, const char *format, ...); int setlogmask(int mask_priority); void closelog(void);
下面重点看看openlog()的log_options选项:
#define LOG_PID 0x01 /* log the pid with each message */------------------------log中包含进程pid。 #define LOG_CONS 0x02 /* log on the console if errors in sending */-----------------如果syslogd无法正确输出,输出到控制台作为替代。 #define LOG_ODELAY 0x04 /* delay open until first syslog() (default) */ #define LOG_NDELAY 0x08 /* don't delay open */ #define LOG_NOWAIT 0x10 /* don't wait for console forks: DEPRECATED */ #define LOG_PERROR 0x20 /* log to stderr as well */
4.syslogd使用
下面从rcS中启动syslog功能、syslog服务、syslogd/klogd、syslog()几个方面,介绍如何使用syslog功能。
4.1 syslog的启动
在rcS中配置启动syslog服务:
#To start syslog
/etc/init.d/S01logging start
S01logging中启动syslogd和klogd两个daemon:
#!/bin/sh # # Start logging # SYSLOGD_ARGS=-n---------------参照syslogd option。 KLOGD_ARGS=-n-----------------参照klogd option。 [ -r /etc/default/logging ] && . /etc/default/logging start() { printf "Starting logging: " start-stop-daemon -b -S -q -m -p /var/run/syslogd.pid --exec /sbin/syslogd -- $SYSLOGD_ARGS------------b表示background运行,-S是启动daemon,-q表示quiet,-m -p表示将当前daemon的pid写入/var/run/syslog.pid中;--exec表示daemon对应的可执行文件, SYSLOGD_ARGS表示可选参数。 start-stop-daemon -b -S -q -m -p /var/run/klogd.pid --exec /sbin/klogd -- $KLOGD_ARGS echo "OK" } stop() { printf "Stopping logging: " start-stop-daemon -K -q -p /var/run/syslogd.pid---------------------------------------------------------K表示停止daemon。 start-stop-daemon -K -q -p /var/run/klogd.pid echo "OK" } case "$1" in start) start ;; stop) stop ;; restart|reload) stop start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 esac exit $?
4.2 syslogd和klogd选项
通过syslogd -h和klogd -h可以了解不同选项作用。
BusyBox v1.27.2 (2019-03-24 12:37:18 CST) multi-call binary. Usage: syslogd [OPTIONS] System logging utility -n Run in foreground------------------------------------------------作为后台进程运行。 -R HOST[:PORT] Log to HOST:PORT (default PORT:514)------------------------------log输出到指定远程服务器。 -L Log locally and via network (default is network only if -R) -C[size_kb] Log to shared mem buffer (use logread to read it)----------------log输出到共享内存,通过logread读取。 -K Log to kernel printk buffer (use dmesg to read it)---------------log输出到内核printk缓存中,通过dmesg读取。 -O FILE Log to FILE (default: /var/log/messages, stdout if -)------------log输出到指定文件中。 -s SIZE Max size (KB) before rotation (default 200KB, 0=off)-------------在log达到一定大小后,循环log到messgas.0中,后续依次变动。 -b N N rotated logs to keep (default 1, max 99, 0=purge)--------------保持的messages个数,messages.0最新,数字越大越老。一次覆盖。 -l N Log only messages more urgent than prio N (1-8)------------------设置记录的log优先级,注意这里如果设置-l 7,是不会记录等级7,即LOG_DEBUG的,只会记录LOG_INFO及更高优先级。 -S Smaller output---------------------------------------------------是否显示hostname,以及user.info之类的详细信息。 -f FILE Use FILE as config (default:/etc/syslog.conf)--------------------指定conf文件。 BusyBox v1.27.2 (2019-03-24 12:37:18 CST) multi-call binary. Usage: klogd [-c N] [-n] Kernel logger -c N Print to console messages more urgent than prio N (1-8)------------------将错误高于N优先级的log输出到console。 -n Run in foreground--------------------------------------------------------转到后台运行。
4.3 使用openlog()/syslog()/closelog()
编写syslog,源码如下:
#include <syslog.h> #include <stdio.h> #include <stdarg.h> int main(int argc, char** argv) { int log_test; /*打开日志*/ openlog("log_test ", LOG_PID|LOG_CONS, LOG_USER); while(1) { /*写日志*/ syslog(LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_DEBUG,"%s: debug message",argv[0]); usleep(1000); } /*关闭日志*/ closelog(); }
启动syslog之后,可以看出/var/log/messages在不停增加。
Jan 1 08:10:56 log_test [176]: ./syslog: PID information, pid=176---------------------log_test是openlog()中的ident,[176]对应LOG_PID。 Jan 1 08:10:56 log_test [176]: ./syslog: debug message Jan 1 08:10:56 log_test [176]: ./syslog: PID information, pid=176 Jan 1 08:10:56 log_test [176]: ./syslog: debug message ...
如果现实完整信息:
Jan 1 08:17:15 XXXX user.info log_test [176]: ./syslog: PID information, pid=176 Jan 1 08:17:15 XXXX user.debug log_test [176]: ./syslog: debug message Jan 1 08:17:15 XXXX user.info log_test [176]: ./syslog: PID information, pid=176 ...
4.4 syslog.conf中使用facility和level过滤log
在syslog.conf中设置filter,显示local0的debug及更高优先级log、不显示local1的log、显示local2的所有log,显示local3的err及更高优先级log:
local0.info /var/log/local.log local1.none /var/log/local.log local2.* /var/log/local.log local3.err /var/log/local.log
测试程序如下:
#include <syslog.h> #include <stdio.h> #include <stdarg.h> int main(int argc, char** argv) { int log_test; /*打开日志*/ openlog("log_test ", LOG_PID|LOG_CONS, LOG_USER); /*写日志*/ syslog(LOG_LOCAL0|LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_LOCAL0|LOG_DEBUG,"%s: debug message",argv[0]); syslog(LOG_LOCAL1|LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_LOCAL1|LOG_DEBUG,"%s: debug message",argv[0]); syslog(LOG_LOCAL2|LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_LOCAL2|LOG_DEBUG,"%s: debug message",argv[0]); syslog(LOG_LOCAL3|LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_LOCAL3|LOG_DEBUG,"%s: debug message",argv[0]); syslog(LOG_LOCAL4|LOG_INFO, "%s: PID information, pid=%d", argv[0], getpid()); syslog(LOG_LOCAL4|LOG_DEBUG,"%s: debug message",argv[0]); /*关闭日志*/ closelog(); }
最终log输出/var/log/local.log:
Jan 1 08:11:54 xxxx local0.info log_test [259]: ./syslog: PID information, pid=259 Jan 1 08:11:54 xxxx local2.info log_test [259]: ./syslog: PID information, pid=259 Jan 1 08:11:54 xxxx local2.debug log_test [259]: ./syslog: debug message
5. busybox中syslogd和klogd分析
syslogd和klogd在busybox的sysklogd目录中,入口是syslogd_main()和klogd_main()。
通过系统日志概览可知:
1.内核log由klogd从内核中取出转发到/dev/log;klogctl()从kmsg的ring buffer中获取数据,通过syslog()发送到/dev/log。
2.用户空间的log直接通过syslog()发送到/dev/log。
3.syslogd从syslog.conf获取配置信息。
4.syslogd从/dev/log接收数据,或者从UDP端口514接收数据。
5.syslogd输出到磁盘文件、控制台、FIFO等。
5.1 klogd和klogctl()
klogd从通过klogctl()将内核log从ring buffer中取出,然后调用syslog()通过/dev/log发送。
这里的一个核心函数是klogctl(),通过对内核kmsg ring buffer的设置,从中取出内容。
klogctl()的type,决定了后面内容的意义。
SYSLOG_ACTION_CLOSE (0) Close the log. Currently a NOP. SYSLOG_ACTION_OPEN (1) Open the log. Currently a NOP. SYSLOG_ACTION_READ (2)-----------------------------等待内核log非空时,从中读取len大小的log到bufp中。返回值是实际读取内容大小,已经读取的部分会从内核ring buffer中移除。 SYSLOG_ACTION_READ_ALL (3) Read all messages remaining in the ring buffer, placing them in the buffer pointed to by bufp. The call reads the last len bytes from the log buffer (nondestructively), but will not read more than was written into the buffer since the last "clear ring buffer" command (see command 5 below)). The call returns the number of bytes read. SYSLOG_ACTION_READ_CLEAR (4)------------------------读取所有的内核log,然后清空。 SYSLOG_ACTION_CLEAR (5) SYSLOG_ACTION_CONSOLE_OFF (6)-----------------------通过保存console_loglevel,然后将其设置到最低,来关闭console的显示。bufp和len参数忽略。 SYSLOG_ACTION_CONSOLE_ON (7)-------------------------打开console显示。bufp和len参数忽略。 SYSLOG_ACTION_CONSOLE_LEVEL (8)----------------------设置console的等级,1-8之间。详细如下:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */ SYSLOG_ACTION_SIZE_UNREAD (9) (since Linux 2.4.10)---返回内核中可读buffer大小,bufp和len参数忽略。 SYSLOG_ACTION_SIZE_BUFFER (10) (since Linux 2.6.6)---内核中log ring buffer大小。
klogd用到的相关klogctl()包括:
static void klogd_open(void) { /* "Open the log. Currently a NOP" */ klogctl(1, NULL, 0); } static void klogd_setloglevel(int lvl) { klogctl(8, NULL, lvl); } static int klogd_read(char *bufp, int len) { return klogctl(2, bufp, len); } static void klogd_close(void) { klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */ klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */ }
klogd_main()走读如下:
int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int klogd_main(int argc UNUSED_PARAM, char **argv) { int i = 0; char *opt_c; int opt; int used; setup_common_bufsiz();----------------------------------------------------分配COMMON_BUFSIZE大小的bufer缓存,指针bb_common_bufsiz1。 opt = getopt32(argv, "c:n", &opt_c); if (opt & OPT_LEVEL) {-----------------------------------------------------获取log等级参数 /* Valid levels are between 1 and 8 */ i = xatou_range(opt_c, 1, 8); } if (!(opt & OPT_FOREGROUND)) {---------------------------------------------klogd是否后台运行。 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); } logmode = LOGMODE_SYSLOG; /* klogd_open() before openlog(), since it might use fixed fd 3, * and openlog() also may use the same fd 3 if we swap them: */ klogd_open(); openlog("kernel", 0, LOG_KERN);---------------------------------------------所有的内核log,都以kernel作为ident。 if (i) klogd_setloglevel(i);---------------------------------------------------从传入log等级参数设置到内核进行过滤。 signal(SIGHUP, SIG_IGN);----------------------------------------------------忽略SIGHUP信号。 /* We want klogd_read to not be restarted, thus _norestart: */ bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo);----------------在record_signo()中记录接收到的信号bb_got_signal。 syslog(LOG_NOTICE, "klogd started: %s", bb_banner); write_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); used = 0; while (!bb_got_signal) {----------------------------------------------------没有受到信号中断,则while循环。 int n; int priority; char *start; /* "2 -- Read from the log." */ start = log_buffer + used;-----------------------------------------------log_buffer指向bb_common_bufsiz1。 n = klogd_read(start, KLOGD_LOGBUF_SIZE-1 - used);-----------------------读取KLOGD_LOGBUF_SIZE-1 - used大小log到start。 if (n < 0) { if (errno == EINTR) continue; bb_perror_msg(READ_ERROR); break; } start[n] = '