Busybox的syslogd認識與使用


關鍵詞: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的設置,從中取出內容。

#include <sys/klog.h>

int klogctl(int type, char *bufp, int len);

 

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] = '\0';

        /* Process each newline-terminated line in the buffer */
        start = log_buffer;
        while (1) {
            char *newline = strchrnul(start, '\n');------------------------------根據\n進行分行處理,一行一個syslog(),newline指向下一行頭部。 if (*newline == '\0') {
                /* This line is incomplete */

                /* move it to the front of the buffer */
                overlapping_strcpy(log_buffer, start);
                used = newline - start;
                if (used < KLOGD_LOGBUF_SIZE-1) {
                    /* buffer isn't full */
                    break;
                }
                /* buffer is full, log it anyway */
                used = 0;
                newline = NULL;
            } else {
                *newline++ = '\0';
            }

            /* Extract the priority */
            priority = LOG_INFO;--------------------------------------------------默認等級 if (*start == '<') {
                start++;
                if (*start)
                    priority = strtoul(start, &start, 10);------------------------等級轉換。 if (*start == '>')
                    start++;
            }
            /* Log (only non-empty lines) */
            if (*start)
                syslog(priority, "%s", start);------------------------------------獲取等級和一行內容start,通過syslog打印。 if (!newline)
                break;
            start = newline;------------------------------------------------------start指向下一行首部,循環處理。
        }
    }

    klogd_close();----------------------------------------------------------------關閉klog。
    syslog(LOG_NOTICE, "klogd: exiting");
    remove_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid");
    if (bb_got_signal)
        kill_myself_with_sig(bb_got_signal);
    return EXIT_FAILURE;
}

klogctl()函數通過調用syslog系統調用,來從內核中獲取msg。

SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len)
{
    return do_syslog(type, buf, len, SYSLOG_FROM_READER);
}


int do_syslog(int type, char __user *buf, int len, int source)
{
    bool clear = false;
    static int saved_console_loglevel = LOGLEVEL_DEFAULT;
    int error;

    error = check_syslog_permissions(type, source);
    if (error)
        goto out;

    switch (type) {
    case SYSLOG_ACTION_CLOSE:    /* Close log */
        break;
    case SYSLOG_ACTION_OPEN:    /* Open log */--------------------------CLOSE和OPEN沒有任何作用。
        break;
    case SYSLOG_ACTION_READ:    /* Read from log */
        error = -EINVAL;
        if (!buf || len < 0)
            goto out;
        error = 0;
        if (!len)
            goto out;
        if (!access_ok(VERIFY_WRITE, buf, len)) {
            error = -EFAULT;
            goto out;
        }
        error = wait_event_interruptible(log_wait,
                         syslog_seq != log_next_seq);
        if (error)
            goto out;
        error = syslog_print(buf, len);
        break;
    /* Read/clear last kernel messages */
    case SYSLOG_ACTION_READ_CLEAR:
        clear = true;-------------------------------------------------可以看出這里並不做實際清空,只是SYSLOG_ACTION_READ_ALL之后才會清空。 /* FALL THRU */
    /* Read last kernel messages */
    case SYSLOG_ACTION_READ_ALL:
        error = -EINVAL;
        if (!buf || len < 0)
            goto out;
        error = 0;
        if (!len)
            goto out;
        if (!access_ok(VERIFY_WRITE, buf, len)) {
            error = -EFAULT;
            goto out;
        }
        error = syslog_print_all(buf, len, clear);--------------------將所有出書到buf中,但是有長度len限制,只保存最新部分。 break;
    /* Clear ring buffer */
    case SYSLOG_ACTION_CLEAR:
        syslog_print_all(NULL, 0, true);------------------------------僅僅做清空操作。 break;
    /* Disable logging to console */
    case SYSLOG_ACTION_CONSOLE_OFF:
        if (saved_console_loglevel == LOGLEVEL_DEFAULT)
            saved_console_loglevel = console_loglevel;
        console_loglevel = minimum_console_loglevel;
        break;
    /* Enable logging to console */
    case SYSLOG_ACTION_CONSOLE_ON:
        if (saved_console_loglevel != LOGLEVEL_DEFAULT) {
            console_loglevel = saved_console_loglevel;
            saved_console_loglevel = LOGLEVEL_DEFAULT;
        }
        break;
    /* Set level of messages printed to console */
    case SYSLOG_ACTION_CONSOLE_LEVEL:
        error = -EINVAL;
        if (len < 1 || len > 8)
            goto out;
        if (len < minimum_console_loglevel)
            len = minimum_console_loglevel;
        console_loglevel = len;----------------------------------------設置console_loglevel等級。 /* Implicitly re-enable logging to console */
        saved_console_loglevel = LOGLEVEL_DEFAULT;
        error = 0;
        break;
    /* Number of chars in the log buffer */
    case SYSLOG_ACTION_SIZE_UNREAD:
...
        break;
    /* Size of the log buffer */
    case SYSLOG_ACTION_SIZE_BUFFER:
        error = log_buf_len;
        break;
    default:
        error = -EINVAL;
        break;
    }
out:
    return error;
}


