文章出处:https://blog.csdn.net/shift_wwx/article/details/89105854
请转载的朋友标明出处,请支持原创~~
系列博文:
syslogd 详解二:源码分析syslogd config
0. 前言
syslogd 是busybox 中用来收集系统日志(不同的类型,如kernel,user层)的一个守护进程,而busybox 大家都比较熟悉,为嵌入式 开发提供了一个相对完整的运行环境。
一般syslogd 在编译后生成于/bin/下 或/sbin 下。
不同的进程(client)都可以将log 输送给syslogd(server),由syslogd 集中收集。client 里面特殊的接口、特殊格式进行log 输送,下面会详细说明。
1. syslogd 架构
- App通过syslog的接口进行log的打印,该接口define在syslog.h中;
- syslog会通过socket发送消息,将log发送给syslogd;
- syslogd在获取到log后,会进行log的处理;
- syslogd可以将log保存到本地,也可以发送到共享内存或远程服务器
2. openlog()
在syslog接口使用之前,有必要通过openlog进行一些log信息的初始化工作,openlog定义在系统头文件syslog.h中,函数原型:
void openlog (const char *__ident, int __option, int __facility)
- 第一个参数为log tag,同Android中LOG_TAG;
- 第二个参数为log flags,定义在syslog.h中;
如下:
#define LOG_PID 0x01 /* log the pid with each message */
#define LOG_CONS 0x02 /* log on the console if errors in sending */
#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 */
例如:
使用LOG_PID,会在每一条log中添加pid信息;
使用LOG_CONS,在出现error信息的时候,会将log输出到congsole上;
使用LOG_NDELAY,在最开始openlog 的时候并不真正创建socket通信,而是等到syslog调用的触发;
- 第三个参数为syslog 的facility,syslog.h中一共有23种选择;
/* 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)
例如:
使用LOG_KERN,表示log为kernel信息;
使用LOB_USER,表示log为普通用户信息,一般package使用的信息可以设为LOG_USER;
使用LOG_DAEMON,表示log为系统守护进程信息;
下面来看下openlog() 的源码:
static void
openlog_internal(const char *ident, int logstat, int logfac)
{
if (ident != NULL)
LogTag = ident;
LogStat = logstat;
if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
LogFacility = logfac;
int retry = 0;
while (retry < 2) {
if (LogFile == -1) {
SyslogAddr.sun_family = AF_UNIX;
(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
sizeof(SyslogAddr.sun_path));
if (LogStat & LOG_NDELAY) {
LogFile = __socket(AF_UNIX, LogType | SOCK_CLOEXEC, 0);
if (LogFile == -1)
return;
}
}
if (LogFile != -1 && !connected)
{
int old_errno = errno;
if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
== -1)
{
int saved_errno = errno;
int fd = LogFile;
LogFile = -1;
(void)__close(fd);
__set_errno (old_errno);
if (saved_errno == EPROTOTYPE)
{
/* retry with the other type: */
LogType = (LogType == SOCK_DGRAM
? SOCK_STREAM : SOCK_DGRAM);
++retry;
continue;
}
} else
connected = 1;
}
break;
}
}
主要是建立socket通信,通信设备是/dev/log,注意,如果openlog() 的第二个参数没有设定LOG_NDELAY,这里不会建立socket,一直等到syslog()接口调用后才能触发。
其他注意:
- LogStat 通过openlog 传入,用于控制log 状态,例如LOG_NDELAY、LOG_PID等;
- LogFacility 默认为LOG_USER,openlog可以传入,也可以在syslog中传入,如果都不传使用默认LOG_USER;
3. syslog()
对于syslogd来说,log是通过syslog传入的,函数的原型:
void syslog (int __pri, const char *__fmt, ...)
应用可以通过openlog进行初始化,例如log的tag信息或者是pid信息。如果不进行openlog而直接使用syslog也是可以的,在syslog中会进行openlog的操作。当然,不进行openlog操作,log中有些信息就无法显示,例如pid信息。
下面来看下syslog中的参数:
- 第一个参数为log的优先级和facility组合,优先级与Android中的LOG_DEBUG或LOG_INFO等类似;
#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 */
这里的priority定义与Android略有不同,例如LOG_CRIT表示critical的信息。
facility 在openlog的时候介绍过。
- 第二个参数为log的具体信息,同printf;
下面来看下源码:
__vsyslog_chk(int pri, int flag, const char *fmt, va_list ap)
{
...
...
/* Get connected, output the message to the local logger. */
if (!connected)
openlog_internal(LogTag, LogStat | LOG_NDELAY, 0);
/* If we have a SOCK_STREAM connection, also send ASCII NUL as
a record terminator. */
if (LogType == SOCK_STREAM)
++bufsize;
if (!connected || __send(LogFile, buf, bufsize, send_flags) < 0)
{
if (connected)
{
/* Try to reopen the syslog connection. Maybe it went
down. */
closelog_internal ();
openlog_internal(LogTag, LogStat | LOG_NDELAY, 0);
}
if (!connected || __send(LogFile, buf, bufsize, send_flags) < 0)
{
closelog_internal (); /* attempt re-open next time */
/*
* Output the message to the console; don't worry
* about blocking, if console blocks everything will.
* Make sure the error reported is the one from the
* syslogd failure.
*/
if (LogStat & LOG_CONS &&
(fd = __open(_PATH_CONSOLE, O_WRONLY|O_NOCTTY, 0)) >= 0)
{
__dprintf (fd, "%s\r\n", buf + msgoff);
(void)__close(fd);
}
}
}
...
...
}
注意:
- log 的优先级必须设置;
- 如果syslog中的第一个参数包含了facility,则之前openlog中配置的会被替代,即使用syslog中的facility;相反,如果没有包含则使用openlog中配置的facility或者默认的LOG_USER;
- priority 和facility 将会以下面的形式传递给syslogd,在syslogd中进行相应的解析;
fprintf (f, "<%d>", pri);
- 通过socket将带有log的信息发送出去。如果在之前没有openlog(),这里会进行openlog,但是参数就不一样了。注意的是,如果openlog()中没有设定LOG_NDELAY,openlog是不会建立socket,需要等到这里进行创建。
- 通过syslog函数传递给syslogd的msg 格式应该是:
<priority|facility>LOGTAG[PID]: msg
4. closelog()
在使用syslog结束之后通过closelog进行扫尾工作。函数原型:
void closelog (void)
主要就是将socket 关闭。
5. syslog.conf
syslogd机制中可以通过 syslog.conf 对log进行管理配置。默认存放在etc目录下。当然,也可以通过initrc进行动态设置。
设置syslog.conf必须按照形式:
facility.priority action
例如:
user.debug /var/log/log.debug
将openlog第二个参数为LOG_USER,syslog第一个参数为LOG_DEBUG的所有信息存放到/var/log/log.debug文件中。
- priority有:
alert、crit、debug、emerg、err、error、info、none、notice、panic、warn、warning
- facility有:
auth、authpriv、cron、daemon、ftp、kern、mail、lpr、mark、news、security、syslog、user、uucp、local0~ local7
举例:
#mail相关的info级别的log记录到mail.log中
mail.info /var/log/mail.log
#表示将auth相关的,级别为info的log记录到10.1.1.1,前提是10.1.1.1要能接收其发来的日志信息
auth.=info @10.1.1.1
#user相关的所有log,但不包括error级别的log
user.!=error /var/log/user.log
#记录所有info级别的log
*.info /var/log/log.info
#如果是多个来源,中间可以用分号隔开
cron.info;mail.info /var/log/mail.log
#相当于cron.info;mail.info
cron,mail.info
#mail相关所有级别的log,但不包括info级别的信息
mail.*;mail.!=info
6. 读取log
syslogd中提供了很多种获取log 的方式,其中一种就是共享内存。当syslogd获取到log信息的时候,会通过log_to_shmem()将buf传入共享内存,用于其他的进程读取:
if (new_tail < G.shbuf->size) {
/* store message, set new tail */
memcpy(G.shbuf->data + old_tail, msg, len);
G.shbuf->tail = new_tail;
} else {
/* k == available buffer space ahead of old tail */
int k = G.shbuf->size - old_tail;
/* copy what fits to the end of buffer, and repeat */
memcpy(G.shbuf->data + old_tail, msg, k);
msg += k;
len -= k;
G.shbuf->tail = 0;
goto again;
}
在另外的进程中通过shmat进行获取:
/* Attach shared memory to our char* */
shbuf = shmat(log_shmid, NULL, SHM_RDONLY);
if (shbuf == NULL)
bb_perror_msg_and_die("can't %s syslogd buffer", "access");
将拿到的shbuf进行处理,例如输出到stdout中。
需要注意的是:syslogd中要求local和ipc是互斥的,所以想要log既能保存到本地log文件,又能通过共享内存方式读取,需要更改代码。
另外,需要使用ipc方式,在运行syslogd的时候需要加上参数 -C,这里需要特别注意。
7. logread.c
在busybox中提供了通过共享内存方式进行读取log的程序logread.c,可以将syslogd共享内存中的log输出到stdout中。
将CONFIG_LOGREAD打开就可以编译出来,默认放置在sbin目录下,运行命令:
./sbin/logread -f
来看下源码:
int logread_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int logread_main(int argc UNUSED_PARAM, char **argv)
{
unsigned cur;
int log_semid; /* ipc semaphore id */
int log_shmid; /* ipc shared memory id */
int follow = getopt32(argv, "fF");
INIT_G();
log_shmid = shmget(KEY_ID, 0, 0);
if (log_shmid == -1)
bb_perror_msg_and_die("can't %s syslogd buffer", "find");
/* Attach shared memory to our char* */
shbuf = shmat(log_shmid, NULL, SHM_RDONLY);
if (shbuf == NULL)
bb_perror_msg_and_die("can't %s syslogd buffer", "access");
log_semid = semget(KEY_ID, 0, 0);
if (log_semid == -1)
error_exit("can't get access to semaphores for syslogd buffer");
bb_signals(BB_FATAL_SIGS, interrupted);
/* Suppose atomic memory read */
/* Max possible value for tail is shbuf->size - 1 */
cur = shbuf->tail;
/* Loop for -f or -F, one pass otherwise */
do {
unsigned shbuf_size;
unsigned shbuf_tail;
const char *shbuf_data;
printf("ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING: %d\n",ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING);
#if 0
int i;
int len_first_part;
int len_total = len_total; /* for gcc */
char *copy = copy; /* for gcc */
#endif
if (semop(log_semid, SMrdn, 2) == -1)
error_exit("semop[SMrdn]");
/* Copy the info, helps gcc to realize that it doesn't change */
shbuf_size = shbuf->size;
shbuf_tail = shbuf->tail;
shbuf_data = shbuf->data; /* pointer! */
if (DEBUG)
printf("cur:%u tail:%u size:%u\n",
cur, shbuf_tail, shbuf_size);
if (!(follow & 1)) { /* not -f */
/* if -F, "convert" it to -f, so that we don't
* dump the entire buffer on each iteration
*/
follow >>= 1;
/* advance to oldest complete message */
/* find NUL */
cur += strlen(shbuf_data + cur);
if (cur >= shbuf_size) { /* last byte in buffer? */
cur = strnlen(shbuf_data, shbuf_tail);
if (cur == shbuf_tail)
goto unlock; /* no complete messages */
}
/* advance to first byte of the message */
cur++;
if (cur >= shbuf_size) /* last byte in buffer? */
cur = 0;
} else { /* -f */
if (cur == shbuf_tail) {
sem_up(log_semid);
fflush_all();
sleep(1); /* TODO: replace me with a sleep_on */
continue;
}
}
/* Read from cur to tail */
#if 0
len_first_part = len_total = shbuf_tail - cur;
if (len_total < 0) {
/* message wraps: */
/* [SECOND PART.........FIRST PART] */
/* ^data ^tail ^cur ^size */
len_total += shbuf_size;
}
copy = xmalloc(len_total + 1);
if (len_first_part < 0) {
/* message wraps (see above) */
len_first_part = shbuf_size - cur;
memcpy(copy + len_first_part, shbuf_data, shbuf_tail);
}
memcpy(copy, shbuf_data + cur, len_first_part);
copy[len_total] = '\0';
cur = shbuf_tail;
#else
while (cur != shbuf_tail) {
fputs(shbuf_data + cur, stdout);
cur += strlen(shbuf_data + cur) + 1;
if (cur >= shbuf_size)
cur = 0;
}
#endif
unlock:
/* release the lock on the log chain */
sem_up(log_semid);
#if 0
for (i = 0; i < len_total; i += strlen(copy + i) + 1) {
fputs(copy + i, stdout);
}
free(copy);
#endif
fflush_all();
} while (follow);
/* shmdt(shbuf); - on Linux, shmdt is not mandatory on exit */
fflush_stdout_and_exit(EXIT_SUCCESS);
}
8. 重启syslogd
有两种方式,将syslogd kill掉,然后重新运行。
另一种方式,在/etc/init.d/目录下有个相关的可执行程序,例如名称为S01logging。运行:
./etc/init.d/S01logging restart
or
./etc/init.d/S01logging reload
更多详细的,以source code 为准!!