zlog使用手冊
來源 http://hardysimpson.github.io/zlog/UsersGuide-CN.htmlhtml
Contents
- Chapter 1 zlog是什麼?
- Chapter 2 zlog不是什麼?
- Chapter 3 Hello World
- Chapter 4 Syslog 模型
- Chapter 5 配置文件
- Chapter 6 zlog接口(API)
- Chapter 7 高階使用
- Chapter 8 尾聲
Chapter 1 zlog是什麼?
zlog是一個高可靠性、高性能、線程安全、靈活、概念清晰的純C日誌函數庫。linux
事實上,在C的世界裏面沒有特別好的日誌函數庫(就像JAVA裏面的的log4j,或者C++的log4cxx)。C程序員都喜歡用本身的輪子。printf就是個挺好的輪子,但沒辦法經過配置改變日誌的格式或者輸出文件。syslog是個系統級別的輪子,不過速度慢,並且功能比較單調。git
因此我寫了zlog。程序員
zlog在效率、功能、安全性上大大超過了log4c,而且是用c寫成的,具備比較好的通用性。github
zlog有這些特性:redis
- syslog分類模型,比log4j模型更加直接了當
- 日誌格式定製,相似於log4j的pattern layout
- 多種輸出,包括動態文件、靜態文件、stdout、stderr、syslog、用戶自定義輸出函數
- 運行時手動、自動刷新配置文件(同時保證安全)
- 高性能,在個人筆記本上達到25萬條日誌每秒, 大概是syslog(3)配合rsyslogd的1000倍速度
- 用戶自定義等級
- 多線程和多進程環境下保證安全轉檔
- 精確到微秒
- 簡單調用包裝dzlog(一個程序默認只用一個分類)
- MDC,線程鍵-值對的表,能夠擴展用戶自定義的字段
- 自診斷,能夠在運行時輸出zlog本身的日誌和配置狀態
- 不依賴其餘庫,只要是個POSIX系統就成(固然還要一個C99兼容的vsnprintf)
相關連接:shell
主頁:http://hardysimpson.github.com/zlog/數據庫
下載:https://github.com/HardySimpson/zlog/releaseswindows
郵箱:HardySimpson1984@gmail.com緩存
1.1 兼容性說明
- zlog是基於POSIX的。目前我手上有的環境只有AIX和linux。在其餘的系統下(FreeBSD, NetBSD, OpenBSD, OpenSolaris, Mac OS X...)估計也能行,有問題歡迎探討。
- zlog使用了一個C99兼容的vsnprintf。也就是說若是緩存大小不足,vsnprintf將會返回目標字符串應有的長度(不包括’\0’)。若是在你的系統上vsnprintf不是這麼運做的,zlog就不知道怎麼擴大緩存。若是在目標緩存不夠的時候vsnprintf返回-1,zlog就會認爲此次寫入失敗。幸運的是目前大多數c標準庫符合C99標準。glibc 2.1,libc on AIX, libc on freebsd...都是好的,不過glibc2.0不是。在這種狀況下,用戶須要本身來裝一個C99兼容的vsnprintf,來crack這個函數庫。我推薦ctrio, 或者C99-snprintf。只要改buf.c就行,祝好運!
- 有網友提供了以下版本,方便其餘平臺上安裝編譯,很是感謝!
auto tools版本: https://github.com/bmanojlovic/zlog
cmake版本: https://github.com/lisongmin/zlog
windows版本: https://github.com/lopsd07/WinZlog
1.2 zlog 1.2 發佈說明
- zlog 1.2 新增了這些功能
- 對管道的支持,今後zlog能夠外接cronolog這樣的日誌過濾程序來輸出
- 全面的日誌轉檔支持,詳見5.6
- 其餘兼容性的代碼改動
- zlog 1.2 在庫方面是和zlog 1.0/1.1二進制兼容的,區別在於:
- 全部的宏改成小寫,ZLOG_INFO->zlog_info,方便開發者手工輸入。這是一個巨大的改變,若是zlog1.1/1.0的用戶要用zlog 1.2的話,須要寫一個腳本,把源代碼中的大寫批量替換爲小寫,而後從新編譯你的程序。我提供了一個腳本:
- sed -i -e ’s/\b\w*ZLOG\w*\b/\L&\E/g’ aa.c
- 取消了auto tools的使用,也就是說,不論你在任何平臺,都須要gcc和gnu make才能編譯安裝zlog。主流的操做系統(Aix, OpenSolaris..)都能安裝gcc和gnu make。固然也能夠自行修改makefile來完成編譯,對於平臺稍有經驗的Geek均可以自行完成!
- 全部的宏改成小寫,ZLOG_INFO->zlog_info,方便開發者手工輸入。這是一個巨大的改變,若是zlog1.1/1.0的用戶要用zlog 1.2的話,須要寫一個腳本,把源代碼中的大寫批量替換爲小寫,而後從新編譯你的程序。我提供了一個腳本:
Chapter 2 zlog不是什麼?
zlog的目標是成爲一個簡而精的日誌函數庫,不會直接支持網絡輸出或者寫入數據庫,不會直接支持日誌內容的過濾和解析。
緣由很明顯,日誌庫是被應用程序調用的,全部花在日誌庫上的時間都是應用程序運行時間的一部分,而上面說的這些操做都很費時間,會拖慢應用程序的速度。這些事兒應該在別的進程或者別的機器上作。
若是你須要這些特性,我建議使用rsyslog、zLogFabric、Logstash,這些日誌蒐集、過濾、存儲軟件,固然這是單獨的進程,不是應用程序的一部分。
目前zlog已經支持7.4,能夠本身實現一個輸出函數,自由的把日誌輸出到其餘進程或者其餘機器。而把日誌的分類匹配、日誌格式成型的工做交給zlog。
目前個人想法是實現一個zlog-redis客戶端,用自定義輸出功能,把日誌存儲到本機或者遠程的redis服務器內,而後用其餘進程(也使用zlog庫)來把日誌寫到文件裏面,不知你們覺得這個想法如何?歡迎和我聯繫探討。
Chapter 3 Hello World
3.1 編譯和安裝zlog
-
$ tar -zxvf zlog-latest-stable.tar.gz
$ cd zlog-latest-stable/
$ make
$ sudo make install
or
$ sudo make PREFIX=/usr/local/ install
PREFIX指明瞭安裝的路徑,安轉完以後爲了讓你的程序能找到zlog動態庫
-
$ sudo vi /etc/ld.so.conf
/usr/local/lib
$ sudo ldconfig
在你的程序運行以前,保證libzlog.so在系統的動態連接庫加載器能夠找到的目錄下。上面的命令適用於linux,別的系統本身想辦法。
- 除了通常的make之外,還能夠
-
$ make 32bit # 32bit version on 64bit machine, libc6-dev-i386 is needed
$ make noopt # without gcc optimization
$ make doc # lyx and hevea is needed
$ make test # test code, which is also good example for zlog
- makefile是用GNU make的格式寫的,因此在你的平臺上須要預裝gnu make和gcc。或者,手工修改一個本身平臺的makefile也行。
3.2 應用程序調用和連接zlog
應用程序使用zlog很簡單,只要在C文件裏面加一行。
- #include "zlog.h"
連接zlog須要pthread庫,命令是:
-
$ cc -c -o app.o app.c -I/usr/local/include
# -I[where zlog.h is put]
$ cc -o app app.o -L/usr/local/lib -lzlog -lpthread
# -L[where libzlog.so is put]
3.3 Hello World 代碼
這些代碼在$(top_builddir)/test/test_hello.c, test_hello.conf
- 寫一個C文件:
-
$ vi test_hello.c
#include <stdio.h>
#include "zlog.h"
int main(int argc, char** argv)
{
-
int rc;
zlog_category_t *c;
rc = zlog_init("test_hello.conf");
if (rc) {
-
printf("init failed\n");
return -1;
}
c = zlog_get_category("my_cat");
if (!c) {
printf("get cat fail\n");
-
zlog_fini();
return -2;
}
zlog_info(c, "hello, zlog");
zlog_fini();
return 0;
-
printf("init failed\n");
}
-
int rc;
-
$ vi test_hello.c
- 寫一個配置文件,放在和test_hello.c一樣的目錄下:
-
$ vi test_hello.conf
[formats]
simple = "%m%n"
[rules]
my_cat.DEBUG >stdout; simple
-
$ vi test_hello.conf
- 編譯、而後運行!
-
$ cc -c -o test_hello.o test_hello.c -I/usr/local/include
$ cc -o test_hello test_hello.o -L/usr/local/lib -lzlog
$ ./test_hello
hello, zlog
-
$ cc -c -o test_hello.o test_hello.c -I/usr/local/include
3.4 更簡單的Hello World
這個例子在$(top_builddir)/test/test_default.c, test_default.conf. 源代碼是:
-
#include <stdio.h>
#include "zlog.h"
int main(int argc, char** argv)
{
-
int rc;
rc = dzlog_init("test_default.conf", "my_cat");
if (rc) {
-
printf("init failed\n");
return -1;
}
dzlog_info("hello, zlog");
zlog_fini();
return 0;
-
printf("init failed\n");
}
-
int rc;
配置文件是test_default.conf,和test_hello.conf如出一轍,最後執行程序的輸出也同樣。區別在於這裏用了dzlog API,內含一個默認的zlog_category_t。詳見6.5。
Chapter 4 Syslog 模型
4.1 分類(Category)、規則(Rule)和格式(Format)
zlog有3個重要的概念:分類(Category)、規則(Rule)和格式(Format)。
分類(Category)用於區分不一樣的輸入。代碼中的分類變量的名字是一個字符串,在一個程序裏面能夠經過獲取不一樣的分類名的category用來後面輸出不一樣分類的日誌,用於不一樣的目的。
格式(Format)是用來描述輸出日誌的格式,好比是否有帶有時間戳,是否包含文件位置信息等,上面的例子裏面的格式simple就是簡單的用戶輸入的信息+換行符。
規則(Rule)則是把分類、級別、輸出文件、格式組合起來,決定一條代碼中的日誌是否輸出,輸出到哪裏,以什麼格式輸出。
因此,當程序執行下面的語句的時候
-
zlog_category_t *c;
c = zlog_get_category("my_cat");
zlog_info(c, "hello, zlog");
zlog會找到c的名字是"my_cat",對應的配置文件中的規則是
-
[rules]
my_cat.DEBUG >stdout; simple
而後庫會檢查,目前這條日誌的級別是否符合規則中的級別來決定是否輸出。由於INFO>=DEBUG,因此這條日誌會被輸出。而且根據這條規則,會被輸出到stdout(標準輸出) ,輸出的格式是simple,在配置文件中定義是
-
[formats]
simple = "%m%n"
最後在屏幕上打印
- hello, zlog
這就是整個過程。用戶要作就是寫本身的信息。日誌往哪裏輸出,以什麼格式輸出,都是庫和配置文件來完成的。
4.2 syslog模型和log4j模型的區別
好,那麼目前這個模型和syslog有什麼關係呢?至今爲止,這個模型仍是比較像log4j。log4j的模型裏面有logger, appender和layout。區別在於,在log4j裏面,代碼中的logger和配置中的logger是一一對應的,而且一個logger有惟一的級別。一對一關係是log4j, log4cxx, log4cpp, log4cplus, log4net的惟一選擇。
但這種模型是不靈活的,他們發明了過濾器(filters)來彌補,但這隻能把事情弄得更加混亂。因此讓咱們把目光轉回syslog的模型,這是一個設計的很簡易正確的模型。
繼續上一節的例子,若是在zlog的配置文件中有這麼2行規則:
-
[rules]
my_cat.DEBUG >stdout; simple
my_cat.INFO >stdout;
而後,一行代碼會產生兩行輸出:
-
hello, zlog
2012-05-29 10:41:36 INFO [11288:test_hello.c:41] hello, zlog
如今一個代碼中的分類對應配置文件中的兩條規則。log4j的用戶可能會說:"這很好,可是隻要在log4j裏面放兩個appender也能作的同樣。"因此繼續看下一個例子:
-
[rules]
my_cat.WARN "/var/log/aa.log"
my_cat.DEBUG "/var/log/bb.log"
代碼是:
-
zlog_info(c, "info, zlog");
zlog_debug(c, "debug, zlog");
最後,在aa.log中只有一條日誌
- 2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog
但在bb.log裏面有兩條
-
2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog
2012-05-29 10:41:36 DEBUG [11288:test_hello.c:42] debug, zlog
從這個例子能看出來區別。log4j沒法輕易的作到這一點。在zlog裏面,一個分類能夠對應多個規則,每一個規則有本身的級別、輸出和格式。這就讓用戶能按照需求過濾、多渠道輸出本身的全部日誌。
4.3 擴展syslog模型
因此到如今你能看出來zlog的模型更像syslog的模型。不幸的是,在syslog裏面,設施(facility)是個int型,並且必須從系統定義的那幾種裏面選擇。zlog走的遠一點,用一個字符串來標識分類。
syslog有一個通配符"*",匹配全部的設施(facility)。zlog裏面也同樣,"*"匹配全部分類。這提供了一個很方便的辦法來重定向你的系統中各個組件的錯誤。只要這麼寫:
-
[rules]
*.error "/var/log/error.log"
zlog強大而獨有的特性是上下級分類匹配。若是你的分類是這樣的:
- c = zlog_get_category("my_cat");
而後配置文件是這樣的
-
[rules]
my_cat.* "/var/log/my_cat.log"
my_.NOTICE "/var/log/my.log"
這兩條規則都匹配c分類"my_cat"。通配符"_" 表示上級分類。 "my_"是"my_cat"和"my_dog"的上級分類。還有一個通配符是"!",詳見5.5.2
Chapter 5 配置文件
大部分的zlog的行爲取決於配置文件:把日誌打到哪裏去,用什麼格式,怎麼轉檔。配置文件是zlog的黑話,我儘可能把這個黑話設計的簡單明瞭。這是個配置文件例子:
-
# comments
[global]
strict init = true
buffer min = 1024
buffer max = 2MB
rotate lock file = /tmp/zlog.lock
default format = "%d.%us %-6V (%c:%F:%L) - %m%n"
file perms = 600
[levels]
TRACE = 10
CRIT = 130, LOG_CRIT
[formats]
simple = "%m%n"
normal = "%d %m%n"
[rules]
default.* >stdout; simple
*.* "%12.2E(HOME)/log/%c.log", 1MB*12; simple
my_.INFO >stderr;
my_cat.!ERROR "/var/log/aa.log"
my_dog.=DEBUG >syslog, LOG_LOCAL0; simple
my_mice.* $user_define;
有關單位:當設置內存大小或者大數字時,能夠設置1k 5GB 4M這樣的單位:
-
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 byte
單位是大小寫不敏感的,因此1GB 1Gb 1gB是等效的。
5.1 全局參數
全局參數以[global]開頭。[]表明一個節的開始,四個小節的順序不能變,依次爲global-levels-formats-rules。這一節能夠忽略不寫。語法爲
- (key) = (value)
- strict init
若是"strict init"是true,zlog_init()將會嚴格檢查全部的格式和規則,任何錯誤都會致使zlog_init() 失敗而且返回-1。當"strict init"是false的時候,zlog_init()會忽略錯誤的格式和規則。 這個參數默認爲true。
- reload conf period
這個選項讓zlog能在一段時間間隔後自動重載配置文件。重載的間隔以每進程寫日誌的次數來定義。當寫日誌次數到了必定值後,內部將會調用zlog_reload()進行重載。每次zlog_reload()或者zlog_init()以後從新計數累加。由於zlog_reload()是原子性的,重載失敗繼續用當前的配置信息,因此自動重載是安全的。默認值是0,自動重載是關閉的。
- buffer min
- buffer max
zlog在堆上爲每一個線程申請緩存。"buffer min"是單個緩存的最小值,zlog_init()的時候申請這個長度的內存。寫日誌的時候,若是單條日誌長度大於緩存,緩存會自動擴充,直到到"buffer max"。 單條日誌再長超過"buffer max"就會被截斷。若是 "buffer max" 是 0,意味着不限制緩存,每次擴充爲原先的2倍,直到這個進程用完全部內存爲止。緩存大小能夠加上 KB, MB 或 GB這些單位。默認來講"buffer min"是 1K , "buffer max" 是2MB。
- rotate lock file
這個選項指定了一個鎖文件,用來保證多進程狀況下日誌安全轉檔。zlog會在zlog_init()時候以讀寫權限打開這個文件。確認你執行程序的用戶有權限建立和讀寫這個文件。轉檔日誌的僞代碼是:
-
write(log_file, a_log)
if (log_file > 1M)
-
if (pthread_mutex_lock succ && fcntl_lock(lock_file) succ)
-
if (log_file > 1M) rotate(log_file);
fcntl_unlock(lock_file);
pthread_mutex_unlock;
-
if (log_file > 1M) rotate(log_file);
-
if (pthread_mutex_lock succ && fcntl_lock(lock_file) succ)
mutex_lock用於多線程, fcntl_lock用於多進程。fcntl_lock是POSIX建議鎖。詳見man 3 fcntl。這個鎖是全系統有效的。在某個進程意外死亡後,操做系統會釋放此進程持有的鎖。這就是我爲何用fcntl鎖來保證安全轉檔。進程須要對鎖文件有讀寫權限。
默認來講,rotate lock file = self。在這種狀況下,zlog不會建立任何鎖文件,用配置文件做爲鎖文件。fcntl是建議鎖,因此用戶能夠自由的修改存儲他們的配置文件。通常來講,單個日誌文件不會被不一樣操做系統用戶的進程轉檔,因此用配置文件做爲鎖文件是安全的。
若是你設置其餘路徑做爲鎖文件,例如/tmp/zlog.lock,zlog會在zlog_init()的時候建立這個文件。若是有多個操做系統用戶的進程須要轉檔同一個日誌文件,確認這個鎖文件對於多個用戶均可讀寫。默認值是/tmp/zlog.lock。
-
write(log_file, a_log)
- default format
這個參數是缺省的日誌格式,默認值爲:
- "%d %V [%p:%F:%L] %m%n"
這種格式產生的輸出相似這樣:
- 2012-02-14 17:03:12 INFO [3758:test_hello.c:39] hello, zlog
- file perms
這個指定了建立日誌文件的缺省訪問權限。必須注意的是最後的產生的日誌文件的權限爲"file perms"& ~umask。默認爲600,只容許當前用戶讀寫。
- fsync period
在每條規則寫了必定次數的日誌到文件後,zlog會調用fsync(3)來讓操做系統立刻把數據寫到硬盤。次數是每條規則單獨統計的,而且在zlog_reload()後會被清0。必須指出的是,在日誌文件名是動態生成或者被轉檔的狀況下,zlog不能保證把全部文件都搞定,zlog只fsync()那個時候剛剛write()的文件描述符。這提供了寫日誌速度和數據安全性之間的平衡。例子:
-
$ time ./test_press_zlog 1 10 100000
real 0m1.806s
user 0m3.060s
sys 0m0.270s
$ wc -l press.log
1000000 press.log
$ time ./test_press_zlog 1 10 100000 #fsync period = 1K
real 0m41.995s
user 0m7.920s
sys 0m0.990s
$ time ./test_press_zlog 1 10 100000 #fsync period = 10K
real 0m6.856s
user 0m4.360s
sys 0m0.550s
若是你極度在意安全而不是速度的話,用同步IO文件,見5.5.3。默認值是0,由操做系統來決定何時刷緩存到文件。
-
$ time ./test_press_zlog 1 10 100000
5.2 日誌等級自定義
這一節以[levels]開始。用於定義用戶本身的日誌等級,建議和用戶自定義的日誌記錄宏一塊兒使用。這一節能夠忽略不寫。語法爲:
- (level string) = (level int), (syslog level, optional)
(level int)必須在[1,253]這個範圍內,越大越重要。(syslog level)是可選的,若是不設默認爲LOG_DEBUG。
詳見7.3。
5.3 格式(Formats)
這一節以[formats]開始。用來定義日誌的格式。語法爲:
- (name) = "(actual formats)"
很好理解,(name)被後面的規則使用。(name)必須由數字和字母組成,下劃線"_"也算字母。(actual format)先後須要有雙引號。 (actual formats)能夠由轉換字符組成,見下一節。
5.4 轉換格式串
轉換格式串的設計是從C的printf函數裏面抄來的。一個轉換格式串由文本字符和轉換說明組成。
轉換格式串用在規則的日誌文件路徑和輸出格式(format)中。
你能夠把任意的文本字符放到轉換格式串裏面。
每一個轉換說明都是以百分號(%)打頭的,後面跟可選的寬度修飾符,最後以轉換字符結尾。轉換字符決定了輸出什麼數據,例如分類名、級別、時間日期、進程號等等。寬度修飾符控制了這個字段的最大最小寬度、左右對齊。下面是簡單的例子。
若是轉換格式串是:
- "%d(%m-%d %T) %-5V [%p:%F:%L] %m%n".
源代碼中的寫日誌語句是:
- zlog_info(c, "hello, zlog");
將會輸出:
- 02-14 17:17:42 INFO [4935:test_hello.c:39] hello, zlog
能夠注意到,在文本字符和轉換說明之間沒有顯式的分隔符。zlog解析的時候知道哪裏是轉換說明的開頭和結尾。在這個例子裏面%-5p這個轉換說明決定了日誌級別要被左對齊,佔5個字符寬。
5.4.1 轉換字符
能夠被辨認的轉換字符是
字符 效果 例子 %c 分類名 aa_bb %d() 打日誌的時間。這個後面要跟一對小括號()內含說明具體的日期格式。就像%d(%F)或者%d(%m-%d %T)。若是不跟小括號,默認是%d(%F %T)。括號內的格式和 strftime(2)的格式一致。詳見 5.4.3 %d(%F) 2011-12-01%d(%m-%d %T) 12-01 17:17:42
%d(%T) 17:17:42.035
%d 2012-02-14 17:03:12
%d()
%E() 獲取環境變量的值 %E(USER) simpson %ms 毫秒,3位數字字符串取自gettimeofday(2)
013 %us 微秒,6位數字字符串取自gettimeofday(2)
002323 %F 源代碼文件名,來源於__FILE__宏。在某些編譯器下 __FILE__是絕對路徑。用%f來去掉目錄只保留文件名,或者編譯器有選項能夠調節 test_hello.c或者在某些編譯器下
/home/zlog/src/test/test_hello.c
%f 源代碼文件名,輸出$F最後一個’/’後面的部分。固然這會有必定的性能損失 test_hello.c %H 主機名,來源於 gethostname(2) zlog-dev %L 源代碼行數,來源於__LINE__宏 135 %m 用戶日誌,用戶從zlog函數輸入的日誌。 hello, zlog %M MDC (mapped diagnostic context),每一個線程一張鍵值對錶,輸出鍵相對應的值。後面必需跟跟一對小括號()內含鍵。例如 %M(clientNumber) ,clientNumbe是鍵。 詳見 7.1 %M(clientNumber) 12345 %n 換行符,目前還不支持windows換行符 \n %p 進程ID,來源於getpid() 2134 %U 調用函數名,來自於__func__(C99)或者__FUNCTION__(gcc),若是編譯器支持的話。 main %V 日誌級別,大寫 INFO %v 日誌級別,小寫 info %t 16進製表示的線程ID,來源於pthread_self()"0x%x",(unsigned int) pthread_t
0xba01e700 %T 至關於%t,不過是以長整型表示的"%lu", (unsigned long) pthread_t
140633234859776 %% 一個百分號 % %[其餘字符] 解析爲錯誤,zlog_init()將會失敗
5.4.2 寬度修飾符
通常來講數據按原樣輸出。不過,有了寬度修飾符,就可以控制最小字段寬度、最大字段寬度和左右對齊。固然這要付出必定的性能代價。
可選的寬度修飾符放在百分號和轉換字符之間。
第一個可選的寬度修飾符是左對齊標識,減號(-)。而後是可選的最小字段寬度,這是一個十進制數字常量,表示最少有幾個字符會被輸出。若是數據原本沒有那麼多字符,將會填充空格(左對齊或者右對齊)直到最小字段寬度爲止。默認是填充在左邊也就是右對齊。固然你也可使用左對齊標誌,指定爲填充在右邊來左對齊。填充字符爲空格(space)。若是數據的寬度超過最小字段寬度,則按照數據的寬度輸出,永遠不會截斷數據。
這種行爲能夠用最大字段寬度來改變。最大字段寬度是放在一個句點號(.)後面的十進制數字常量。若是數據的寬度超過了最大字段寬度,則尾部多餘的字符(超過最大字段寬度的部分)將會被截去。 最大字段寬度是8,數據的寬度是10,則最後兩個字符會被丟棄。這種行爲和C的printf是同樣的,把後面的部分截斷。
下面是各類寬度修飾符和分類轉換字符配合一塊兒用的例子。
寬度修飾符
|
左對齊
|
最小字段寬度
|
最大字段寬度
|
附註
|
%20c
|
否
|
20
|
無
|
左補充空格,若是分類名小於20個字符長。
|
%-20c
|
是
|
20
|
無
|
右補充空格,若是分類名小於20個字符長。
|
%.30c
|
無
|
無
|
30
|
若是分類名大於30個字符長,取前30個字符,去掉後面的。
|
%20.30c
|
否
|
20
|
30
|
若是分類名小於20個字符長,左補充空格。若是在20-30之間,按照原樣輸出。若是大於30個字符長,取前30個字符,去掉後面的。
|
%-20.30c
|
是
|
20
|
30
|
若是分類名小於20個字符長,右補充空格。若是在20-30之間,按照原樣輸出。若是大於30個字符長,取前30個字符,去掉後面的。
|
5.4.3 時間字符
這裏是轉換字符d支持的時間字符。
全部字符都是由strftime(2)生成的,在個人linux操做系統上支持的是:
字符 效果 例子 %a 一星期中各天的縮寫名,根據locale顯示 Wed %A 一星期中各天的全名,根據locale顯示 Wednesday %b 縮寫的月份名,根據locale顯示 Mar %B 月份全名,根據locale顯示 March %c 當地時間和日期的全表示, 根據locale顯示 Thu Feb 16 14:16:35 2012 %C 世紀 (年/100),2位的數字(SU) 20 %d 一個月中的某一天 (01-31) 06 %D 至關於%m/%d/%y. (呃,美國人專用,美國人要知道在別的國家%d/%m/%y 纔是主流。也就是說在國際環境下這個格式容易形成誤解,要少用) (SU) 02/16/12 %e 就像%d,一個月中的某一天,可是頭上的0被替換成空格(SU) 6 %F 至關於%Y-%m-%d (ISO 8601日期格式)(C99) 2012-02-16 %G The ISO 8601 week-based year (see NOTES) with century as a decimal number. The 4-digit year corre‐ sponding to the ISO week number (see %V). This has the same format and value as %Y, except that if the ISO week number belongs to the previous or next year, that year is used instead. (TZ)大意是採用%V定義的年,若是那年的前幾天不算新年的第一週,就算上一年
2012 %g 至關於%G,就是不帶世紀 (00-99). (TZ) 12 %h 至關於%b(SU) Feb %H 小時,24小時表示(00-23) 14 %I 小時,12小時表示(01-12) 02 %j 一年中的各天(001-366) 047 %k 小時,24小時表示( 0-23); 一位的前面爲空格 (可和%H比較) (TZ) 15 %l 小時,12小時表示( 0-12); 一位的前面爲空格 (可和%比較)(TZ) 3 %m 月份(01-12) 02 %M 分鐘(00-59) 11 %n 換行符 (SU) \n %p "AM" 或 "PM",根據當時的時間,根據locale顯示相應的值,例如"上午"、"下午" 。 中午是"PM",凌晨是"AM" PM %P 至關於%p不過是小寫,根據locale顯示相應的值 (GNU) pm %r 時間+後綴AM或PM。在POSIX locale下至關於%I:%M:%S %p. (SU) 03:11:54 PM %R 小時(24小時制):分鐘 (%H:%M) (SU) 若是要帶秒的,見%T 15:11 %s Epoch以來的秒數,也就是從1970-01-01 00:00:00 UTC. (TZ) 1329376487 %S 秒(00-60). (容許60是爲了閏秒) 54 %t 製表符tab(SU) %T 小時(24小時制):分鐘:秒 (%H:%M:%S) (SU) 15:14:47 %u 一週的天序號(1-7),週一是1,另見%w (SU) 4 %U 一年中的星期序號(00-53),週日是一週的開始,一年中第一個週日所在的周是第01周。另見%V和%W 07 %V ISO 8601星期序號(01-53),01周是第一個至少有4天在新年的周。另見%U 和%W(SU) 07 %w 一週的天序號(0-6),週日是0。另見%u 4 %W 一年中的星期序號(00-53),週一是一週的開始,一年中第一個週一所在的周是第01周。另見%V和%W 07 %x 當前locale下的偏好日期 02/16/12 %X 當前locale下的偏好時間 15:14:47 %y 不帶世紀數目的年份(00-99) 12 %Y 帶世紀數目的年份 2012 %z 當前時區相對於GMT時間的偏移量。採用RFC 822-conformant來計算(話說我也不知道是啥) (using "%a, %d %b %Y %H:%M:%S %z"). (GNU) +0800 %Z 時區名(若是有的話) CST %% 一個百分號 %
5.5 規則(Rules)
這一節以[rules]開頭。這個描述了日誌是怎麼被過濾、格式化以及被輸出的。這節能夠忽略不寫,不過這樣就沒有日誌輸出了,嘿嘿。語法是:
- (category).(level) (output), (options, optional); (format name, optional)
當zlog_init()被調用的時候,全部規則都會被讀到內存中。當zlog_get_category()被調用,規則就被被分配給分類(5.5.2)。在實際寫日誌的時候,例如zlog_info()被調用的時候,就會比較這個INFO和各條規則的等級,來決定這條日誌會不會經過這條規則輸出。當zlog_reload()被調用的時候,配置文件會被從新讀入,包括全部的規則,而且從新計算分類對應的規則。
5.5.1 級別匹配
zlog有6個默認的級別:"DEBUG", "INFO", "NOTICE", "WARN", "ERROR"和"FATAL"。就像其餘的日誌函數庫那樣, aa.DEBUG意味着任何大於等於DEBUG級別的日誌會被輸出。固然還有其餘的表達式。配置文件中的級別是大小寫不敏感的。
表達式 | 含義 |
* | 全部等級 |
aa.debug | 代碼內等級>=debug |
aa.=debug | 代碼內等級==debug |
aa.!debug | 代碼內等級!=debug |
用戶能夠自定義等級,詳見7.3。
5.5.2 分類匹配
分類必須由數字和字母組成,下劃線"_"也算字母。
總結
|
配置文件規則分類
|
匹配的代碼分類
|
不匹配的代碼分類
|
*匹配全部
|
*.*
|
aa, aa_bb, aa_cc, xx, yy ...
|
NONE
|
以_結尾的分類匹配本級及下級分類
|
aa_.*
|
aa, aa_bb, aa_cc, aa_bb_cc
|
xx, yy
|
不以_結尾的精確匹配分類名
|
aa.*
|
aa
|
aa_bb, aa_cc, aa_bb_cc
|
!匹配那些沒有找到規則的分類
|
!.*
|
xx
|
aa(as it matches rules above)
|
5.5.3 輸出動做
目前zlog支持若干種輸出,語法是:
[輸出], [附加選項, 可選]; [format(格式)名, 可選]
動做 | 輸出字段 |
附加選項
|
標準輸出 | >stdout |
無心義
|
標準錯誤輸出 | >stderr |
無心義
|
輸出到syslog | >syslog |
syslog設施(facilitiy):
LOG_USER(default), LOG_LOCAL[0-7] 必填 |
管道輸出 | |cat |
無心義
|
文件 | "文件路徑" |
文件轉檔,詳見
5.6
10M * 3 ~ "press.#r.log" |
同步IO文件 | -"文件路徑" | |
用戶自定義輸出 | $name |
"path" 動態或者靜態的用於record輸出
|
- stdout, stderr, syslog
如表格描述,其中只有sylog的附加選項是有意義並必須寫的。
值得注意的是,zlog在寫日誌的時候會用這樣的語句
- write(STDOUT_FILENO, zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf))
而若是你的程序是個守護進程,在啓動的時候把STDOUT_FILENO,也就是1的文件描述符關掉的話,會發生什麼結果呢?
日誌會被寫到新的1的文件描述符所表明的文件裏面!我收到過郵件,說zlog把日誌寫到本身的配置文件裏面去了!
因此,千萬不要在守護進程的規則裏面加上>stdout或>stderr。這會產生不可預料的結果……若是必定要輸出到終端,用"/dev/tty"代替。
- 管道輸出
- *.* | /usr/bin/cronolog /www/logs/example_%Y%m%d.log ; normal
這是一個將zlog的輸出到管道後接cronolog的例子。實現的原理很簡單,在zlog_init的時候調用popen("/usr/bin/cronolog /www/logs/example_%Y%m%d.log", "w"),後面往這個文件描述符裏面寫指定格式的日誌。使用cronolog來生成按天分割的日誌效率比zlog本身的動態路徑的效率要高,由於經過管道,無須每次打開關閉動態路徑的文件描述符。
-
[rules]
*.* "press%d(%Y%m%d).log"
$ time ./test_press_zlog 1 10 100000
real 0m4.240s
user 0m2.500s
sys 0m5.460s
[rules]
*.* | /usr/bin/cronolog press%Y%m%d.log
$ time ./test_press_zlog 1 10 100000
real 0m1.911s
user 0m1.980s
sys 0m1.470s
不過,使用管道也是有限制的:
- POSIX.1-2001保證讀寫不大於PIPE_BUF大小的內容是原子的。linux上PIPE_BUF爲4096。
- 單條日誌的長度超過PIPE_BUF的時候而且有多個有父子關係的進程寫經過zlog寫同一個管道,也就是在zlog_init以後fork多個子進程,此時只有一個cronolog的進程監聽一個管道描述符,日誌內容可能會交錯。
- 多個進程分別zlog_init,啓動多個cronolog進程,寫擁有同一個文件路徑的日誌文件,即便單條日誌長度不超過PIPE_BUF,也有可能致使日誌交錯,由於cronolog讀到的文件流是連續的,它不知道單條日誌的邊界在哪裏。
因此,總結一下,使用管道來輸出到單個日誌文件的狀況是:
- 單進程寫,單條日誌長度不限制。單進程內內的多線程寫日誌的原子性已經由zlog保證了。
- 有父子關係的多進程,單條日誌長度不能超過PIPE_BUF(4096)
- 無父子關係的多進程使用管道同時寫一個日誌,不管單條日誌長度是多少,都有可能致使日誌交錯。
zlog自己的直接文件輸出能保證即便是多進程,同時調用zlog寫一個日誌文件也不會產生交錯,見下。
- 文件
- 文件路徑
能夠是相對路徑或者絕對路徑,被雙引號"包含。轉換格式串能夠用在文件路徑上。例如文件路徑是 "%E(HOME)/log/out.log",環境變量$HOME是/home/harry,那最後的輸出文件是/home/harry/log/output.log。轉換格式串詳見 5.4。
zlog的文件功能極爲強大,例如
- 輸出到命名管道(FIFO),必須在調用前由mkfifo(1)建立
- *.* "/tmp/pipefile"
- 輸出到NULL,也就是不輸出
- *.* "/dev/null"
- 在任何狀況下輸出到終端
- *.* "/dev/tty"
- 每線程一個日誌,在程序運行的目錄下
- *.* "%T.log"
- 輸出到有進程號區分的日誌,天天,在$HOME/log目錄,每1GB轉檔一次,保持5個日誌文件。
- *.* "%E(HOME)/log/aa.%p.%d(%F).log",1GB * 5
- aa_及下級分類,每一個分類一個日誌
- aa_.* "/var/log/%c.log"
- 輸出到命名管道(FIFO),必須在調用前由mkfifo(1)建立
- 文件轉檔
控制文件的大小和個數。zlog根據這個字段來轉檔,當日志文件太大的時候。例如
- "%E(HOME)/log/out.log", 1M * 3 ~ "%E(HOME)/log/out.log.#r"
這三個參數都不是必填項,zlog的轉檔功能詳見5.6
- 同步IO文件
在文件路徑前加上一個"-"就打開了同步IO選項。在打開文件(open)的時候,會以O_SYNC選項打開,這時候每次寫日誌操做都會等操做系統把數據寫到硬盤後才返回。這個選項極爲耗時:
-
$ time ./test_press_zlog 100 1000
real 0m0.732s
user 0m1.030s
sys 0m1.080s
$ time ./test_press_zlog 100 1000 # synchronous I/O open
real 0m20.646s
user 0m2.570s
sys 0m6.950s
-
$ time ./test_press_zlog 100 1000
- 文件路徑
- 格式名
是可選的,若是不寫,用全局配置裏面的默認格式:
-
[global]
default format = "%d(%F %T) %V [%p:%F:%L] %m%n"
-
[global]
- 用戶自定義輸出詳見7.4
5.6 文件轉檔
爲何須要將日誌文件轉檔?我已經在實際的運行環境中不止一次的看到過,由於日誌文件過大,致使系統硬盤被撐爆,或者單個日誌文件過大而即便用grep也要花費不少時間來尋找匹配的日誌。對於日誌轉檔,我總結了以下幾種範式:
- 按固定時間段來切分日誌。
例如,天天生成一個日誌
-
aa.2012-08-02.log
aa.2012-08-03.log
aa.2012-08-04.log
這種日誌適合的場景是,管理員大概知道天天生成的日誌量,而後但願在n個月以後能精確的找出某天的全部日誌。這種日誌切分最好由日誌庫來完成,其次的方法是用cronosplit這種軟件來分析日誌內容的時間字符串來進行後期的切分,較差的辦法是用crontab+logrotate或mv來按期移動(但這並不精確,會形成若干條當天的日誌被放到上一天的文件裏面去)。
在zlog裏面,這種需求不須要用日誌轉檔功能來完成,簡單的在日誌文件名裏面設置時間日期字符串就能解決問題:
- *.* "aa.%d(%F).log"
或者用cronolog來完成,速度會更快一點
- *.* | cronolog aa.%F.log
-
aa.2012-08-02.log
- 按照日誌大小切分
多用於開發環境,適合的場景是,程序在短期內生成大量的日誌,而用編輯器vi,ue等能快速打開的日誌大小是有限的,或者大的日誌打開來極慢。一樣的,這種日誌的切分能夠在過後用split等工具來完成,但對於開發而言會增長步驟,因此最好也是由日誌庫來完成。值得一提的是存檔有兩種模式,nlog裏面稱之爲Sequence和Rolling,在Sequence狀況下
-
aa.log (new)
aa.log.2 (less new)
aa.log.1
aa.log.0 (old)
而在Rolling的狀況下
-
aa.log (new)
aa.log.0 (less new)
aa.log.1
aa.log.2 (old)
很難說哪一種更加符合人的直覺。
若是隻有若干個最新的文件是有意義的,須要日誌庫來作主動的刪除舊的工做。由外部程序是很難斷定哪些日誌是舊的。
最簡單的zlog的轉檔配置爲
- *.* "aa.log", 10MB
這個配置是Rolling的狀況,每次aa.log超過10MB的時候,會作這樣的重命名
-
aa.log.2 -> aa.log.3
aa.log.1 -> aa.log.2
aa.log.0 -> aa.log.1
aa.log -> aa.log.0
上面的配置能夠寫的更加羅嗦一點
- *.* "aa.log", 10MB * 0 ~ "aa.log.#r"
逗號後第一個參數表示文件達到多大後開始進行轉檔。
第二個參數表示保留多少個存檔文件(0表明不刪除任何存檔文件)。
第三個參數表示轉檔的文件名,其中#r表示存檔文件的序號,r是rolling的縮寫。還能夠放#s,是sequence的縮寫。轉檔文件名必須包含#r或者#s。
-
aa.log (new)
- 按照日誌大小切分,但同時加上時間標籤
-
aa.log
aa.log-20070305.00.log
aa.log-20070501.00.log
aa.log-20070501.01.log
aa.log-20071008.00.log
這種狀況適合於程序自己的日誌通常不是很受關注,可是又在某一天想要找出來看的狀況。固然,在這種狀況下,萬一在20070501這一天日誌的量超過了指定值,例如100MB,就又要退回到第二種狀態,在文件名中加後綴。
zlog對應的配置是
- *.* "aa.log", 100MB ~ "aa-%d(%Y%m%d).#2s.log"
每到100MB的時候轉檔,轉檔文件名也支持轉換字符,能夠把轉檔當時的時間串做爲轉檔文件名的一部分。#2s的意思是序號的長度最少爲2位,從00開始編號,Sequence轉檔。這是zlog對轉檔最複雜的支持了!
-
aa.log
- 壓縮、移動、刪除舊的日誌
首先,壓縮不該該由日誌庫來完成,由於壓縮消耗時間和CPU。日誌庫的任務是配合壓縮。
對於第一種和第三種,管理較爲簡單,只要符合某些文件名規則或修改日期的,能夠用shell腳本+crontab輕易的壓縮、移動和刪除。
對於第二種,其實不是很是須要壓縮,只須要刪除就能夠了。
若是必定須要轉檔的同時進行壓縮,只有logrotate能幹這活兒,畢竟他是獨立的程序,能在轉檔同時搞壓縮,不會有混淆的問題。
- zlog對外部轉檔工具,例如logrotate的支持
zlog的轉檔功能已經極爲強大,固然也有幾種狀況是zlog沒法處理的,例如按時間條件進行轉檔,轉檔先後調用一些自制的shell腳本……這會把zlog的配置和表達弄得過於複雜而缺少美感。
這時候你也許喜歡用一些外部轉檔工具,例如logrotate來完成工做。問題是,在linux操做系統下,轉檔工具重命名日誌文件名後,應用進程仍是往原來的文件描述符寫日誌,沒辦法從新打開日誌文件寫新的日誌。標準的作法是給應用程序一個信號,讓他從新打開日誌文件,對於syslogd是
- kill -SIGHUP ‘cat /var/run/syslogd.pid‘
對於zlog,由於是個函數庫,不適合接受信號。zlog提供了函數接口zlog_reload(),這個函數會重載配置文件,從新打開全部的日誌文件。應用程序在logrotate的信號,或者其餘途徑,例如客戶端的命令後,能夠調用這個函數,來從新打開全部的日誌文件。
5.7 配置文件工具
-
$ zlog-chk-conf -h
Useage: zlog-chk-conf [conf files]...
-q, suppress non-error message
-h, show help message
zlog-chk-conf 嘗試讀取配置文件,檢查語法,而後往屏幕上輸出這些配置文件是否正確。我建議每次建立或者改動一個配置文件以後都用一下這個工具。輸出多是這樣:
-
$ ./zlog-chk-conf zlog.conf
03-08 15:35:44 ERROR (10595:rule.c:391) sscanf [aaa] fail, category or level is null
03-08 15:35:44 ERROR (10595:conf.c:155) zlog_rule_new fail [aaa]
03-08 15:35:44 ERROR (10595:conf.c:258) parse configure file[zlog.conf] line[126] fail
03-08 15:35:44 ERROR (10595:conf.c:306) zlog_conf_read_config fail
03-08 15:35:44 ERROR (10595:conf.c:366) zlog_conf_build fail
03-08 15:35:44 ERROR (10595:zlog.c:66) conf_file[zlog.conf], init conf fail
03-08 15:35:44 ERROR (10595:zlog.c:131) zlog_init_inner[zlog.conf] fail
---[zlog.conf] syntax error, see error message above
這個告訴你配置文件zlog.conf的126行,是錯的。第一行進一步告訴你[aaa]不是一條正確的規則。
zlog-chk-conf能夠同時分析多個配置文件,舉例:
-
$ zlog-chk-conf zlog.conf ylog.conf
--[zlog.conf] syntax right
--[ylog.conf] syntax right
Chapter 6 zlog接口(API)
zlog的全部函數都是線程安全的,使用的時候只須要
- #include "zlog.h"
6.1 初始化和清理
- 總覽
-
-
int zlog_init(const char *confpath);
int zlog_reload(const char *confpath);
void zlog_fini(void);
-
int zlog_init(const char *confpath);
- 描述
-
zlog_init()從配置文件confpath中讀取配置信息到內存。若是confpath爲NULL,會尋找環境變量ZLOG_CONF_PATH的值做爲配置文件名。若是環境變量ZLOG_CONF_PATH也沒有,全部日誌之內置格式寫到標準輸出上。每一個進程只有第一次調用zlog_init()是有效的,後面的多餘調用都會失敗並不作任何事情。
zlog_reload()從confpath重載配置,並根據這個配置文件來重計算內部的分類規則匹配、重建每一個線程的緩存、並設置原有的用戶自定義輸出函數。能夠在配置文件發生改變後調用這個函數。這個函數使用次數不限。若是confpath爲NULL,會重載上一次zlog_init()或者zlog_reload()使用的配置文件。若是zlog_reload()失敗,上一次的配置依然有效。因此zlog_reload()具備原子性。
zlog_fini()清理全部zlog API申請的內存,關閉它們打開的文件。使用次數不限。
- 返回值
-
若是成功,zlog_init()和zlog_reload()返回0。失敗的話,zlog_init()和zlog_reload()返回-1。詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.2 分類(Category)操做
- 總覽
-
-
typedef struct zlog_category_s zlog_category_t;
zlog_category_t *zlog_get_category(const char *cname);
-
typedef struct zlog_category_s zlog_category_t;
- 描述
-
zlog_get_category()從zlog的全局分類表裏面找到分類,用於之後輸出日誌。若是沒有的話,就建一個。而後它會遍歷全部的規則,尋找和cname匹配的規則並綁定。
配置文件規則中的分類名匹配cname的規律描述以下:
- * 匹配任意cname。
- 如下劃線_結尾的分類名同時匹配本級分類和下級分類。例如aa_匹配aa, aa_, aa_bb, aa_bb_cc這幾個cname。
- 不如下劃線_結尾的分類名精確匹配cname。例如aa_bb匹配aa_bb這個cname。
- ! 匹配目前尚未規則的cname。
每一個zlog_category_t *對應的規則,在zlog_reload()的時候會被自動從新計算。不用擔憂內存釋放,zlog_fini() 最後會清理一切。
- 返回值
-
若是成功,返回zlog_category_t的指針。若是失敗,返回NULL。詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.3 寫日誌函數及宏
- 總覽
-
-
void zlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, ...);
void vzlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, va_list args);
void hzlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const void *buf, size_t buflen);
-
void zlog(zlog_category_t * category,
- 描述
-
這3個函數是實際寫日誌的函數,輸入的數據對應於配置文件中的%m。category來自於調用zlog_get_category()。
zlog()和vzlog()根據format輸出,就像printf(3)和vprintf(3)。
vzlog()至關於zlog(),只是它用一個va_list類型的參數args,而不是一堆類型不一樣的參數。vzlog() 內部使用了 va_copy 宏,args的內容在vzlog()後保持不變,能夠參考stdarg(3)。
hzlog()有點不同,它產生下面這樣的輸出,長度爲buf_len的內存buf以16進制的形式表示出來。
-
hex_buf_len=[5365]
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0000000001 23 21 20 2f 62 69 6e 2f 62 61 73 68 0a 0a 23 20 #! /bin/bash..#
0000000002 74 65 73 74 5f 68 65 78 20 2d 20 74 65 6d 70 6f test_hex - tempo
0000000003 72 61 72 79 20 77 72 61 70 70 65 72 20 73 63 72 rary wrapper scr
參數file和line填寫爲__FILE__和__LINE__這兩個宏。這兩個宏標識日誌是在哪裏發生的。參數func 填寫爲__func__或者__FUNCTION__,若是編譯器支持的話,若是不支持,就填寫爲"<unkown>"。
level是一個整數,應該是在下面幾個裏面取值。
-
typedef enum {
-
ZLOG_LEVEL_DEBUG = 20,
ZLOG_LEVEL_INFO = 40,
ZLOG_LEVEL_NOTICE = 60,
ZLOG_LEVEL_WARN = 80,
ZLOG_LEVEL_ERROR = 100,
ZLOG_LEVEL_FATAL = 120
} zlog_level;
-
ZLOG_LEVEL_DEBUG = 20,
每一個函數都有對應的宏,簡單使用。例如:
-
#define zlog_fatal(cat, format, args...) \
zlog(cat, __FILE__, sizeof(__FILE__)-1, \
__func__, sizeof(__func__)-1, __LINE__, \
ZLOG_LEVEL_FATAL, format, ##args)
全部的宏列表:
-
/* zlog macros */
zlog_fatal(cat, format, ...)
zlog_error(cat, format, ...)
zlog_warn(cat, format, ...)
zlog_notice(cat, format, ...)
zlog_info(cat, format, ...)
zlog_debug(cat, format, ...)
/* vzlog macros */
vzlog_fatal(cat, format, args)
vzlog_error(cat, format, args)
vzlog_warn(cat, format, args)
vzlog_notice(cat, format, args)
vzlog_info(cat, format, args)
vzlog_debug(cat, format, args)
/* hzlog macros */
hzlog_fatal(cat, buf, buf_len)
hzlog_error(cat, buf, buf_len)
hzlog_warn(cat, buf, buf_len)
hzlog_notice(cat, buf, buf_len)
hzlog_info(cat, buf, buf_len)
hzlog_debug(cat, buf, buf_len)
-
hex_buf_len=[5365]
- 返回值
-
這些函數不返回。若是有錯誤發生,詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.4 MDC操做
- 總覽
-
-
int zlog_put_mdc(const char *key, const char *value);
char *zlog_get_mdc(const char *key);
void zlog_remove_mdc(const char *key);
void zlog_clean_mdc(void);
-
int zlog_put_mdc(const char *key, const char *value);
- 描述
-
MDC(Mapped Diagnostic Context)是一個每線程擁有的鍵-值表,因此和分類沒什麼關係。
key和value是字符串,長度不能超過MAXLEN_PATH(1024)。若是超過MAXLEN_PATH(1024)的話,會被截斷。
記住這個表是和線程綁定的,每一個線程有本身的表,因此在一個線程內的調用不會影響其餘線程。
- 返回值
-
zlog_put_mdc()成功返回0,失敗返回-1。zlog_get_mdc()成功返回value的指針,失敗或者沒有相應的key返回NULL。若是有錯誤發生,詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.5 dzlog接口
- 總覽
-
-
int dzlog_init(const char *confpath, const char *cname);
int dzlog_set_category(const char *cname);
void dzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, ...);
void vdzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, va_list args);
void hdzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const void *buf, size_t buflen);
-
int dzlog_init(const char *confpath, const char *cname);
- 描述
-
dzlog是忽略分類(zlog_category_t)的一組簡單zlog接口。它採用內置的一個默認分類,這個分類置於鎖的保護下。這些接口也是線程安全的。忽略了分類,意味着用戶不須要操心建立、存儲、傳輸zlog_category_t類型的變量。固然也能夠在用dzlog接口的同時用通常的zlog接口函數,這樣會更爽。
dzlog_init()和zlog_init()同樣作初始化,就是多須要一個默認分類名cname的參數。zlog_reload()、 zlog_fini() 能夠和之前同樣使用,用來刷新配置,或者清理。
dzlog_set_category()是用來改變默認分類用的。上一個分類會被替換成新的。一樣不用擔憂內存釋放的問題,zlog_fini()最後會清理。
dzlog的宏也定義在zlog.h裏面。更簡單的寫法。
-
dzlog_fatal(format, ...)
dzlog_error(format, ...)
dzlog_warn(format, ...)
dzlog_notice(format, ...)
dzlog_info(format, ...)
dezlog_debug(format, ...)
vdzlog_fatal(format, args)
vdzlog_error(format, args)
vdzlog_warn(format, args)
vdzlog_notice(format, args)
vdzlog_info(format, args)
vdzlog_debug(format, args)
hdzlog_fatal(buf, buf_len)
hdzlog_error(buf, buf_len)
hdzlog_warn(buf, buf_len)
hdzlog_noticebuf, buf_len)
hdzlog_info(buf, buf_len)
hdzlog_debug(buf, buf_len)
-
dzlog_fatal(format, ...)
- 返回值
-
成功狀況下dzlog_init()和dzlog_set_category()返回0。失敗狀況下dzlog_init()和 dzlog_set_category()返回-1。詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.6 用戶自定義輸出
- 總覽
-
-
typedef struct zlog_msg_s {
char *buf;
size_t len;
char *path;
} zlog_msg_t;
typedef int (*zlog_record_fn)(zlog_msg_t *msg);
int zlog_set_record(const char *rname, zlog_record_fn record);
-
typedef struct zlog_msg_s {
- 描述
-
zlog容許用戶自定義輸出函數。輸出函數須要綁定到某條特殊的規則上。這種規則的例子是:
- *.* $name, "record path %c %d"; simple
zlog_set_record()作綁定動做。規則中輸出段有$name的,會被用來作用戶自定義輸出。輸出函數爲record。這個函數須要爲zlog_record_fn的格式。
zlog_msg_t結構的各個成員描述以下:
path來自規則的逗號後的字符串,這個字符串會被動態的解析,輸出當前的path,就像動態文件路徑同樣。
buf和len 是zlog格式化後的日誌信息和長度。
全部zlog_set_record()作的綁定在zlog_reload()使用後繼續有效。
- 返回值
-
成功狀況下zlog_set_record()返回0。失敗狀況下zlog_set_record()返回-1。詳細錯誤會被寫在由環境變量ZLOG_PROFILE_ERROR指定的錯誤日誌裏面。
6.7 調試和診斷
- 總覽
-
- void zlog_profile(void);
- 描述
-
環境變量ZLOG_PROFILE_ERROR指定zlog自己的錯誤日誌。
環境變量ZLOG_PROFILE_DEBUG指定zlog自己的調試日誌。
zlog_profile()打印全部內存中的配置信息到ZLOG_PROFILE_ERROR,在運行時。能夠把這個和配置文件比較,看看有沒有問題。
Chapter 7 高階使用
7.1 MDC
MDC是什麼?在log4j裏面解釋爲Mapped Diagnostic Context。聽起來是個很複雜的技術,其實MDC就是一個鍵-值對錶。一旦某次你設置了,後面庫能夠幫你自動打印出來,或者成爲文件名的一部分。讓咱們看一個例子,來自於$(top_builddir)/test/test_mdc.c.
-
$ cat test_mdc.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include "zlog.h"
int main(int argc, char** argv)
{
-
int rc;
zlog_category_t *zc;
rc = zlog_init("test_mdc.conf");
-
if (rc) {
printf("init failed\n");
return -1;
}
zc = zlog_get_category("my_cat");
if (!zc) {
-
printf("get cat fail\n");
zlog_fini();
return -2;
}
zlog_info(zc, "1.hello, zlog");
zlog_put_mdc("myname", "Zhang");
zlog_info(zc, "2.hello, zlog");
zlog_put_mdc("myname", "Li");
zlog_info(zc, "3.hello, zlog");
zlog_fini();
return 0;
-
if (rc) {
}
-
int rc;
配置文件
-
$ cat test_mdc.conf
[formats]
mdc_format= "%d(%F %X.%ms) %-6V (%c:%F:%L) [%M(myname)] - %m%n"
[rules]
*.* >stdout; mdc_format
輸出
-
$ ./test_mdc
2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:47) [] - 1.hello, zlog
2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:51) [Zhang] - 2.hello, zlog
2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:55) [Li] - 3.hello, zlog
你能夠看到zlog_put_mdc()在表裏面設置鍵"myname"對應值"Zhang",而後在配置文件裏面%M(myname)指出了在日誌的哪一個位置須要把值打出來。第二次,鍵"myname"的值被覆蓋寫成"Li",而後日誌裏面也有相應的變化。
MDC何時有用呢?每每在用戶須要在一樣的日誌行爲區分不一樣的業務數據的時候。好比說,c源代碼是
-
zlog_put_mdc("customer_name", get_customer_name_from_db() );
zlog_info("get in");
zlog_info("pick product");
zlog_info("pay");
zlog_info("get out");
在配置文件裏面是
- &format "%M(customer_name) %m%n"
當程序同時處理兩個用戶的時候,打出來的日誌多是
-
Zhang get in
Li get in
Zhang pick product
Zhang pay
Li pick product
Li pay
Zhang get out
Li get out
這樣,你就能夠用grep命令把這兩個用戶的日誌分開來了
-
$ grep Zhang aa.log > Zhang.log
$ grep Li aa.log >Li.log
或者,還有另一條路,一開始在文件名裏面作區分,看配置文件:
- *.* "mdc_%M(customer_name).log";
這就會產生3個日誌文件。
- mdc_.log mdc_Zhang.log mdc_Li.log
這是一條近路,若是用戶知道本身在幹什麼。
MDC是每一個線程獨有的,因此能夠把一些線程專有的變量設置進去。若是單單爲了區分線程,能夠用轉換字符裏面的%t來搞定。
7.2 診斷zlog自己
OK,至今爲止,我假定zlog庫自己是不出毛病的。zlog幫助用戶程序寫日誌,幫助程序員debug程序。可是若是zlog內部出錯了呢?怎麼知道錯在哪裏呢?其餘的程序能夠用日誌庫來debug,但日誌庫本身怎麼debug?答案很簡單,zlog有本身的日誌——診斷日誌。這個日誌一般是關閉的,能夠經過環境變量來打開。
-
$ export ZLOG_PROFILE_DEBUG=/tmp/zlog.debug.log
$ export ZLOG_PROFILE_ERROR=/tmp/zlog.error.log
診斷日誌只有兩個級別debug和error。設置好環境變量後. 再跑test_hello程序3.3,而後debug日誌爲
-
$ more zlog.debug.log
03-13 09:46:56 DEBUG (7503:zlog.c:115) ------zlog_init start, compile time[Mar 13 2012 11:28:56]------
03-13 09:46:56 DEBUG (7503:spec.c:825) spec:[0x7fdf96b7c010][%d(%F %T)][%F %T 29][]
03-13 09:46:56 DEBUG (7503:spec.c:825) spec:[0x7fdf96b52010][ ][ 0][]
......
03-13 09:52:40 DEBUG (8139:zlog.c:291) ------zlog_fini end------
zlog.error.log日誌沒產生,由於沒有錯誤發生。
你能夠看出來,debug日誌展現了zlog是怎麼初始化還有清理的。不過在zlog_info()執行的時候沒有日誌打出來,這是爲了效率。
若是zlog庫有任何問題,都會打日誌到ZLOG_PROFILE_ERROR所指向的錯誤日誌。好比說,在zlog_info()上用一個錯誤的printf的語法:
- zlog_info(zc, "%l", 1);
而後編譯執行程序,ZLOG_PROFILE_ERROR的日誌會是
-
$ cat zlog.error.log
03-13 10:04:58 ERROR (10102:buf.c:189) vsnprintf fail, errno[0]
03-13 10:04:58 ERROR (10102:buf.c:191) nwrite[-1], size_left[1024], format[%l]
03-13 10:04:58 ERROR (10102:spec.c:329) zlog_buf_vprintf maybe fail or overflow
03-13 10:04:58 ERROR (10102:spec.c:467) a_spec->gen_buf fail
03-13 10:04:58 ERROR (10102:format.c:160) zlog_spec_gen_msg fail
03-13 10:04:58 ERROR (10102:rule.c:265) zlog_format_gen_msg fail
03-13 10:04:58 ERROR (10102:category.c:164) hzb_log_rule_output fail
03-13 10:04:58 ERROR (10102:zlog.c:632) zlog_output fail, srcfile[test_hello.c], srcline[41]
這樣,用戶就能知道爲啥期待的輸出沒有產生,而後搞定這個問題。
運行時診斷會帶來必定的性能損失。通常來講,我在生產環境把ZLOG_PROFILE_ERROR打開,ZLOG_PROFILE_DEBUG關閉。
還有另一個辦法來診斷zlog。咱們都知道,zlog_init()會把配置信息讀入內存。在整個寫日誌的過程當中,這塊內存保持不變。若是用戶程序由於某種緣由損壞了這塊內存,那麼就會形成問題。還有多是內存中的信息和配置文件的信息不匹配。因此我設計了一個函數,把內存的信息展示到ZLOG_PROFILE_ERROR指向的錯誤日誌。
代碼見$(top_builddir)/test/test_profile.c
-
$ cat test_profile.c
#include <stdio.h>
#include "zlog.h"
int main(int argc, char** argv)
{
-
int rc;
rc = dzlog_init("test_profile.conf", "my_cat");
if (rc) {
-
printf("init failed\n");
return -1;
}
dzlog_info("hello, zlog");
zlog_profile();
zlog_fini();
return 0;
-
printf("init failed\n");
}
-
int rc;
zlog_profile()就是這個函數。配置文件很簡單。
-
$ cat test_profile.conf
[formats]
simple = "%m%n"
[rules]
my_cat.* >stdout; simple
而後zlog.error.log會是
-
$ cat /tmp/zlog.error.log
06-01 11:21:26 WARN (7063:zlog.c:783) ------zlog_profile start------
06-01 11:21:26 WARN (7063:zlog.c:784) init_flag:[1]
06-01 11:21:26 WARN (7063:conf.c:75) -conf[0x2333010]-
06-01 11:21:26 WARN (7063:conf.c:76) --global--
06-01 11:21:26 WARN (7063:conf.c:77) ---file[test_profile.conf],mtime[2012-06-01 11:20:44]---
06-01 11:21:26 WARN (7063:conf.c:78) ---strict init[1]---
06-01 11:21:26 WARN (7063:conf.c:79) ---buffer min[1024]---
06-01 11:21:26 WARN (7063:conf.c:80) ---buffer max[2097152]---
06-01 11:21:26 WARN (7063:conf.c:82) ---default_format---
06-01 11:21:26 WARN (7063:format.c:48) ---format[0x235ef60][default = %d(%F %T) %V [%p:%F:%L] %m%n(0x233b810)]---
06-01 11:21:26 WARN (7063:conf.c:85) ---file perms[0600]---
06-01 11:21:26 WARN (7063:conf.c:87) ---rotate lock file[/tmp/zlog.lock]---
06-01 11:21:26 WARN (7063:rotater.c:48) --rotater[0x233b7d0][0x233b7d0,/tmp/zlog.lock,4]--
06-01 11:21:26 WARN (7063:level_list.c:37) --level_list[0x2335490]--
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x23355c0][0,*,*,1,6]---
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x23375e0][20,DEBUG,debug,5,7]---
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x2339600][40,INFO,info,4,6]---
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233b830][60,NOTICE,notice,6,5]---
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233d850][80,WARN,warn,4,4]---
06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233fc80][100,ERROR,error,5,3]---
7.3 用戶自定義等級
這裏我把用戶自定義等級的幾個步驟寫下來。
- 在配置文件中定義新的等級
-
$ cat $(top_builddir)/test/test_level.conf
[global]
default format = "%V %v %m%n"
[levels]
TRACE = 30, LOG_DEBUG
[rules]
my_cat.TRACE >stdout;
內置的默認等級是(這些不須要寫在配置文件裏面)
-
DEBUG = 20, LOG_DEBUG
INFO = 40, LOG_INFO
NOTICE = 60, LOG_NOTICE
WARN = 80, LOG_WARNING
ERROR = 100, LOG_ERR
FATAL = 120, LOG_ALERT
UNKNOWN = 254, LOG_ERR
這樣在zlog看來,一個整數(30)還有一個等級字符串(TRACE)表明了等級。這個整數必須位於[1,253]之間,其餘數字是非法的。數字越大表明越重要。如今TRACE比DEBUG重要(30>20),比INFO等級低(30<40)。在這樣的定義後,TRACE就能夠在下面的配置文件裏面用了。例如這句話:
- my_cat.TRACE >stdout;
意味着等級>=TRACE的,包括INFO, NOTICE, WARN, ERROR, FATAL會被寫到標準輸出。
格式裏面的轉換字符%V會產生等級字符串的大寫輸出,%v會產生小寫的等級字符串輸出。
另外,在等級的定義裏面,LOG_DEBUG是指當須要輸出到syslog的時候,自定義的TRACE等級會以LOG_DEBUG輸出到syslog。
-
$ cat $(top_builddir)/test/test_level.conf
- 在源代碼裏面直接用新的等級是這麼搞的
-
zlog(cat, __FILE__, sizeof(__FILE__)-1, \
__func__, sizeof(__func__)-1,__LINE__, \
30, "test %d", 1);
爲了簡單使用,建立一個.h頭文件
-
$ cat $(top_builddir)/test/test_level.h
#ifndef __test_level_h
#define __test_level_h
#include "zlog.h"
enum {
-
ZLOG_LEVEL_TRACE = 30,
/* must equals conf file setting */
};
#define zlog_trace(cat, format, ...) \
zlog(cat, __FILE__, sizeof(__FILE__)-1, \
__func__, sizeof(__func__)-1, __LINE__, \
ZLOG_LEVEL_TRACE, format, ## __VA_ARGS__)
#endif
-
ZLOG_LEVEL_TRACE = 30,
-
zlog(cat, __FILE__, sizeof(__FILE__)-1, \
- 這樣zlog_trace就能在.c文件裏面用了
-
$ cat $(top_builddir)/test/test_level.c
#include <stdio.h>
#include "test_level.h"
int main(int argc, char** argv)
{
-
int rc;
zlog_category_t *zc;
rc = zlog_init("test_level.conf");
if (rc) {
-
printf("init failed\n");
return -1;
}
zc = zlog_get_category("my_cat");
if (!zc) {
-
printf("get cat fail\n");
zlog_fini();
return -2;
}
zlog_trace(zc, "hello, zlog - trace");
zlog_debug(zc, "hello, zlog - debug");
zlog_info(zc, "hello, zlog - info");
zlog_fini();
return 0;
-
printf("init failed\n");
}
-
int rc;
-
$ cat $(top_builddir)/test/test_level.c
- 最後咱們能看到輸出
-
$ ./test_level
TRACE trace hello, zlog - trace
INFO info hello, zlog - info
正是咱們所期待的,配置文件只容許>=TRACE等級的日誌輸出到屏幕上。%V和%v也顯示了正確的結果。
-
$ ./test_level
7.4 用戶自定義輸出
用戶自定義輸出的意義是zlog放棄一些權力。zlog只負責動態生成單條日誌和文件路徑,但怎麼輸出、轉檔、清理等等工做由用戶按照本身的需求自行寫函數完成。寫完函數只要綁定到某個規則就能夠。這裏我把用戶自定義輸出的幾個步驟寫下來。
- 在配置文件裏面定義規則
-
$ cat test_record.conf
[formats]
simple = "%m%n"
[rules]
my_cat.* $myoutput, " mypath %c %d";simple
-
$ cat test_record.conf
- 綁定一個函數到$myoutput,並使用之
-
#include <stdio.h>
#include "zlog.h"
int output(zlog_msg_t *msg)
{
-
printf("[mystd]:[%s][%s][%ld]\n", msg->path, msg->buf, (long)msg->len);
return 0;
}
int main(int argc, char** argv)
{
-
int rc;
zlog_category_t *zc;
-
rc = zlog_init("test_record.conf");
if (rc) {
printf("init failed\n");
return -1;
}
zlog_set_record("myoutput", output);
zc = zlog_get_category("my_cat");
if (!zc) {
-
printf("get cat fail\n");
zlog_fini();
return -2;
}
zlog_info(zc, "hello, zlog");
zlog_fini();
return 0;
-
rc = zlog_init("test_record.conf");
}
-
printf("[mystd]:[%s][%s][%ld]\n", msg->path, msg->buf, (long)msg->len);
-
#include <stdio.h>
- 最後咱們發現用戶自定義輸出的函數能用了!
-
$ ./test_record
[mystd]:[ mypath my_cat 2012-07-19 12:23:08][hello, zlog
][12]
正如你所見,msglen是12,zlog生成的msg在最後有一個換行符。
-
$ ./test_record
- 用戶自定義輸出能夠幹不少神奇的事情,就像某個用戶(flw@newsmth.net)的需求
- 日誌文件名爲 foo.log
- 若是 foo.log 超過 100M,則生成一個新文件,其中包含的就是 foo.log 目前的內容 而 foo.log 則變爲空,從新開始增加
- 若是距離上次生成文件時已經超過 5 分鐘,則即便不到 100M,也應當生成一個新文件
- 新文件名稱能夠自定義,好比加上設備名做爲前綴、日期時間串做爲後綴
- 我但願這個新文件能夠被壓縮,以節省磁盤空間或者網絡帶寬。
希望他能順利寫出這個需求的代碼!在多進程或者多線程的狀況下!上帝保佑他!
Chapter 8 尾聲
好酒!全部人生問題的終極起源和終極答案。
荷馬.辛普森 |
- 1
- 星星之火,能夠燎原。
- 2
- 適用於 zlog v1.2.*
- 3
- 有問題,在github上 開個issue,或者寫郵件到 HardySimpson1984@gmail.com
This document was translated from LATEX by HEVEA .
======================== End