static int syslog_print(char __user *buf, int size)
{
    char *text;
    struct printk_log *msg;
    int len = 0;

    text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL);
    if (!text)
        return -ENOMEM;

    while (size > 0) {
        size_t n;
        size_t skip;

        raw_spin_lock_irq(&logbuf_lock);
        if (syslog_seq < log_first_seq) {
            /* messages are gone, move to first one */
            syslog_seq = log_first_seq;
            syslog_idx = log_first_idx;
            syslog_prev = 0;
            syslog_partial = 0;
        }
        if (syslog_seq == log_next_seq) {
            raw_spin_unlock_irq(&logbuf_lock);
            break;
        }

        skip = syslog_partial;
        msg = log_from_idx(syslog_idx);
        n = msg_print_text(msg, syslog_prev, true, text,
                   LOG_LINE_MAX + PREFIX_MAX);
        if (n - syslog_partial <= size) {
            /* message fits into buffer, move forward */
            syslog_idx = log_next(syslog_idx);
            syslog_seq++;
            syslog_prev = msg->flags;
            n -= syslog_partial;
            syslog_partial = 0;
        } else if (!len){
            /* partial read(), remember position */
            n = size;
            syslog_partial += n;
        } else
            n = 0;
        raw_spin_unlock_irq(&logbuf_lock);

        if (!n)
            break;

        if (copy_to_user(buf, text + skip, n)) {
            if (!len)
                len = -EFAULT;
            break;
        }

        len += n;
        size -= n;
        buf += n;
    }

    kfree(text);
    return len;
}

static int syslog_print_all(char __user *buf, int size, bool clear)
{
    char *text;
    int len = 0;

    text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL);
    if (!text)
        return -ENOMEM;

    raw_spin_lock_irq(&logbuf_lock);
    if (buf) {
        u64 next_seq;
        u64 seq;
        u32 idx;
        enum log_flags prev;

        /*
         * Find first record that fits, including all following records,
         * into the user-provided buffer for this dump.
         */
        seq = clear_seq;
        idx = clear_idx;
        prev = 0;
        while (seq < log_next_seq) {
            struct printk_log *msg = log_from_idx(idx);

            len += msg_print_text(msg, prev, true, NULL, 0);---------------------------獲取所有log大小。
            prev = msg->flags;
            idx = log_next(idx);
            seq++;
        }

        /* move first record forward until length fits into the buffer */
        seq = clear_seq;
        idx = clear_idx;
        prev = 0;
        while (len > size && seq < log_next_seq) {
            struct printk_log *msg = log_from_idx(idx);

            len -= msg_print_text(msg, prev, true, NULL, 0);---------------------------找到滿足剩余長度小於size大小的seq,后面將seq之后所有的log發送到text中。保證滿足buf尺寸前提下,取最新的log。
            prev = msg->flags;
            idx = log_next(idx);
            seq++;
        }

        /* last message fitting into this dump */
        next_seq = log_next_seq;

        len = 0;
        while (len >= 0 && seq < next_seq) {
            struct printk_log *msg = log_from_idx(idx);
            int textlen;

            textlen = msg_print_text(msg, prev, true, text,
                         LOG_LINE_MAX + PREFIX_MAX);----------------------------------將msg內容輸出到text中,第3參數true表示要輸出給syslog()使用,所以需要加上facility和level頭。 if (textlen < 0) {
                len = textlen;
                break;
            }
            idx = log_next(idx);
            seq++;
            prev = msg->flags;

            raw_spin_unlock_irq(&logbuf_lock);
            if (copy_to_user(buf + len, text, textlen))
                len = -EFAULT;
            else
                len += textlen;
            raw_spin_lock_irq(&logbuf_lock);

            if (seq < log_first_seq) {
                /* messages are gone, move to next one */
                seq = log_first_seq;
                idx = log_first_idx;
                prev = 0;
            }
        }
    }

    if (clear) {-------------------------------------------------------------------------清空所有buffer。
        clear_seq = log_next_seq;
        clear_idx = log_next_idx;
    }
    raw_spin_unlock_irq(&logbuf_lock);

    kfree(text);
    return len;
}

 

 

