APUE读书笔记-13守护进程(3)
4、登陆错误
守护进程的一个问题就是如何处理错误消息。不能将消息简单地写入到标准错误输出中,因为守护进程并没有控制终端。我们也不想让守护进程往console设备上面写,因为在许多工作站上面,console设备运行在窗口系统中。我们也不想要每个守护进程将信息写入到一个特定的文件中,因为这样管理员需要知道哪些守护进程向哪些文件写,需要基于某个规则来检查这些文件,这是很麻烦的。所以,需要一个集中管理错误日志登记的守护进程工具。
BSD的syslog工具在4.2BSD中应用很多,大多数继承自BSD的系统都支持syslog。
直到SVR4,System V从来没有一个用来集中登记日志的守护进程工具。
syslog函数作为Single UNIX Specification的XSI扩展被包含。
由于BSD的syslog从BSD4.2之后工具应用很广,大多数的守护进程都使用这个工具。
BSD的syslog工具
+---------+ +---------+
| user | | syslogd |
| process | +-^--^--^-+
+---------+ | | |
| -----+ | +-------
syslog / | \
| / | \
+- - -|- - - -/- - - - - + - - - - - -\- - - - - - - - - +
| v / | \ |
+----------+ +---------+ +-----------+
| | /dev/log | | UDP | | /dev/klog | |
+----------+ | port514 | +-----^-----+
| UNIX domain +---------+ | |
datagram socket Internet domain log|
| datagram socket | |
^ +-----------+
| | | kernel | |
| | routines |
|kernel | +-----------+ |
+ - - - - - - - - - - - -|- - - - - - - - - - - - - - - -+
|
TCP/IP network
对于上图:
- syslogd守护进程读取/dev/log,UDP的514号端口,或/dev/klog,并把日志写入到文件或者发送到别的主机上面。
- 对于上面三个文件,用户进程通过syslog写/dev/log;来自TCP/IP的网络写UDP的514号端口;内核进程通过log写/dev/klog。
有三种方法生成日志消息:
- 内核例程可以调用log函数。这些消息可以通过打开/dev/klog文件被其他用户进程读取。我们这里不详细讨论这个函数了,因为我们不打算讨论编写内核例程。
- 大多数用户进程(守护进程)通过调用syslog函数生成日志消息。我们后面描述它。这会导致消息被发送到UNIX套接字/dev/log上面。
- 一个本地用户进程,或者通过TCP/IP网络连接到本地机器上面的其它主机,可以把日志消息发送到UDP的514端口上面。需要注意的是,syslog函数不会生成这些UDP数据报,他们需要进程通过网络编程的方式生成日志消息。
一般来说,syslogd守护进程读取所有三种类型的日志消息。在启动的时候,这个守护进程读取一个配置文件(一般为/etc/syslog.conf),这个配置文件决定了不同种类的消息被发送到哪里。例如,比较紧急的消息可以给发送到系统管理员,并且打印到console屏幕上面,而一些警告类型的消息可以被登记到一个特定的文件当中。
我们把syslog函数做为使用这个工具的接口。
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);
返回:前次的日志优先级mask值。
openlog函数的调用是可选的,如果第一次使用syslog的时候没有调用openlog,那么会自动调用openlog函数。closelog也是可选的,它只是关闭用户来和syslogd守护进程通信的文件描述符号。
openlog函数允许我们向每一条日志消息添加一个ident,一般它就是程序的名称(例如cron,inetd,等等)。option参数是一个位码(bitmask)的组合,可以用来指定各种选项。(注意对Single UNIX Specification 的XSI扩展支持)详细请见参考网址。大致如下:
- LOG_CONS :如果日志消息没有通过UNIX域的数据报发送给syslogd,那么会被写到console上面。
- LOG_NDELAY :立即打开到syslogd守护进程的UNIX 域数据报套接字,而不是等到第一条日志消息的登记。一般来说,这个套接字知道登记第一条日志的时候才会被打开。
- LOG_NOWAIT :不会等待那些可能会在登记日志过程中创建的子进程。这样会防止与捕获SIGCHLD的进程产生冲突,因为程序可能会在syslog调用wait的时候已经获取了子进程的状态(???)。
- LOG_ODELAY :延迟打开到syslogd守护进程的连接,直到登记第一条日志消息。
- LOG_PERROR :除了将日志消息发送给syslogd之外,也将其写到标准错误输出。(Solaris上不可用)
- LOG_PID :为每一条消息登记进程ID。这个用于调用fork创建子进程的守护进程来处理不同的请求(相对于一些守护进程,例如syslogd守护进程从来不会使用fork创建子进程)。
openlog函数的facility参数参见参考网址给出的表格,注意Single UNIX Specification 只给出了在一些平台上可用的常用的facility码的子集。facility参数存在的意义就是允许使用配置文件指出,来自不同facility的消息使用不同的方式处理。如果我们不使用openlog或者我们使用facility值为0来调用这个函数,我们仍然可以指定facility做为syslog函数priority参数的一个部分。 facility参数的值(例如LOG_FTP,LOG_CRON,LOG_DAEMON等)如下表所列:
openlog的参数值
+----------------------------------------------------------------------------------------+
| facility | XSI | Description |
|--------------+-----+-------------------------------------------------------------------|
| LOG_AUTH | | authorization programs: login, su, getty, ... |
|--------------+-----+-------------------------------------------------------------------|
| LOG_AUTHPRIV | | same as LOG_AUTH, but logged to file with restricted permissions |
|--------------+-----+-------------------------------------------------------------------|
| LOG_CRON | | cron and at |
|--------------+-----+-------------------------------------------------------------------|
| LOG_DAEMON | | system daemons: inetd, routed, ... |
|--------------+-----+-------------------------------------------------------------------|
| LOG_FTP | | the FTP daemon (ftpd) |
|--------------+-----+-------------------------------------------------------------------|
| LOG_KERN | | messages generated by the kernel |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL0 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL1 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL2 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL3 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL4 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL5 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL6 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LOCAL7 | • | reserved for local use |
|--------------+-----+-------------------------------------------------------------------|
| LOG_LPR | | line printer system: lpd, lpc, ... |
|--------------+-----+-------------------------------------------------------------------|
| LOG_MAIL | | the mail system |
|--------------+-----+-------------------------------------------------------------------|
| LOG_NEWS | | the Usenet network news system |
|--------------+-----+-------------------------------------------------------------------|
| LOG_SYSLOG | | the syslogd daemon itself |
|--------------+-----+-------------------------------------------------------------------|
| LOG_USER | • | messages from other user processes (default) |
|--------------+-----+-------------------------------------------------------------------|
| LOG_UUCP | | the UUCP system |
+----------------------------------------------------------------------------------------+
syslog函数用来产生日志消息,其中priority参数可以是openlog函数的facility参数和一个表示优先级别(level)的值的组合(按位或)。级别的值按照由高到低的顺序排列如下:
- LOG_EMERG :紧急消息(例如系统无法使用),优先级最高。
- LOG_ALERT :应该立刻被修正的情况。
- LOG_CRIT :关键情况(例如硬件设备故障)。
- LOG_ERR :错误情况。
- LOG_WARNING :警告情况。
- LOG_NOTICE :正常的但是也比较重要的情况。
- LOG_INFO :一些信息。
- LOG_DEBUG :调试信息(优先级最低)。
format参数以及剩下的参数被传送给了vsprintf函数,如果在format中遇到了'%m'那么它会被替换成相应于errno的错误消息的字符串。
setlogmask函数可以为进程用来设置日志的优先级屏蔽码(priority mask)。这个函数返回之前的屏蔽码。当日志优先级屏蔽码被设置的时候,消息不会被登记除非它们的优先级别被设置到优先级屏蔽码中。需要注意的是,如果设置日志优先级屏蔽码为0将会没有任何作用。
许多系统也提供logger程序,这个程序可以给syslog工具发送日志消息。尽管Single UNIX Specification没有定义任何选项,有些系统实现允许为这个程序指定一些选项参数,例如facility,level,和ident.logger命令可以用于没有交互但是需要产生日志消息的shell脚本。
举例:在一个打印机守护进程中,你可能会遇到这样的行:
openlog("lpd", LOG_PID, LOG_LPR);
syslog(LOG_ERR, "open error for %s: %m", filename);
第一个调用设置ident参数为程序的名称(lpd),用LOG_PID指定始终打印进程ID,设置默认的facility参数为行打印系统(LOG_LPR)。调用syslog函数指定为错误条件(LOG_ERR),并且指定了消息字符串。如果我们没有调用openlog那么我们可以用如下方式调用第二条语句:
syslog(LOG_ERR | LOG_LPR, "open error for %s: %m", filename);
这里我们将priority 参数指定为level和facility的组合。
除了syslog之外,有一些系统也提供了一个变体的函数,这个函数可以处理可变参数列表:
#include <syslog.h>
#include <stdarg.h>
void vsyslog(int priority, const char *format, va_list arg);
本书中的所有四个平台都支持这个vsyslog函数,但是这个函数并没有被包含在Single UNIX Specification标准中。
大多数的syslogd实现都会对消息进行一个短期的排队。如果在这个期间有重复的消息到达,那么syslog守护进程会不登记这个消息,而是打印出一条消息,内容类似“上次这个消息已经重复了N次”。