點點滴滴 - Nginx日誌功能 PK Linux內核printk

題記:Nginx之旅系列是用來記錄Nginx從使用到源碼學習的點點滴滴,分享學習Nginx的快樂

Nginx 首頁: http://nginx.org/

Nginx日誌功能 PK Linux內核printk


        本來只想分析一下Nginx中日誌的實現,但是突發奇想,想把Nginx中的日誌功能與Linux kernel中的printk進行一下橫向對比,即學習了Nginx的日誌功能,又總結了Linux的printk的實現,於是乎這麼一篇博文就出現了。本文將從日誌級別相關函數實現和日誌函數使用的角度來梳理,byhankswang的初衷是使用盡量少的代碼和文字說明白儘量多的事情。


PK1:  Nginx日誌級別和printk日誌級別

        對與日誌系統的設計,良好的日誌系統應該提供豐富的日誌級別和簡單易用的API。豐富的日誌級別對與開發人員的調試是非常有幫助的。不許要時儘可能的少顯示不重要的日誌,需要時又能提供豐富調試的信息,儘快的確定問題所在。日誌系統對於一個產品來說不是核心功能但是是很重要的功能。

1.1Nginx日誌級別的定義

// within file nginx-1.5.2/src/core/ngx_log.h

#define NGX_LOG_STDERR            0
#define NGX_LOG_EMERG              1
#define NGX_LOG_ALERT               2
#define NGX_LOG_CRIT                  3
#define NGX_LOG_ERR                   4
#define NGX_LOG_WARN               5
#define NGX_LOG_NOTICE             6
#define NGX_LOG_INFO                  7
#define NGX_LOG_DEBUG              8

//within file nginx-1.5.2/src/core/ngx_log.h
#define NGX_LOG_DEBUG_CORE        0x010
#define NGX_LOG_DEBUG_ALLOC       0x020
#define NGX_LOG_DEBUG_MUTEX       0x040
#define NGX_LOG_DEBUG_EVENT       0x080
#define NGX_LOG_DEBUG_HTTP        0x100
#define NGX_LOG_DEBUG_MAIL        0x200
#define NGX_LOG_DEBUG_MYSQL       0x400


1.2printk日誌級別的定義

//within file linux-3.10.1/include/linux/kern_levels.h

#define KERN_SOH        "\001"          /* ASCII Start Of Header */
#define KERN_SOH_ASCII  '\001'

#define KERN_EMERG      KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT      KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT       KERN_SOH "2"    /* critical conditions */
#define KERN_ERR        KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE     KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO       KERN_SOH "6"    /* informational */
#define KERN_DEBUG      KERN_SOH "7"    /* debug-level messages */

#define KERN_DEFAULT    KERN_SOH "d"    /* the default kernel loglevel */


Nginx中關於日誌的級別定義來9種,在ngx_log_init初始化的時候默認的級別是NGX_LOG_NOTICE,也就是說當日志級別高於NOTICE的時候都會被看到,而低於此級別的日誌就會被忽略。Linux kernel中printk的默認級別是KERN_DEFAULT。


PK2:Nginx日誌函數實現和printk的實現

本小節講分析一下ngx_log_error, ngx_log_error_core, ngx_log_debug 和linux內核中printk的實現:

2.1 Nginx的日誌函數封裝

Nginx中日誌相關的函數最重要的三個分別是ngx_log_error, ngx_log_error_core 和ngx_log_debug。其中ngx_log_error和ngx_log_debug又是對ngx_log_error_core的封裝。封裝的方式包括三種,區分了C99, GCC 和普通的可變參數方式。下面以GCC的可變參數方式說明具體問題,其他的兩種與此類似。

//within file ngnix-1.5.2/src/core/ngx_log.c

#elif (NGX_HAVE_GCC_VARIADIC_MACROS)

#define NGX_HAVE_VARIADIC_MACROS  1

#define ngx_log_error(level, log, args...)                                    \
    if ((log)->log_level >= level) ngx_log_error_core(level, log, args)

void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
    const char *fmt, ...);

#define ngx_log_debug(level, log, args...)                                    \
    if ((log)->log_level & level)                                             \
        ngx_log_error_core(NGX_LOG_DEBUG, log, args)


2.2Nginx的ngx_log_error_core實現

對於ngx_log_error_core如何內容格式化這裏暫不討論,討論的是最終調用的那個底層函數。

   //within file ngnix-1.5.2/src/core/ngx_log.c

    while (log) {
        if (log->log_level < level && !debug_connection) {
            break;
        }
        (void) ngx_write_fd(log->file->fd, errstr, p - errstr);

        if (log->file->fd == ngx_stderr) {
            wrote_stderr = 1;
        }
        log = log->next;
    }

其中ngx_write_fd是最終把日誌信息寫到文件中的,對於ngx_write_fd的實現最終是通過調用libc函數的write來直接寫文件的,封裝再多層,脫掉了還是一樣。

/*
 * we use inlined function instead of simple #define
 * because glibc 2.3 sets warn_unused_result attribute for write()
 * and in this case gcc 4.3 ignores (void) cast
 */
static ngx_inline ssize_t
ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
{
    return write(fd, buf, n);
}

2.3 Linux內核printk的內核實現

// within file linux-3.10.1/kernel/printk.c

asmlinkage int printk(const char *fmt, ...) 
{
        va_list args;
        int r;

#ifdef CONFIG_KGDB_KDB
        if (unlikely(kdb_trap_printk)) {
                va_start(args, fmt);
                r = vkdb_printf(fmt, args);
                va_end(args);
                return r;
        }    
#endif
        va_start(args, fmt);
        r = vprintk_emit(0, -1, NULL, 0, fmt, args);
        va_end(args);

        return r;
}
EXPORT_SYMBOL(printk);

說明:

A. asmlinkage指明printk直接從內存中讀取參數而不是從寄存器中讀取參數;

B. CONFIG_KGDB_KDB 是不是很熟悉呢?調試內核的話用KDB或者KGDB其實就是把日誌重定向到串口;

C. va_list, va_start, va_end其實就是可變參數的實現核心,不管是用戶層可變參數函數的實現還是內核層,va_*都是利器;

D. 爲了便於追蹤和調試,日誌應該存在文件之中,Linux中dmesg就是從/var/log/dmesg文件中讀取內核的日誌。當我們加printk的時候也是會被重定向到這個文件之中。Nginx的日誌;

E. vprintk_emit和更細緻的內容另外開篇博客再講。


PK3:Nginx日誌API和printk的使用

3.1 Nginx日誌函數的調用

//within file nginx-1.5.2/src/core/connection.c

ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "setsockopt(SO_RCVBUF, %d) %V failed, ignored", ls[i].rcvbuf, &ls[i].addr_text);

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler");

3.2 Printk的使用

//within file linux-3.10.1/kernel/sched/core.c

printk(  "[ BUG: circular locking deadlock detected! ]\n");

printk(KERN_DEBUG "%*s groups:", level + 1, "");


從使用性的角度來看,ngx_log_error和ngx_log_debug把日誌嚴格分離開了,各自用途明確。 Printk如果不加log level的話是默認level。


PK4:日誌API的可封裝性

在內核中不同的模塊都有對printk的封裝,例如netfilter模塊對printk的重定義:#define NFDEBUG(format, args...)  printk(KERN_DEBUG format , ## args)。Nginx對ngx_log_error_core的封裝在2.1中已經簡單的說了一下。


另外對日誌信息的輸出也是對日誌信息實現的體現,既可以輸出到標準輸出stderr中,也可以輸出到相關的文件中例如/var/log中。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章