5.2 syslog()函數

在glibc庫的misc/syslog.c中,定義了openlog()/syslog()/closelog()函數。

這里重點看一下syslog()都做了哪些內容。

ldbl_hidden_def (__syslog, syslog)

void
__syslog(int pri, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    __vsyslog_chk(pri, -1, fmt, ap);
    va_end(ap);
}

void
__vsyslog_chk(int pri, int flag, const char *fmt, va_list ap)
{
    struct tm now_tm;
    time_t now;
    int fd;
    FILE *f;
    char *buf = 0;
    size_t bufsize = 0;
    size_t msgoff;
#ifndef NO_SIGPIPE
     struct sigaction action, oldaction;
     int sigpipe;
#endif
    int saved_errno = errno;
    char failbuf[3 * sizeof (pid_t) + sizeof "out of memory []"];
...

    /* Build the message in a memory-buffer stream.  */
    f = __open_memstream (&buf, &bufsize);
    if (f == NULL)
      {
...
      }
    else
      {
        __fsetlocking (f, FSETLOCKING_BYCALLER);
        fprintf (f, "<%d>", pri);---------------------------------------------------輸出pri到f,pri包括facility和level。
        (void) time (&now);
        f->_IO_write_ptr += __strftime_l (f->_IO_write_ptr,
                          f->_IO_write_end
                          - f->_IO_write_ptr,
                          "%h %e %T ",
                          __localtime_r (&now, &now_tm),
                          _nl_C_locobj_ptr);----------------------------------------輸出log產生的時間。
        msgoff = ftell (f);
        if (LogTag == NULL)
          LogTag = __progname;
        if (LogTag != NULL)
          __fputs_unlocked (LogTag, f);
        if (LogStat & LOG_PID)
          fprintf (f, "[%d]", (int) __getpid ());-----------------------------------在使能LOG_PID之后,輸出pid到f。 if (LogTag != NULL)
          {
        __putc_unlocked (':', f);
        __putc_unlocked (' ', f);
          }

        /* Restore errno for %m format.  */
        __set_errno (saved_errno);

        /* We have the header.  Print the user's format into the
               buffer.  */
        if (flag == -1)--------------------------------------------------------------根據flag是否有效,輸出flag、fmt、ap內容到f。
          vfprintf (f, fmt, ap);
        else
          __vfprintf_chk (f, flag, fmt, ap);

        /* Close the memory stream; this will finalize the data
           into a malloc'd buffer in BUF.  */
        fclose (f);
      }
...

    /* Prepare for multiple users.  We have to take care: open and
       write are cancellation points.  */
    struct cleanup_arg clarg;
    clarg.buf = buf;
    clarg.oldaction = NULL;
    __libc_cleanup_push (cancel_handler, &clarg);
    __libc_lock_lock (syslog_lock);

...
    /* Get connected, output the message to the local logger. */
    if (!connected)
        openlog_internal(LogTag, LogStat | LOG_NDELAY, 0);---------------------------如果還沒有和/dev/log連接上,調用openlog_internal()連接,操作句柄是LogFile。/* 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)-----------------buf中存放上面組裝好的log,通過send()發送到/dev/log。
      {
        if (connected)---------------------------------------------------------------如果通過openlog()打開過,那么send異常有可能是socket異常。需要重新關閉打開。
          {
        /* 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 */----------------------關閉LogFile。
        /*...
          }
      }

#ifndef NO_SIGPIPE
    if (sigpipe == 0)
        __sigaction (SIGPIPE, &oldaction, (struct sigaction *) NULL);
#endif

    /* End of critical section.  */
    __libc_cleanup_pop (0);
    __libc_lock_unlock (syslog_lock);

    if (buf != failbuf)
        free (buf);
}

openlog()和closelog()主要通過openlog_internal()和closelog_internal()實現。

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);---------------------打開一個socket。 if (LogFile == -1)
                return;
            }
        }
        if (LogFile != -1 && !connected)
        {
            int old_errno = errno;
            if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))-----------------------將LogFile和/dev/log socket連接,后面可以傳輸數據。 == -1)
            {
...
            } else
                connected = 1;
        }
        break;
    }
}

static void
closelog_internal (void)
{
  if (!connected)
    return;

  __close (LogFile);
  LogFile = -1;
  connected = 0;
}

 

