題記:Nginx之旅系列是用來記錄Nginx從使用到源碼學習的點點滴滴,分享學習Nginx的快樂linux
Nginx 首頁: http://nginx.org/nginx
Nginx日誌功能 PK Linux內核printk
socket
原本只想分析一下Nginx中日誌的實現,可是突發奇想,想把Nginx中的日誌功能與Linux kernel中的printk進行一下橫向對比,即學習了Nginx的日誌功能,又總結了Linux的printk的實現,因而乎這麼一篇博文就出現了。本文將從日誌級別相關函數實現和日誌函數使用的角度來梳理,byhankswang的初衷是使用盡可能少的代碼和文字說明白儘可能多的事情。函數
PK1: Nginx日誌級別和printk日誌級別學習
對與日誌系統的設計,良好的日誌系統應該提供豐富的日誌級別和簡單易用的API。豐富的日誌級別對與開發人員的調試是很是有幫助的。不準要時儘量的少顯示不重要的日誌,須要時又能提供豐富調試的信息,儘快的肯定問題所在。日誌系統對於一個產品來講不是核心功能可是是很重要的功能。ui
1.1Nginx日誌級別的定義this
// within file nginx-1.5.2/src/core/ngx_log.hspa
#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
debug
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中。