5.3 syslogd解析

syslogd_main()中主要是解析各種選項,轉換成參數。

static void do_syslogd(void) NORETURN;
static void do_syslogd(void)
{
#if ENABLE_FEATURE_REMOTE_LOG
    llist_t *item;
#endif
#if ENABLE_FEATURE_SYSLOGD_DUP
    int last_sz = -1;
    char *last_buf;
    char *recvbuf = G.recvbuf;
#else
#define recvbuf (G.recvbuf)
#endif

    /* Set up signal handlers (so that they interrupt read()) */
    signal_no_SA_RESTART_empty_mask(SIGTERM, record_signo);
    signal_no_SA_RESTART_empty_mask(SIGINT, record_signo);
    //signal_no_SA_RESTART_empty_mask(SIGQUIT, record_signo);
    signal(SIGHUP, SIG_IGN);
#ifdef SYSLOGD_MARK
    signal(SIGALRM, do_mark);
    alarm(G.markInterval);
#endif
    xmove_fd(create_socket(), STDIN_FILENO);-------------------------------------創建/dev/log socket,並且將句柄dup到STDIN_FILENO。從socket接收數據。 if (option_mask32 & OPT_circularlog)
        ipcsyslog_init();

    if (option_mask32 & OPT_kmsg)
        kmsg_init();

    timestamp_and_log_internal("syslogd started: BusyBox v" BB_VER);

    while (!bb_got_signal) {
        ssize_t sz;

#if ENABLE_FEATURE_SYSLOGD_DUP
        last_buf = recvbuf;
        if (recvbuf == G.recvbuf)
            recvbuf = G.recvbuf + MAX_READ;
        else
            recvbuf = G.recvbuf;
#endif
 read_again:
        sz = read(STDIN_FILENO, recvbuf, MAX_READ - 1);----------------------------------從STDIN_FILENO讀取syslog()輸入的數據。 if (sz < 0) {
            if (!bb_got_signal)
                bb_perror_msg("read from %s", _PATH_LOG);
            break;
        }

        /* Drop trailing '\n' and NULs (typically there is one NUL) */
        while (1) {
            if (sz == 0)
                goto read_again;
            /* man 3 syslog says: "A trailing newline is added when needed".
             * However, neither glibc nor uclibc do this:
             * syslog(prio, "test")   sends "test\0" to /dev/log,
             * syslog(prio, "test\n") sends "test\n\0".
             * IOW: newline is passed verbatim!
             * I take it to mean that it's syslogd's job
             * to make those look identical in the log files. */
            if (recvbuf[sz-1] != '\0' && recvbuf[sz-1] != '\n')---------------------------'\0'作為不同syslog()的分隔符;'\n'作為換行符。 break;
            sz--;
        }

...
        if (!ENABLE_FEATURE_REMOTE_LOG || (option_mask32 & OPT_locallog)) {
            recvbuf[sz] = '\0'; /* ensure it *is* NUL terminated */ split_escape_and_log(recvbuf, sz);---------------------------------------------這里的recvbuf已經根據'\0'和'\n'進行分割,里面分析facility和level,進行轉發。
        }
    } /* while (!bb_got_signal) */

    timestamp_and_log_internal("syslogd exiting");
    remove_pidfile(CONFIG_PID_FILE_PATH "/syslogd.pid");
    ipcsyslog_cleanup();
    if (option_mask32 & OPT_kmsg)
        kmsg_cleanup();
    kill_myself_with_sig(bb_got_signal);
#undef recvbuf
}

split_escape_and_log()將消息分割出prio和msg,進行處理。

static void split_escape_and_log(char *tmpbuf, int len)
{
    char *p = tmpbuf;

    tmpbuf += len;
    while (p < tmpbuf) {
        char c;
        char *q = G.parsebuf;
        int pri = (LOG_USER | LOG_NOTICE);

        if (*p == '<') {-------------------------------------------此處的tmpbuf都是在__vsyslog_chk()中組裝,這里開頭應該是'<pri>',從中解析出pri。 /* Parse the magic priority number */
            pri = bb_strtou(p + 1, &p, 10);
            if (*p == '>')
                p++;
            if (pri & ~(LOG_FACMASK | LOG_PRIMASK))
                pri = (LOG_USER | LOG_NOTICE);
        }

        while ((c = *p++)) {---------------------------------------遇到'\0'停止。而且一定會有。 if (c == '\n')
                c = ' ';
            if (!(c & ~0x1f) && c != '\t') {
                *q++ = '^';
                c += '@'; /* ^@, ^A, ^B... */
            }
            *q++ = c;
        }
        *q = '\0';

        /* Now log it */ timestamp_and_log(pri, G.parsebuf, q - G.parsebuf);
    }
}

static void timestamp_and_log(int pri, char *msg, int len)
{
    char *timestamp;
    time_t now;

    /* Jan 18 00:11:22 msg... */
    /* 01234567890123456 */
    if (len < 16 || msg[3] != ' ' || msg[6] != ' '
     || msg[9] != ':' || msg[12] != ':' || msg[15] != ' '
    ) {----------------------------------------------------------------------------------沒有時間戳的msg處理,添加timestamp。
        time(&now);
    #if 0
        timestamp = ctime(&now) + 4; /* skip day of week */
    #else
        char time_str[16];
        struct timespec ts;
        int ret = 0;

        timestamp = time_str;
        ret = clock_gettime(CLOCK_MONOTONIC, &ts);
        snprintf(timestamp, 16, "%8ld%c%6ld", ts.tv_sec, '.', ts.tv_nsec);
    #endif
    } else {
        now = 0;
        timestamp = msg;
#if 1-------------------------------------------------------------------------------------修改時間戳的來源,使用更高精度的時間戳。
        struct timespec ts;
        int ret = 0;

        ret = clock_gettime(CLOCK_MONOTONIC, &ts);
        snprintf(timestamp, 16, "%8ld%c%6ld", ts.tv_sec, '.', ts.tv_nsec);
#endif
        msg += 16;
    }
    timestamp[15] = '\0';-----------------------------------------------------------------將msg分割成timestamp和msg兩部分、 if (option_mask32 & OPT_kmsg) {
        log_to_kmsg(pri, msg);
        return;
    }

    if (option_mask32 & OPT_small)-------------------------------------------------------根據syslogd選項'-S'來決定輸出內容。
        sprintf(G.printbuf, "%s %s\n", timestamp, msg);
    else {
        char res[20];
        parse_fac_prio_20(pri, res);
        sprintf(G.printbuf, "%s %.64s %s %s\n", timestamp, G.hostname, res, msg);--------增加hostname和facility/level輸出。
    }

    /* Log message locally (to file or shared mem) */
#if ENABLE_FEATURE_SYSLOGD_CFG
    {
        bool match = 0;
        logRule_t *rule;
        uint8_t facility = LOG_FAC(pri);
        uint8_t prio_bit = 1 << LOG_PRI(pri);

        for (rule = G.log_rules; rule; rule = rule->next) {
            if (rule->enabled_facility_priomap[facility] & prio_bit) {--------------------如果當前msg的prio滿足/etc/syslog.conf中某一個條件,則將log內容輸出到對應的rule->file中。如果一個msd滿足多個rule,那么輸出多次。
                log_locally(now, G.printbuf, rule->file);
                match = 1;
            }
        }
        if (match)-------------------------------------------------------------------------如果msg滿足rule中的一個,則退出。不會進入下面的全局性/var/log/messages。 return;
    }
#endif
    if (LOG_PRI(pri) < G.logLevel) {
#if ENABLE_FEATURE_IPC_SYSLOG
        if ((option_mask32 & OPT_circularlog) && G.shbuf) {
            log_to_shmem(G.printbuf);
            return;
        }
#endif log_locally(now, G.printbuf, &G.logFile);-------------------------------------------不在/etc/syslog.conf中的msg,滿足設置的loglevel后。
    }
}

 log_locally()將log保存到指定的文件。

/* Print a message to the log file. */
static void log_locally(time_t now, char *msg, logFile_t *log_file)
{
#ifdef SYSLOGD_WRLOCK
    struct flock fl;
#endif
    int len = strlen(msg);

    /* fd can't be 0 (we connect fd 0 to /dev/log socket) */----------------------------log_file->fd句柄0給/dev/log使用,1給stdout使用。
    /* fd is 1 if "-O -" is in use */
    if (log_file->fd > 1) {
        if (!now)
            now = time(NULL);
        if (log_file->last_log_time != now) {-------------------------------------------每一秒鍾開關一次log_file,讓用戶有刪除log_file的機會。
            log_file->last_log_time = now;
            close(log_file->fd);
            goto reopen;
        }
    }
    else if (log_file->fd == 1) {
        /* We are logging to stdout: do nothing */
    }
    else {
        if (LONE_DASH(log_file->path)) {
            log_file->fd = 1;
            /* log_file->isRegular = 0; - already is */
        } else {
 reopen:
            log_file->fd = open(log_file->path, O_WRONLY | O_CREAT
                    | O_NOCTTY | O_APPEND | O_NONBLOCK,
                    0666);----------------------------------------------------------------打開log_file文件。 if (log_file->fd < 0) {-------------------------------------------------------打開失敗輸出到/dev/console。 /* cannot open logfile? - print to /dev/console then */
                int fd = device_open(DEV_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
                if (fd < 0)
                    fd = 2; /* then stderr, dammit */
                full_write(fd, msg, len);
                if (fd != 2)
                    close(fd);
                return;
            }
#if ENABLE_FEATURE_ROTATE_LOGFILE
            {
                struct stat statf;
                log_file->isRegular = (fstat(log_file->fd, &statf) == 0 && S_ISREG(statf.st_mode));
                /* bug (mostly harmless): can wrap around if file > 4gb */
                log_file->size = statf.st_size;
            }
#endif
        }
    }

#ifdef SYSLOGD_WRLOCK
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 1;
    fl.l_type = F_WRLCK;
    fcntl(log_file->fd, F_SETLKW, &fl);
#endif

#if ENABLE_FEATURE_ROTATE_LOGFILE
    if (G.logFileSize && log_file->isRegular && log_file->size > G.logFileSize) {---------判斷當前寫入是否會發生尺寸溢出,超出分隔尺寸的話。進行文件分隔。 if (G.logFileRotate) { /* always 0..99 */
            int i = strlen(log_file->path) + 3 + 1;
            char oldFile[i];
            char newFile[i];
            i = G.logFileRotate - 1;
            /* rename: f.8 -> f.9; f.7 -> f.8; ... */
            while (1) {-------------------------------------------------------------------將已有的文件名末尾數字加1,文件名末尾達到最大數字的文件將被覆蓋丟棄。
                sprintf(newFile, "%s.%d", log_file->path, i);
                if (i == 0) break;
                sprintf(oldFile, "%s.%d", log_file->path, --i);
                /* ignore errors - file might be missing */
                rename(oldFile, newFile);
            }
            /* newFile == "f.0" now */
            rename(log_file->path, newFile);----------------------------------------------將messages變成messages.0.
        }

        unlink(log_file->path);
#ifdef SYSLOGD_WRLOCK
        fl.l_type = F_UNLCK;
        fcntl(log_file->fd, F_SETLKW, &fl);
#endif
        close(log_file->fd);
        goto reopen;
    }
/* TODO: what to do on write errors ("disk full")? */
    len = full_write(log_file->fd, msg, len);---------------------------------------------將msg寫入文件。 if (len > 0)
        log_file->size += len;
#else
    full_write(log_file->fd, msg, len);
#endif

#ifdef SYSLOGD_WRLOCK
    fl.l_type = F_UNLCK;
    fcntl(log_file->fd, F_SETLKW, &fl);
#endif
}

經過如上分析可知內核log如何被klogd通過klogctl()獲取,調用syslog()轉發;應用如何通過syslog()發送消息到/dev/log;syslod是如何從/dev/log接收信息,並進行分發的。

5.3.1 解析syslog.conf

static void parse_syslogdcfg(const char *file)
{
    char *t;
    logRule_t **pp_rule;
    /* tok[0] set of selectors */
    /* tok[1] file name */
    /* tok[2] has to be NULL */
    char *tok[3];
    parser_t *parser;

    parser = config_open2(file ? file : "/etc/syslog.conf",
                file ? xfopen_for_read : fopen_for_read);
    if (!parser)
        /* didn't find default /etc/syslog.conf */
        /* proceed as if we built busybox without config support */
        return;

    /* use ptr to ptr to avoid checking whether head was initialized */
    pp_rule = &G.log_rules;
    /* iterate through lines of config, skipping comments */
    while (config_read(parser, tok, 3, 2, "# \t", PARSE_NORMAL | PARSE_MIN_DIE)) {-------------------------讀取syslog.conf每一行。 char *cur_selector;
        logRule_t *cur_rule;

        /* unexpected trailing token? */
        if (tok[2])
            goto cfgerr;

        cur_rule = *pp_rule = xzalloc(sizeof(*cur_rule));

        cur_selector = tok[0];
        /* iterate through selectors: "kern.info;kern.!err;..." */
        do {-----------------------------------------------------------------------------------------------編譯一行中每一個selector。 const CODE *code;
            char *next_selector;
            uint8_t negated_prio; /* "kern.!err" */
            uint8_t single_prio;  /* "kern.=err" */
            uint32_t facmap; /* bitmap of enabled facilities */
            uint8_t primap;  /* bitmap of enabled priorities */
            unsigned i;

            next_selector = strchr(cur_selector, ';');
            if (next_selector)
                *next_selector++ = '\0';

            t = strchr(cur_selector, '.');
            if (!t)
                goto cfgerr;
            *t++ = '\0'; /* separate facility from priority */

            negated_prio = 0;
            single_prio = 0;-------------------------------------------------------------------------------解析selector中的priority,五種情況!、=、*、none、info/notice/debu/err。 if (*t == '!') {-------------------------------------------------------------------------------排除特定優先級。
                negated_prio = 1;
                ++t;
            }
            if (*t == '=') {-------------------------------------------------------------------------------指定特定優先級。
                single_prio = 1;
                ++t;
            }

            /* parse priority */
            if (*t == '*')---------------------------------------------------------------------------------所有優先級。
                primap = 0xff; /* all 8 log levels enabled */
            else {
                uint8_t priority;
                code = find_by_name(t, prioritynames);
                if (!code)
                    goto cfgerr;
                primap = 0;
                priority = code->c_val;
                if (priority == INTERNAL_NOPRI) {-----------------------------------------------------------INTERNAL_NOPRI為0x10,這里需要取反,所以所有優先級都不顯示。 /* ensure we take "enabled_facility_priomap[fac] &= 0" branch below */
                    negated_prio = 1;
                } else {
                    priority = 1 << priority;
                    do {
                        primap |= priority;
                        if (single_prio)---------------------------------------------------------------------特定優先級情況,只置一位。 break;
                        priority >>= 1;----------------------------------------------------------------------將當前優先級以及更高優先級位置位。
                    } while (priority);
                    if (negated_prio)------------------------------------------------------------------------'!'情況,下面enabled_facility_priomap還會特殊處理。
                        primap = ~primap;
                }
            }

            /* parse facility */------------------------------------------------------------------------------解析facility情況。
            if (*cur_selector == '*')-------------------------------------------------------------------------所有facility。
                facmap = (1<<LOG_NFACILITIES) - 1;
            else {
                char *next_facility;
                facmap = 0;
                t = cur_selector;
                /* iterate through facilities: "kern,daemon.<priospec>" */
                do {------------------------------------------------------------------------------------------多facility情況。
                    next_facility = strchr(t, ',');
                    if (next_facility)
                        *next_facility++ = '\0';
                    code = find_by_name(t, facilitynames);
                    if (!code)
                        goto cfgerr;
                    /* "mark" is not a real facility, skip it */
                    if (code->c_val != INTERNAL_MARK)
                        facmap |= 1<<(LOG_FAC(code->c_val));
                    t = next_facility;
                } while (t);
            }

            /* merge result with previous selectors */
            for (i = 0; i < LOG_NFACILITIES; ++i) {------------------------------------------------------------合並多selector數據,合並到cur_rule中。 if (!(facmap & (1<<i)))
                    continue;
                if (negated_prio)
                    cur_rule->enabled_facility_priomap[i] &= primap;
                else
                    cur_rule->enabled_facility_priomap[i] |= primap;
            }

            cur_selector = next_selector;
        } while (cur_selector);

        /* check whether current file name was mentioned in previous rules or
         * as global logfile (G.logFile).
         */
        if (strcmp(G.logFile.path, tok[1]) == 0) {
            cur_rule->file = &G.logFile;
            goto found;
        }
        /* temporarily use cur_rule as iterator, but *pp_rule still points
         * to currently processing rule entry.
         * NOTE: *pp_rule points to the current (and last in the list) rule.
         */
        for (cur_rule = G.log_rules; cur_rule != *pp_rule; cur_rule = cur_rule->next) {
            if (strcmp(cur_rule->file->path, tok[1]) == 0) {
                /* found - reuse the same file structure */
                (*pp_rule)->file = cur_rule->file;
                cur_rule = *pp_rule;
                goto found;
            }
        }
        cur_rule->file = xzalloc(sizeof(*cur_rule->file));
        cur_rule->file->fd = -1;
        cur_rule->file->path = xstrdup(tok[1]);--------------------------------------------------------一行一個rule,設置輸出文件路徑。
 found:
        pp_rule = &cur_rule->next;
    }
    config_close(parser);
    return;

 cfgerr:
    bb_error_msg_and_die("error in '%s' at line %d",
            file ? file : "/etc/syslog.conf",
            parser->lineno);
}

 

 

5.3.2 輸出syslog到/dev/kmsg

如果在syslogd選項中加-K,那么就會將syslog()輸出寫到/dev/kmsg中。通過dmesg可以讀取。

static void kmsg_init(void)
{
    G.kmsgfd = xopen("/dev/kmsg", O_WRONLY);------------------------------------------打開/dev/kmsg文件。 /*
     * kernel < 3.5 expects single char printk KERN_* priority prefix,
     * from 3.5 onwards the full syslog facility/priority format is supported
     */
    if (get_linux_version_code() < KERNEL_VERSION(3,5,0))
        G.primask = LOG_PRIMASK;
    else
        G.primask = -1;
}

static void kmsg_cleanup(void)
{
    if (ENABLE_FEATURE_CLEAN_UP)
        close(G.kmsgfd);
}

/* Write message to /dev/kmsg */
static void log_to_kmsg(int pri, const char *msg)
{
    /*
     * kernel < 3.5 expects single char printk KERN_* priority prefix,
     * from 3.5 onwards the full syslog facility/priority format is supported
     */
    pri &= G.primask;

    full_write(G.kmsgfd, G.printbuf, sprintf(G.printbuf, "<%d>%s\n", pri, msg));-------加上pri,和msg寫入/dev/kmsg。
}

 

 

5.3.3 ipc和logread功能

 

/* our shared key (syslogd.c and logread.c must be in sync) */
enum { KEY_ID = 0x414e4547 }; /* "GENA" */

static void ipcsyslog_cleanup(void)
{
    if (G.shmid != -1) {
        shmdt(G.shbuf);
    }
    if (G.shmid != -1) {
        shmctl(G.shmid, IPC_RMID, NULL);
    }
    if (G.s_semid != -1) {
        semctl(G.s_semid, 0, IPC_RMID, 0);
    }
}

static void ipcsyslog_init(void)
{
    if (DEBUG)
        printf("shmget(%x, %d,...)\n", (int)KEY_ID, G.shm_size);

    G.shmid = shmget(KEY_ID, G.shm_size, IPC_CREAT | 0644);
    if (G.shmid == -1) {
        bb_perror_msg_and_die("shmget");
    }

    G.shbuf = shmat(G.shmid, NULL, 0);
    if (G.shbuf == (void*) -1L) { /* shmat has bizarre error return */
        bb_perror_msg_and_die("shmat");
    }

    memset(G.shbuf, 0, G.shm_size);
    G.shbuf->size = G.shm_size - offsetof(struct shbuf_ds, data) - 1;
    /*G.shbuf->tail = 0;*/

    /* we'll trust the OS to set initial semval to 0 (let's hope) */
    G.s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023);
    if (G.s_semid == -1) {
        if (errno == EEXIST) {
            G.s_semid = semget(KEY_ID, 2, 0);
            if (G.s_semid != -1)
                return;
        }
        bb_perror_msg_and_die("semget");
    }
}

/* Write message to shared mem buffer */
static void log_to_shmem(const char *msg)
{
    int old_tail, new_tail;
    int len;

    if (semop(G.s_semid, G.SMwdn, 3) == -1) {
        bb_perror_msg_and_die("SMwdn");
    }

    /* Circular Buffer Algorithm:
     * --------------------------
     * tail == position where to store next syslog message.
     * tail's max value is (shbuf->size - 1)
     * Last byte of buffer is never used and remains NUL.
     */
    len = strlen(msg) + 1; /* length with NUL included */
 again:
    old_tail = G.shbuf->tail;
    new_tail = old_tail + len;
    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;
    }
    if (semop(G.s_semid, G.SMwup, 1) == -1) {
        bb_perror_msg_and_die("SMwup");
    }
    if (DEBUG)
        printf("tail:%d\n", G.shbuf->tail);
}

 

 

 

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;
#if ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
        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 ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
        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 ENABLE_FEATURE_LOGREAD_REDUCED_LOCKING
        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);
}

 

 

 

6. 小結

上述講到了syslog機制的各種相關內容,kernel相關的有syslog系統調用、klogctl()、klogd;syslogd相關的有syslog()、syslogd、syslog.conf等。

在使用中有兩個地方可以配置syslog行為:一是啟動syslogd/klogd時設置選項;一是配置syslog.conf文件,rule項是越少越好。

在應用中調用syslog()輸出日志。

 

參考文檔:《linux日志:syslogd和klogd及syslog》、《syslogd以及syslog.conf文件解讀說明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM