#LyX 2.0 created this file. For more info see http://www.lyx.org/ \lyxformat 413 \begin_document \begin_header \textclass extbook \begin_preamble \usepackage[cm-default]{fontspec} \usepackage{xunicode} \usepackage[utf8]{inputenc} \usepackage{xltxtra} \setmainfont[ItalicFont=KaiTi]{SimSun} %\setmainfont[ItalicFont=KaiTi]{YaHei Consolas Hybrid} %\setmainfont[BoldFont=KaiTi,ItalicFont=KaiTi]{SimSun} %\setsansfont[BoldFont=KaiTi]{KaiTi} %\setmonofont{KaiTi} %其他中文设置% \XeTeXlinebreaklocale “zh”%中文断行 \XeTeXlinebreakskip = 0pt plus 1pt minus 0.1pt%左右弹性间距 \usepackage{indentfirst}%段落首行缩进 \setlength{\parindent}{2em}%缩进两个字符 %编号语言、样式设置% \numberwithin{equation}{section}%设置公式按章节进行编号 \numberwithin{figure}{section}% 按章节编号 \end_preamble \use_default_options true \begin_modules theorems-ams eqs-within-sections figs-within-sections \end_modules \maintain_unincluded_children false \language chinese-simplified \language_package default \inputencoding utf8-plain \fontencoding global \font_roman default \font_sans default \font_typewriter default \font_default_family default \use_non_tex_fonts false \font_sc false \font_osf false \font_sf_scale 100 \font_tt_scale 100 \graphics default \default_output_format default \output_sync 0 \bibtex_command default \index_command default \paperfontsize 12 \spacing single \use_hyperref true \pdf_title "zlog Users Guide CN" \pdf_author "Hardy Simpson" \pdf_bookmarks true \pdf_bookmarksnumbered true \pdf_bookmarksopen true \pdf_bookmarksopenlevel 1 \pdf_breaklinks true \pdf_pdfborder true \pdf_colorlinks true \pdf_backref false \pdf_pdfusetitle true \pdf_quoted_options "unicode=false" \papersize a4paper \use_geometry true \use_amsmath 1 \use_esint 1 \use_mhchem 1 \use_mathdots 1 \cite_engine basic \use_bibtopic false \use_indices false \paperorientation portrait \suppress_date false \use_refstyle 0 \index 索引 \shortcut idx \color #008000 \end_index \rightmargin 2cm \secnumdepth 2 \tocdepth 2 \paragraph_separation indent \paragraph_indentation default \quotes_language english \papercolumns 1 \papersides 2 \paperpagestyle default \tracking_changes false \output_changes false \html_math_output 0 \html_css_as_file 0 \html_be_strict false \end_header \begin_body \begin_layout Title zlog \begin_inset Foot status collapsed \begin_layout Plain Layout 星星之火,可以燎原——毛泽东 \end_layout \end_inset 使用手册 \end_layout \begin_layout Author 难易 著 \begin_inset Foot status collapsed \begin_layout Plain Layout 适用于 zlog v1.2.* \end_layout \end_inset \begin_inset Foot status collapsed \begin_layout Plain Layout 有问题,在github上 \begin_inset CommandInset href LatexCommand href name "开个issue" target "https://github.com/HardySimpson/zlog/issues/new" type "mailto:" \end_inset ,或者写邮件到 \begin_inset CommandInset href LatexCommand href name "HardySimpson1984@gmail.com" target "HardySimpson1984@gmail.com" type "mailto:" \end_inset \end_layout \end_inset \end_layout \begin_layout Standard \begin_inset CommandInset toc LatexCommand tableofcontents \end_inset \end_layout \begin_layout Chapter zlog是什么? \end_layout \begin_layout Standard zlog是一个高可靠性、高性能、线程安全、灵活、概念清晰的纯C日志函数库。 \end_layout \begin_layout Standard 事实上,在C的世界里面没有特别好的日志函数库(就像JAVA里面的的log4j,或者C++的log4cxx)。C程序员都喜欢用自己的轮子。printf就是个挺好的 轮子,但没办法通过配置改变日志的格式或者输出文件。syslog是个系统级别的轮子,不过速度慢,而且功能比较单调。 \end_layout \begin_layout Standard 所以我写了zlog。 \end_layout \begin_layout Standard zlog在效率、功能、安全性上大大超过了log4c,并且是用c写成的,具有比较好的通用性。 \end_layout \begin_layout Standard zlog有这些特性: \end_layout \begin_layout Itemize syslog分类模型,比log4j模型更加直接了当 \end_layout \begin_layout Itemize 日志格式定制,类似于log4j的pattern layout \end_layout \begin_layout Itemize 多种输出,包括动态文件、静态文件、stdout、stderr、syslog、用户自定义输出函数 \end_layout \begin_layout Itemize 运行时手动、自动刷新配置文件(同时保证安全) \end_layout \begin_layout Itemize 高性能,在我的笔记本上达到25万条日志每秒, 大概是syslog(3)配合rsyslogd的1000倍速度 \end_layout \begin_layout Itemize 用户自定义等级 \end_layout \begin_layout Itemize 多线程和多进程环境下保证安全转档 \end_layout \begin_layout Itemize 精确到微秒 \end_layout \begin_layout Itemize 简单调用包装dzlog(一个程序默认只用一个分类) \end_layout \begin_layout Itemize MDC,线程键-值对的表,可以扩展用户自定义的字段 \end_layout \begin_layout Itemize 自诊断,可以在运行时输出zlog自己的日志和配置状态 \end_layout \begin_layout Itemize 不依赖其他库,只要是个POSIX系统就成(当然还要一个C99兼容的vsnprintf) \end_layout \begin_layout Standard 相关链接: \end_layout \begin_layout Standard 主页: \begin_inset CommandInset href LatexCommand href name "http://hardysimpson.github.com/zlog/" target "http://hardysimpson.github.com/zlog/" \end_inset \end_layout \begin_layout Standard 下载: \begin_inset CommandInset href LatexCommand href target "https://github.com/HardySimpson/zlog/releases" \end_inset \end_layout \begin_layout Standard 邮箱: \begin_inset CommandInset href LatexCommand href name "HardySimpson1984@gmail.com" target "HardySimpson1984@gmail.com" type "mailto:" \end_inset \end_layout \begin_layout Section 兼容性说明 \end_layout \begin_layout Enumerate zlog是基于POSIX的。目前我手上有的环境只有AIX和linux。在其他的系统下(FreeBSD, NetBSD, OpenBSD, OpenSolaris , Mac OS X...)估计也能行,有问题欢迎探讨。 \end_layout \begin_layout Enumerate zlog使用了一个C99兼容的vsnprintf。也就是说如果缓存大小不足,vsnprintf将会返回目标字符串应有的长度(不包括' \backslash 0')。如果在你的系统上vsnprintf不是这么运作的,zlog就不知道怎么扩大缓存。如果在目标缓存不够的时候vsnprintf返回-1,zlog就会认为这次 写入失败。幸运的是目前大多数c标准库符合C99标准。glibc 2.1,libc on AIX, libc on freebsd...都是好的,不过glibc2.0不是。 在这种情况下,用户需要自己来装一个C99兼容的vsnprintf,来crack这个函数库。我推荐 \begin_inset CommandInset href LatexCommand href name "ctrio" target "http://sourceforge.net/projects/ctrio/" \end_inset , 或者 \begin_inset CommandInset href LatexCommand href name "C99-snprintf" target "http://www.jhweiss.de/software/snprintf.html" \end_inset 。只要改buf.c就行,祝好运! \end_layout \begin_layout Enumerate 有网友提供了如下版本,方便其他平台上安装编译,非常感谢! \end_layout \begin_deeper \begin_layout Standard auto tools版本: \begin_inset CommandInset href LatexCommand href target "https://github.com/bmanojlovic/zlog" \end_inset \end_layout \begin_layout Standard cmake版本: \begin_inset CommandInset href LatexCommand href target "https://github.com/lisongmin/zlog" \end_inset \end_layout \begin_layout Standard windows版本: \begin_inset CommandInset href LatexCommand href target "https://github.com/lopsd07/WinZlog" \end_inset \end_layout \end_deeper \begin_layout Section zlog 1.2 发布说明 \end_layout \begin_layout Enumerate zlog 1.2 新增了这些功能 \end_layout \begin_deeper \begin_layout Enumerate 对管道的支持,从此zlog可以外接cronolog这样的日志过滤程序来输出 \end_layout \begin_layout Enumerate 全面的日志转档支持,详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:文件转档" \end_inset \end_layout \begin_layout Enumerate 其他兼容性的代码改动 \end_layout \end_deeper \begin_layout Enumerate zlog 1.2 在库方面是和zlog 1.0/1.1二进制兼容的,区别在于: \end_layout \begin_deeper \begin_layout Enumerate 所有的宏改为小写,ZLOG_INFO->zlog_info,方便开发者手工输入。这是一个巨大的改变,如果zlog1.1/1.0的用户要用zlog 1.2的话,需要写一 个脚本,把源代码中的大写批量替换为小写,然后重新编译你的程序。我提供了一个脚本: \end_layout \begin_deeper \begin_layout LyX-Code sed -i -e 's/ \backslash b \backslash w*ZLOG \backslash w* \backslash b/ \backslash L& \backslash E/g' aa.c \end_layout \end_deeper \begin_layout Enumerate 取消了auto tools的使用,也就是说,不论你在任何平台,都需要gcc和gnu make才能编译安装zlog。主流的操作系统(Aix, OpenSolari s..)都能安装gcc和gnu make。当然也可以自行修改makefile来完成编译,对于平台稍有经验的Geek都可以自行完成! \end_layout \end_deeper \begin_layout Chapter zlog不是什么? \end_layout \begin_layout Standard zlog的目标是成为一个简而精的日志函数库,不会直接支持网络输出或者写入数据库,不会直接支持日志内容的过滤和解析。 \end_layout \begin_layout Standard 原因很明显,日志库是被应用程序调用的,所有花在日志库上的时间都是应用程序运行时间的一部分,而上面说的这些操作都很费时间,会拖慢应用程序的速度。这些事儿应该在别的 进程或者别的机器上做。 \end_layout \begin_layout Standard 如果你需要这些特性,我建议使用rsyslog、zLogFabric、Logstash,这些日志搜集、过滤、存储软件,当然这是单独的进程,不是应用程序的一部分。 \end_layout \begin_layout Standard 目前zlog已经支持 \begin_inset CommandInset ref LatexCommand ref reference "sec:用户自定义输出" \end_inset ,可以自己实现一个输出函数,自由的把日志输出到其他进程或者其他机器。而把日志的分类匹配、日志格式成型的工作交给zlog。 \end_layout \begin_layout Standard 目前我的想法是实现一个zlog-redis客户端,用自定义输出功能,把日志存储到本机或者远程的redis服务器内,然后用其他进程(也使用zlog库)来把日志写到 文件里面,不知大家以为这个想法如何?欢迎和我联系探讨。 \end_layout \begin_layout Chapter Hello World \end_layout \begin_layout Section 编译和安装zlog \end_layout \begin_layout Standard 下载 \begin_inset CommandInset href LatexCommand href name "zlog-latest-stable.tar.gz" target "https://github.com/HardySimpson/zlog/archive/latest-stable.tar.gz" type "file:" \end_inset \end_layout \begin_layout LyX-Code $ tar -zxvf zlog-latest-stable.tar.gz \end_layout \begin_layout LyX-Code $ cd zlog-latest-stable/ \end_layout \begin_layout LyX-Code $ make \end_layout \begin_layout LyX-Code $ sudo make install \end_layout \begin_layout LyX-Code or \end_layout \begin_layout LyX-Code $ sudo make PREFIX=/usr/local/ install \end_layout \begin_layout Standard PREFIX指明了安装的路径,安转完之后为了让你的程序能找到zlog动态库 \end_layout \begin_layout LyX-Code $ sudo vi /etc/ld.so.conf \end_layout \begin_layout LyX-Code /usr/local/lib \end_layout \begin_layout LyX-Code $ sudo ldconfig \end_layout \begin_layout Standard 在你的程序运行之前,保证libzlog.so在系统的动态链接库加载器可以找到的目录下。上面的命令适用于linux,别的系统自己想办法。 \end_layout \begin_layout Itemize 除了一般的make以外,还可以 \end_layout \begin_layout LyX-Code $ make 32bit # 32bit version on 64bit machine, libc6-dev-i386 is needed \end_layout \begin_layout LyX-Code $ make noopt # without gcc optimization \end_layout \begin_layout LyX-Code $ make doc # lyx and hevea is needed \end_layout \begin_layout LyX-Code $ make test # test code, which is also good example for zlog \end_layout \begin_layout Itemize makefile是用GNU make的格式写的,所以在你的平台上需要预装gnu make和gcc。或者,手工修改一个自己平台的makefile也行。 \end_layout \begin_layout Section 应用程序调用和链接zlog \end_layout \begin_layout Standard 应用程序使用zlog很简单,只要在C文件里面加一行。 \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout Standard 链接zlog需要pthread库,命令是: \end_layout \begin_layout LyX-Code $ cc -c -o app.o app.c -I/usr/local/include \end_layout \begin_layout LyX-Code # -I[where zlog.h is put] \end_layout \begin_layout LyX-Code $ cc -o app app.o -L/usr/local/lib -lzlog -lpthread \end_layout \begin_layout LyX-Code # -L[where libzlog.so is put] \end_layout \begin_layout Section Hello World 代码 \begin_inset CommandInset label LatexCommand label name "sec:Hello-World-代码" \end_inset \end_layout \begin_layout Standard 这些代码在$(top_builddir)/test/test_hello.c, test_hello.conf \end_layout \begin_layout Enumerate 写一个C文件: \end_layout \begin_deeper \begin_layout LyX-Code $ vi test_hello.c \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code zlog_category_t *c; \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code rc = zlog_init("test_hello.conf"); \end_layout \begin_layout LyX-Code if (rc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code c = zlog_get_category("my_cat"); \end_layout \begin_layout LyX-Code if (!c) { \end_layout \begin_layout LyX-Code printf("get cat fail \backslash n"); \end_layout \begin_deeper \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return -2; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code zlog_info(c, "hello, zlog"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \end_deeper \begin_layout Enumerate 写一个配置文件,放在和test_hello.c同样的目录下: \end_layout \begin_deeper \begin_layout LyX-Code $ vi test_hello.conf \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code simple = "%m%n" \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.DEBUG >stdout; simple \end_layout \end_deeper \begin_layout Enumerate 编译、然后运行! \end_layout \begin_deeper \begin_layout LyX-Code $ cc -c -o test_hello.o test_hello.c -I/usr/local/include \end_layout \begin_layout LyX-Code $ cc -o test_hello test_hello.o -L/usr/local/lib -lzlog \end_layout \begin_layout LyX-Code $ ./test_hello \end_layout \begin_layout LyX-Code hello, zlog \end_layout \end_deeper \begin_layout Section 更简单的Hello World \end_layout \begin_layout Standard 这个例子在$(top_builddir)/test/test_default.c, test_default.conf. 源代码是: \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code rc = dzlog_init("test_default.conf", "my_cat"); \end_layout \begin_layout LyX-Code if (rc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code dzlog_info("hello, zlog"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout Standard 配置文件是test_default.conf,和test_hello.conf一模一样,最后执行程序的输出也一样。区别在于这里用了dzlog API,内含一个默认的 zlog_category_t。详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:dzlog接口" \end_inset 。 \end_layout \begin_layout Chapter Syslog 模型 \end_layout \begin_layout Section 分类(Category)、规则(Rule)和格式(Format) \end_layout \begin_layout Standard zlog有3个重要的概念:分类(Category)、规则(Rule)和格式(Format)。 \end_layout \begin_layout Standard 分类(Category)用于区分不同的输入。代码中的分类变量的名字是一个字符串,在一个程序里面可以通过获取不同的分类名的category用来后面输出不同分类的日 志,用于不同的目的。 \end_layout \begin_layout Standard 格式(Format)是用来描述输出日志的格式,比如是否有带有时间戳,是否包含文件位置信息等,上面的例子里面的格式simple就是简单的用户输入的信息+换行符。 \end_layout \begin_layout Standard 规则(Rule)则是把分类、级别、输出文件、格式组合起来,决定一条代码中的日志是否输出,输出到哪里,以什么格式输出。 \end_layout \begin_layout Standard 所以,当程序执行下面的语句的时候 \end_layout \begin_layout LyX-Code zlog_category_t *c; \end_layout \begin_layout LyX-Code c = zlog_get_category("my_cat"); \end_layout \begin_layout LyX-Code zlog_info(c, "hello, zlog"); \end_layout \begin_layout Standard zlog会找到c的名字是"my_cat",对应的配置文件中的规则是 \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.DEBUG >stdout; simple \end_layout \begin_layout Standard 然后库会检查,目前这条日志的级别是否符合规则中的级别来决定是否输出。因为INFO>=DEBUG,所以这条日志会被输出。并且根据这条规则,会被输出到stdout( 标准输出) ,输出的格式是simple,在配置文件中定义是 \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code simple = "%m%n" \end_layout \begin_layout Standard 最后在屏幕上打印 \end_layout \begin_layout LyX-Code hello, zlog \end_layout \begin_layout Standard 这就是整个过程。用户要做就是写自己的信息。日志往哪里输出,以什么格式输出,都是库和配置文件来完成的。 \end_layout \begin_layout Section syslog模型和log4j模型的区别 \end_layout \begin_layout Standard 好,那么目前这个模型和syslog有什么关系呢?至今为止,这个模型还是比较像log4j。log4j的模型里面有logger, appender和layout。区 别在于,在log4j里面,代码中的logger和配置中的logger是一一对应的,并且一个logger有唯一的级别。一对一关系是log4j, log4cxx, log4cpp, log4cplus, log4net的唯一选择。 \end_layout \begin_layout Standard 但这种模型是不灵活的,他们发明了过滤器(filters)来弥补,但这只能把事情弄得更加混乱。所以让我们把目光转回syslog的模型,这是一个设计的很简易正确的模 型。 \end_layout \begin_layout Standard 继续上一节的例子,如果在zlog的配置文件中有这么2行规则: \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.DEBUG >stdout; simple \end_layout \begin_layout LyX-Code my_cat.INFO >stdout; \end_layout \begin_layout Standard 然后,一行代码会产生两行输出: \end_layout \begin_layout LyX-Code hello, zlog \end_layout \begin_layout LyX-Code 2012-05-29 10:41:36 INFO [11288:test_hello.c:41] hello, zlog \end_layout \begin_layout Standard 现在一个代码中的分类对应配置文件中的两条规则。log4j的用户可能会说:"这很好,但是只要在log4j里面放两个appender也能做的一样。"所以继续看下一个 例子: \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.WARN "/var/log/aa.log" \end_layout \begin_layout LyX-Code my_cat.DEBUG "/var/log/bb.log" \end_layout \begin_layout Standard 代码是: \end_layout \begin_layout LyX-Code zlog_info(c, "info, zlog"); \end_layout \begin_layout LyX-Code zlog_debug(c, "debug, zlog"); \end_layout \begin_layout Standard 最后,在aa.log中只有一条日志 \end_layout \begin_layout LyX-Code 2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog \end_layout \begin_layout Standard 但在bb.log里面有两条 \end_layout \begin_layout LyX-Code 2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog \end_layout \begin_layout LyX-Code 2012-05-29 10:41:36 DEBUG [11288:test_hello.c:42] debug, zlog \end_layout \begin_layout Standard 从这个例子能看出来区别。log4j无法轻易的做到这一点。在zlog里面,一个分类可以对应多个规则,每个规则有自己的级别、输出和格式。这就让用户能按照需求过滤、多 渠道输出自己的所有日志。 \end_layout \begin_layout Section 扩展syslog模型 \end_layout \begin_layout Standard 所以到现在你能看出来zlog的模型更像syslog的模型。不幸的是,在syslog里面,设施(facility)是个int型,而且必须从系统定义的那几种里面选择 。zlog走的远一点,用一个字符串来标识分类。 \end_layout \begin_layout Standard syslog有一个通配符"*",匹配所有的设施(facility)。zlog里面也一样,"*"匹配所有分类。这提供了一个很方便的办法来重定向你的系统中各个组件的 错误。只要这么写: \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code *.error "/var/log/error.log" \end_layout \begin_layout Standard zlog强大而独有的特性是上下级分类匹配。如果你的分类是这样的: \end_layout \begin_layout LyX-Code c = zlog_get_category("my_cat"); \end_layout \begin_layout Standard 然后配置文件是这样的 \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.* "/var/log/my_cat.log" \end_layout \begin_layout LyX-Code my_.NOTICE "/var/log/my.log" \end_layout \begin_layout Standard 这两条规则都匹配c分类"my_cat"。通配符"_" 表示上级分类。 "my_"是"my_cat"和"my_dog"的上级分类。还有一个通配符是"!",详见 \begin_inset CommandInset ref LatexCommand ref reference "sub:分类匹配" \end_inset \end_layout \begin_layout Chapter 配置文件 \end_layout \begin_layout Standard 大部分的zlog的行为取决于配置文件:把日志打到哪里去,用什么格式,怎么转档。配置文件是zlog的黑话,我尽量把这个黑话设计的简单明了。这是个配置文件例子: \end_layout \begin_layout LyX-Code # comments \end_layout \begin_layout LyX-Code [global] \end_layout \begin_layout LyX-Code strict init = true \end_layout \begin_layout LyX-Code buffer min = 1024 \end_layout \begin_layout LyX-Code buffer max = 2MB \end_layout \begin_layout LyX-Code rotate lock file = /tmp/zlog.lock \end_layout \begin_layout LyX-Code default format = "%d.%us %-6V (%c:%F:%L) - %m%n" \end_layout \begin_layout LyX-Code file perms = 600 \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code [levels] \end_layout \begin_layout LyX-Code TRACE = 10 \end_layout \begin_layout LyX-Code CRIT = 130, LOG_CRIT \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code simple = "%m%n" \end_layout \begin_layout LyX-Code normal = "%d %m%n" \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code default.* >stdout; simple \end_layout \begin_layout LyX-Code *.* "%12.2E(HOME)/log/%c.log", 1MB*12; simple \end_layout \begin_layout LyX-Code my_.INFO >stderr; \end_layout \begin_layout LyX-Code my_cat.!ERROR "/var/log/aa.log" \end_layout \begin_layout LyX-Code my_dog.=DEBUG >syslog, LOG_LOCAL0; simple \end_layout \begin_layout LyX-Code my_mice.* $user_define; \end_layout \begin_layout Standard 有关单位:当设置内存大小或者大数字时,可以设置1k 5GB 4M这样的单位: \end_layout \begin_layout LyX-Code # 1k => 1000 bytes \end_layout \begin_layout LyX-Code # 1kb => 1024 bytes \end_layout \begin_layout LyX-Code # 1m => 1000000 bytes \end_layout \begin_layout LyX-Code # 1mb => 1024*1024 bytes \end_layout \begin_layout LyX-Code # 1g => 1000000000 bytes \end_layout \begin_layout LyX-Code # 1gb => 1024*1024*1024 byte \end_layout \begin_layout Standard 单位是大小写不敏感的,所以1GB 1Gb 1gB是等效的。 \end_layout \begin_layout Section 全局参数 \end_layout \begin_layout Standard 全局参数以[global]开头。[]代表一个节的开始,四个小节的顺序不能变,依次为global-levels-formats-rules。这一节可以忽略不写。语 法为 \end_layout \begin_layout LyX-Code (key) = (value) \end_layout \begin_layout Itemize strict init \end_layout \begin_deeper \begin_layout Standard 如果"strict init"是true,zlog_init()将会严格检查所有的格式和规则,任何错误都会导致zlog_init() 失败并且返回-1。当"st rict init"是false的时候,zlog_init()会忽略错误的格式和规则。 这个参数默认为true。 \end_layout \end_deeper \begin_layout Itemize reload conf period \end_layout \begin_deeper \begin_layout Standard 这个选项让zlog能在一段时间间隔后自动重载配置文件。重载的间隔以每进程写日志的次数来定义。当写日志次数到了一定值后,内部将会调用zlog_reload()进行 重载。每次zlog_reload()或者zlog_init()之后重新计数累加。因为zlog_reload()是原子性的,重载失败继续用当前的配置信息,所以自动 重载是安全的。默认值是0,自动重载是关闭的。 \end_layout \end_deeper \begin_layout Itemize buffer min \end_layout \begin_layout Itemize buffer max \end_layout \begin_deeper \begin_layout Standard zlog在堆上为每个线程申请缓存。"buffer min"是单个缓存的最小值,zlog_init()的时候申请这个长度的内存。写日志的时候,如果单条日志长度大于 缓存,缓存会自动扩充,直到到"buffer max"。 单条日志再长超过"buffer max"就会被截断。如果 "buffer max" 是 0,意味着不限制缓存,每次扩充为原先的2倍,直到这个进程用完所有内存为止。缓存大小可以加上 KB, MB 或 GB这些单位。默认来说"buffer min"是 1K , "buffer max" 是2MB。 \end_layout \end_deeper \begin_layout Itemize rotate lock file \end_layout \begin_deeper \begin_layout Standard 这个选项指定了一个锁文件,用来保证多进程情况下日志安全转档。zlog会在zlog_init()时候以读写权限打开这个文件。确认你执行程序的用户有权限创建和读写这 个文件。转档日志的伪代码是: \end_layout \begin_layout LyX-Code write(log_file, a_log) \end_layout \begin_layout LyX-Code if (log_file > 1M) \end_layout \begin_deeper \begin_layout LyX-Code if (pthread_mutex_lock succ && fcntl_lock(lock_file) succ) \end_layout \begin_deeper \begin_layout LyX-Code if (log_file > 1M) rotate(log_file); \end_layout \begin_layout LyX-Code fcntl_unlock(lock_file); \end_layout \begin_layout LyX-Code pthread_mutex_unlock; \end_layout \end_deeper \end_deeper \begin_layout Standard mutex_lock用于多线程, fcntl_lock用于多进程。fcntl_lock是POSIX建议锁。详见man 3 fcntl。这个锁是全系统有效的。在某 个进程意外死亡后,操作系统会释放此进程持有的锁。这就是我为什么用fcntl锁来保证安全转档。进程需要对锁文件有读写权限。 \end_layout \begin_layout Standard 默认来说,rotate lock file = self。在这种情况下,zlog不会创建任何锁文件,用配置文件作为锁文件。fcntl是建议锁,所以用户可以自由的 修改存储他们的配置文件。一般来说,单个日志文件不会被不同操作系统用户的进程转档,所以用配置文件作为锁文件是安全的。 \end_layout \begin_layout Standard 如果你设置其他路径作为锁文件,例如/tmp/zlog.lock,zlog会在zlog_init()的时候创建这个文件。如果有多个操作系统用户的进程需要转档同一个日 志文件,确认这个锁文件对于多个用户都可读写。默认值是/tmp/zlog.lock。 \end_layout \end_deeper \begin_layout Itemize default format \end_layout \begin_deeper \begin_layout Standard 这个参数是缺省的日志格式,默认值为: \end_layout \begin_layout LyX-Code "%d %V [%p:%F:%L] %m%n" \end_layout \begin_layout Standard 这种格式产生的输出类似这样: \end_layout \begin_layout LyX-Code 2012-02-14 17:03:12 INFO [3758:test_hello.c:39] hello, zlog \end_layout \end_deeper \begin_layout Itemize file perms \end_layout \begin_deeper \begin_layout Standard 这个指定了创建日志文件的缺省访问权限。必须注意的是最后的产生的日志文件的权限为"file perms"& ~umask。默认为600,只允许当前用户读写。 \end_layout \end_deeper \begin_layout Itemize fsync period \end_layout \begin_deeper \begin_layout Standard 在每条规则写了一定次数的日志到文件后,zlog会调用fsync(3)来让操作系统马上把数据写到硬盘。次数是每条规则单独统计的,并且在zlog_reload()后 会被清0。必须指出的是,在日志文件名是动态生成或者被转档的情况下,zlog不能保证把所有文件都搞定,zlog只fsync()那个时候刚刚write()的文件描述 符。这提供了写日志速度和数据安全性之间的平衡。例子: \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 1 10 100000 \end_layout \begin_layout LyX-Code real 0m1.806s \end_layout \begin_layout LyX-Code user 0m3.060s \end_layout \begin_layout LyX-Code sys 0m0.270s \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code $ wc -l press.log \end_layout \begin_layout LyX-Code 1000000 press.log \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 1 10 100000 #fsync period = 1K \end_layout \begin_layout LyX-Code real 0m41.995s \end_layout \begin_layout LyX-Code user 0m7.920s \end_layout \begin_layout LyX-Code sys 0m0.990s \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 1 10 100000 #fsync period = 10K \end_layout \begin_layout LyX-Code real 0m6.856s \end_layout \begin_layout LyX-Code user 0m4.360s \end_layout \begin_layout LyX-Code sys 0m0.550s \end_layout \begin_layout Standard 如果你极度在乎安全而不是速度的话,用同步IO文件,见 \begin_inset CommandInset ref LatexCommand ref reference "ite:同步IO文件" \end_inset 。默认值是0,由操作系统来决定什么时候刷缓存到文件。 \end_layout \end_deeper \begin_layout Section 日志等级自定义 \end_layout \begin_layout Standard 这一节以[levels]开始。用于定义用户自己的日志等级,建议和用户自定义的日志记录宏一起使用。这一节可以忽略不写。语法为: \end_layout \begin_layout LyX-Code (level string) = (level int), (syslog level, optional) \end_layout \begin_layout Standard (level int)必须在[1,253]这个范围内,越大越重要。(syslog level)是可选的,如果不设默认为LOG_DEBUG。 \end_layout \begin_layout Standard 详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:用户自定义等级" \end_inset 。 \end_layout \begin_layout Section 格式(Formats) \end_layout \begin_layout Standard 这一节以[formats]开始。用来定义日志的格式。语法为: \end_layout \begin_layout LyX-Code (name) = "(actual formats)" \end_layout \begin_layout Standard 很好理解,(name)被后面的规则使用。(name)必须由数字和字母组成,下划线"_"也算字母。(actual format)前后需要有双引号。 (actual formats)可以由转换字符组成,见下一节。 \end_layout \begin_layout Section 转换格式串 \begin_inset CommandInset label LatexCommand label name "sec:转换格式串" \end_inset \end_layout \begin_layout Standard 转换格式串的设计是从C的printf函数里面抄来的。一个转换格式串由文本字符和转换说明组成。 \end_layout \begin_layout Standard 转换格式串用在规则的日志文件路径和输出格式(format)中。 \end_layout \begin_layout Standard 你可以把任意的文本字符放到转换格式串里面。 \end_layout \begin_layout Standard 每个转换说明都是以百分号(%)打头的,后面跟可选的宽度修饰符,最后以转换字符结尾。转换字符决定了输出什么数据,例如分类名、级别、时间日期、进程号等等。宽度修饰符 控制了这个字段的最大最小宽度、左右对齐。下面是简单的例子。 \end_layout \begin_layout Standard 如果转换格式串是: \end_layout \begin_layout LyX-Code "%d(%m-%d %T) %-5V [%p:%F:%L] %m%n". \end_layout \begin_layout Standard 源代码中的写日志语句是: \end_layout \begin_layout LyX-Code zlog_info(c, "hello, zlog"); \end_layout \begin_layout Standard 将会输出: \end_layout \begin_layout LyX-Code 02-14 17:17:42 INFO [4935:test_hello.c:39] hello, zlog \end_layout \begin_layout Standard 可以注意到,在文本字符和转换说明之间没有显式的分隔符。zlog解析的时候知道哪里是转换说明的开头和结尾。在这个例子里面%-5p这个转换说明决定了日志级别要被左对 齐,占5个字符宽。 \end_layout \begin_layout Subsection 转换字符 \end_layout \begin_layout Standard 可以被辨认的转换字符是 \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 字符 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 效果 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 例子 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 分类名 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa_bb \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %d() \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 打日志的时间。这个后面要跟一对小括号()内含说明具体的日期格式。就像%d(%F)或者%d(%m-%d %T)。如果不跟小括号,默认是%d(%F %T)。括号内的格式和 strftime(2)的格式一致。详见 \begin_inset CommandInset ref LatexCommand ref reference "sub:时间字符" \end_inset \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %d(%F) 2011-12-01 \end_layout \begin_layout Plain Layout %d(%m-%d %T) 12-01 17:17:42 \end_layout \begin_layout Plain Layout %d(%T) 17:17:42.035 \end_layout \begin_layout Plain Layout %d 2012-02-14 17:03:12 \end_layout \begin_layout Plain Layout %d() \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %E() \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 获取环境变量的值 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %E(USER) simpson \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %ms \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 毫秒,3位数字字符串 \end_layout \begin_layout Plain Layout 取自gettimeofday(2) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 013 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %us \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 微秒,6位数字字符串 \end_layout \begin_layout Plain Layout 取自gettimeofday(2) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 002323 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %F \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 源代码文件名,来源于__FILE__宏。在某些编译器下 __FILE__是绝对路径。用%f来去掉目录只保留文件名,或者编译器有选项可以调节 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout test_hello.c \end_layout \begin_layout Plain Layout 或者在某些编译器下 \end_layout \begin_layout Plain Layout /home/zlog/src/test/test_hello.c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %f \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 源代码文件名,输出$F最后一个'/'后面的部分。当然这会有一定的性能损失 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout test_hello.c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %H \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 主机名,来源于 gethostname(2) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout zlog-dev \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %L \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 源代码行数,来源于__LINE__宏 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 135 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %m \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 用户日志,用户从zlog函数输入的日志。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout hello, zlog \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %M \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout MDC (mapped diagnostic context),每个线程一张键值对表,输出键相对应的值。后面必需跟跟一对小括号()内含键。例如 %M(clientNumber) ,clientNumbe是键。 详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:MDC" \end_inset \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %M(clientNumber) 12345 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %n \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 换行符,目前还不支持windows换行符 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout \backslash n \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %p \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 进程ID,来源于getpid() \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 2134 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %U \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 调用函数名,来自于__func__(C99)或者__FUNCTION__(gcc),如果编译器支持的话。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout main \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %V \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 日志级别,大写 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout INFO \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %v \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 日志级别,小写 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout info \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %t \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 16进制表示的线程ID,来源于pthread_self() \end_layout \begin_layout Plain Layout "0x%x",(unsigned int) pthread_t \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 0xba01e700 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %T \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%t,不过是以长整型表示的 \end_layout \begin_layout Plain Layout "%lu", (unsigned long) pthread_t \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 140633234859776 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %% \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一个百分号 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout % \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %[其他字符] \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 解析为错误,zlog_init()将会失败 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout \end_layout \end_inset \end_inset \end_layout \begin_layout Subsection 宽度修饰符 \end_layout \begin_layout Standard 一般来说数据按原样输出。不过,有了宽度修饰符,就能够控制最小字段宽度、最大字段宽度和左右对齐。当然这要付出一定的性能代价。 \end_layout \begin_layout Standard 可选的宽度修饰符放在百分号和转换字符之间。 \end_layout \begin_layout Standard 第一个可选的宽度修饰符是左对齐标识,减号(-)。然后是可选的最小字段宽度,这是一个十进制数字常量,表示最少有几个字符会被输出。如果数据本来没有那么多字符,将会填 充空格(左对齐或者右对齐)直到最小字段宽度为止。默认是填充在左边也就是右对齐。当然你也可以使用左对齐标志,指定为填充在右边来左对齐。填充字符为空格(space) 。如果数据的宽度超过最小字段宽度,则按照数据的宽度输出,永远不会截断数据。 \end_layout \begin_layout Standard 这种行为可以用最大字段宽度来改变。最大字段宽度是放在一个句点号(.)后面的十进制数字常量。如果数据的宽度超过了最大字段宽度,则尾部多余的字符(超过最大字段宽度的部 分)将会被截去。 最大字段宽度是8,数据的宽度是10,则最后两个字符会被丢弃。这种行为和C的printf是一样的,把后面的部分截断。 \end_layout \begin_layout Standard 下面是各种宽度修饰符和分类转换字符配合一起用的例子。 \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 宽度修饰符 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 左对齐 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 最小字段宽度 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 最大字段宽度 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 附注 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %20c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 否 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 20 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 左补充空格,如果分类名小于20个字符长。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %-20c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 是 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 20 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 右补充空格,如果分类名小于20个字符长。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %.30c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 30 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 如果分类名大于30个字符长,取前30个字符,去掉后面的。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %20.30c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 否 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 20 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 30 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 如果分类名小于20个字符长,左补充空格。如果在20-30之间,按照原样输出。如果大于30个字符长,取前30个字符,去掉后面的。 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %-20.30c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 是 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 20 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 30 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 如果分类名小于20个字符长,右补充空格。如果在20-30之间,按照原样输出。如果大于30个字符长,取前30个字符,去掉后面的。 \end_layout \end_inset \end_inset \end_layout \begin_layout Subsection 时间字符 \begin_inset CommandInset label LatexCommand label name "sub:时间字符" \end_inset \end_layout \begin_layout Standard 这里是转换字符d支持的时间字符。 \end_layout \begin_layout Standard 所有字符都是由strftime(2)生成的,在我的linux操作系统上支持的是: \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 字符 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 效果 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 例子 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %a \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一星期中各天的缩写名,根据locale显示 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Wed \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %A \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一星期中各天的全名,根据locale显示 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Wednesday \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %b \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 缩写的月份名,根据locale显示 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Mar \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %B \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 月份全名,根据locale显示 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout March \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %c \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 当地时间和日期的全表示, 根据locale显示 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Thu Feb 16 14:16:35 2012 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %C \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 世纪 (年/100),2位的数字(SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 20 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %d \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一个月中的某一天 (01-31) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 06 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %D \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%m/%d/%y. (呃,美国人专用,美国人要知道在别的国家%d/%m/%y 才是主流。也就是说在国际环境下这个格式容易造成误解,要少用) (SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 02/16/12 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %e \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 就像%d,一个月中的某一天,但是头上的0被替换成空格(SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 6 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %F \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%Y-%m-%d (ISO 8601日期格式)(C99) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 2012-02-16 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %G \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 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) \end_layout \begin_layout Plain Layout 大意是采用%V定义的年,如果那年的前几天不算新年的第一周,就算上一年 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 2012 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %g \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%G,就是不带世纪 (00-99). (TZ) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 12 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %h \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%b(SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Feb \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %H \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时,24小时表示(00-23) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 14 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %I \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时,12小时表示(01-12) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 02 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %j \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一年中的各天(001-366) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 047 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %k \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时,24小时表示( 0-23); 一位的前面为空格 (可和%H比较) (TZ) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 15 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %l \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时,12小时表示( 0-12); 一位的前面为空格 (可和%比较)(TZ) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 3 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %m \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 月份(01-12) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 02 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %M \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 分钟(00-59) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 11 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %n \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 换行符 (SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout \backslash n \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %p \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout "AM" 或 "PM",根据当时的时间,根据locale显示相应的值,例如"上午"、"下午" 。 中午是"PM",凌晨是"AM" \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout PM \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %P \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 相当于%p不过是小写,根据locale显示相应的值 (GNU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout pm \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %r \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 时间+后缀AM或PM。在POSIX locale下相当于%I:%M:%S %p. (SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 03:11:54 PM \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %R \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时(24小时制):分钟 (%H:%M) (SU) 如果要带秒的,见%T \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 15:11 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %s \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout Epoch以来的秒数,也就是从1970-01-01 00:00:00 UTC. (TZ) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 1329376487 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %S \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 秒(00-60). (允许60是为了闰秒) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 54 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %t \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 制表符tab(SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %T \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 小时(24小时制):分钟:秒 (%H:%M:%S) (SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 15:14:47 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %u \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一周的天序号(1-7),周一是1,另见%w (SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 4 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %U \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一年中的星期序号(00-53),周日是一周的开始,一年中第一个周日所在的周是第01周。另见%V和%W \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 07 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %V \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout ISO 8601星期序号(01-53),01周是第一个至少有4天在新年的周。另见%U 和%W(SU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 07 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %w \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一周的天序号(0-6),周日是0。另见%u \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 4 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %W \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一年中的星期序号(00-53),周一是一周的开始,一年中第一个周一所在的周是第01周。另见%V和%W \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 07 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %x \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 当前locale下的偏好日期 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 02/16/12 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %X \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 当前locale下的偏好时间 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 15:14:47 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %y \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 不带世纪数目的年份(00-99) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 12 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %Y \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 带世纪数目的年份 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 2012 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %z \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 当前时区相对于GMT时间的偏移量。采用RFC 822-conformant来计算(话说我也不知道是啥) (using "%a, %d %b %Y %H:%M:%S %z"). (GNU) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout +0800 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %Z \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 时区名(如果有的话) \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout CST \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout %% \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 一个百分号 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout % \end_layout \end_inset \end_inset \end_layout \begin_layout Section 规则(Rules) \end_layout \begin_layout Standard 这一节以[rules]开头。这个描述了日志是怎么被过滤、格式化以及被输出的。这节可以忽略不写,不过这样就没有日志输出了,嘿嘿。语法是: \end_layout \begin_layout LyX-Code (category).(level) (output), (options, optional); (format name, optional) \end_layout \begin_layout Standard 当zlog_init()被调用的时候,所有规则都会被读到内存中。当zlog_get_category()被调用,规则就被被分配给分类( \begin_inset CommandInset ref LatexCommand ref reference "sub:分类匹配" \end_inset )。在实际写日志的时候,例如zlog_info()被调用的时候,就会比较这个INFO和各条规则的等级,来决定这条日志会不会通过这条规则输出。当zlog_relo ad()被调用的时候,配置文件会被重新读入,包括所有的规则,并且重新计算分类对应的规则。 \end_layout \begin_layout Subsection 级别匹配 \end_layout \begin_layout Standard zlog有6个默认的级别:"DEBUG", "INFO", "NOTICE", "WARN", "ERROR"和"FATAL"。就像其他的日志函数库那样, aa.DEBUG意味着任何大于等于DEBUG级别的日志会被输出。当然还有其他的表达式。配置文件中的级别是大小写不敏感的。 \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 表达式 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 含义 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout * \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 所有等级 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa.debug \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 代码内等级>=debug \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa.=debug \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 代码内等级==debug \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa.!debug \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 代码内等级!=debug \end_layout \end_inset \end_inset \end_layout \begin_layout Standard 用户可以自定义等级,详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:用户自定义等级" \end_inset 。 \end_layout \begin_layout Subsection 分类匹配 \begin_inset CommandInset label LatexCommand label name "sub:分类匹配" \end_inset \end_layout \begin_layout Standard \end_layout \begin_layout Standard 分类必须由数字和字母组成,下划线"_"也算字母。 \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 总结 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 配置文件规则分类 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 匹配的代码分类 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 不匹配的代码分类 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout *匹配所有 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout *.* \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa, aa_bb, aa_cc, xx, yy ... \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout NONE \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 以_结尾的分类匹配本级及下级分类 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa_.* \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa, aa_bb, aa_cc, aa_bb_cc \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout xx, yy \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 不以_结尾的精确匹配分类名 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa.* \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa_bb, aa_cc, aa_bb_cc \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout !匹配那些没有找到规则的分类 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout !.* \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout xx \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout aa(as it matches rules above) \end_layout \end_inset \end_inset \end_layout \begin_layout Subsection 输出动作 \end_layout \begin_layout Standard 目前zlog支持若干种输出,语法是: \end_layout \begin_layout Standard [输出], [附加选项, 可选]; [format(格式)名, 可选] \end_layout \begin_layout Standard \begin_inset Tabular \begin_inset Text \begin_layout Plain Layout 动作 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 输出字段 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 附加选项 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 标准输出 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout >stdout \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无意义 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 标准错误输出 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout >stderr \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无意义 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 输出到syslog \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout >syslog \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout syslog设施(facilitiy): \end_layout \begin_layout Plain Layout LOG_USER(default), LOG_LOCAL[0-7] \end_layout \begin_layout Plain Layout 必填 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 管道输出 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout |cat \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 无意义 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 文件 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout "文件路径" \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 文件转档,详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:文件转档" \end_inset \end_layout \begin_layout Plain Layout 10M * 3 ~ "press.#r.log" \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 同步IO文件 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout -"文件路径" \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout 用户自定义输出 \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout $name \end_layout \end_inset \begin_inset Text \begin_layout Plain Layout "path" 动态或者静态的用于record输出 \end_layout \end_inset \end_inset \end_layout \begin_layout Itemize stdout, stderr, syslog \end_layout \begin_deeper \begin_layout Standard 如表格描述,其中只有sylog的附加选项是有意义并必须写的。 \end_layout \begin_layout Standard 值得注意的是,zlog在写日志的时候会用这样的语句 \end_layout \begin_layout LyX-Code write(STDOUT_FILENO, zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg _buf)) \end_layout \begin_layout Standard 而如果你的程序是个守护进程,在启动的时候把STDOUT_FILENO,也就是1的文件描述符关掉的话,会发生什么结果呢? \end_layout \begin_layout Standard 日志会被写到新的1的文件描述符所代表的文件里面!我收到过邮件,说zlog把日志写到自己的配置文件里面去了! \end_layout \begin_layout Standard 所以,千万不要在守护进程的规则里面加上>stdout或>stderr。这会产生不可预料的结果……如果一定要输出到终端,用"/dev/tty"代替。 \end_layout \end_deeper \begin_layout Itemize 管道输出 \end_layout \begin_deeper \begin_layout LyX-Code *.* | /usr/bin/cronolog /www/logs/example_%Y%m%d.log ; normal \end_layout \begin_layout Standard 这是一个将zlog的输出到管道后接cronolog的例子。实现的原理很简单,在zlog_init的时候调用popen("/usr/bin/cronolog /www/logs/example_%Y%m%d.log", "w"),后面往这个文件描述符里面写指定格式的日志。使用cronolog来生成按天分割的日志效率比 zlog自己的动态路径的效率要高,因为通过管道,无须每次打开关闭动态路径的文件描述符。 \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code *.* "press%d(%Y%m%d).log" \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 1 10 100000 \end_layout \begin_layout LyX-Code real 0m4.240s \end_layout \begin_layout LyX-Code user 0m2.500s \end_layout \begin_layout LyX-Code sys 0m5.460s \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code *.* | /usr/bin/cronolog press%Y%m%d.log \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 1 10 100000 \end_layout \begin_layout LyX-Code real 0m1.911s \end_layout \begin_layout LyX-Code user 0m1.980s \end_layout \begin_layout LyX-Code sys 0m1.470s \end_layout \begin_layout Standard 不过,使用管道也是有限制的: \end_layout \begin_layout Itemize POSIX.1-2001保证读写不大于PIPE_BUF大小的内容是原子的。linux上PIPE_BUF为4096。 \end_layout \begin_layout Itemize 单条日志的长度超过PIPE_BUF的时候并且有多个有父子关系的进程写通过zlog写同一个管道,也就是在zlog_init之后fork多个子进程,此时只有一个cr onolog的进程监听一个管道描述符,日志内容可能会交错。 \end_layout \begin_layout Itemize 多个进程分别zlog_init,启动多个cronolog进程,写拥有同一个文件路径的日志文件,即使单条日志长度不超过PIPE_BUF,也有可能导致日志交错,因为 cronolog读到的文件流是连续的,它不知道单条日志的边界在哪里。 \end_layout \begin_layout Standard 所以,总结一下,使用管道来输出到单个日志文件的情况是: \end_layout \begin_layout Itemize 单进程写,单条日志长度不限制。单进程内内的多线程写日志的原子性已经由zlog保证了。 \end_layout \begin_layout Itemize 有父子关系的多进程,单条日志长度不能超过PIPE_BUF(4096) \end_layout \begin_layout Itemize 无父子关系的多进程使用管道同时写一个日志,无论单条日志长度是多少,都有可能导致日志交错。 \end_layout \begin_layout Standard zlog本身的直接文件输出能保证即使是多进程,同时调用zlog写一个日志文件也不会产生交错,见下。 \end_layout \end_deeper \begin_layout Itemize 文件 \end_layout \begin_deeper \begin_layout Itemize 文件路径 \end_layout \begin_deeper \begin_layout Standard 可以是相对路径或者绝对路径,被双引号"包含。转换格式串可以用在文件路径上。例如文件路径是 "%E(HOME)/log/out.log",环境变量$HOME是/ho me/harry,那最后的输出文件是/home/harry/log/output.log。转换格式串详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:转换格式串" \end_inset 。 \end_layout \begin_layout Standard zlog的文件功能极为强大,例如 \end_layout \begin_layout Enumerate 输出到命名管道(FIFO),必须在调用前由mkfifo(1)创建 \end_layout \begin_deeper \begin_layout LyX-Code *.* "/tmp/pipefile" \end_layout \end_deeper \begin_layout Enumerate 输出到NULL,也就是不输出 \end_layout \begin_deeper \begin_layout LyX-Code *.* "/dev/null" \end_layout \end_deeper \begin_layout Enumerate 在任何情况下输出到终端 \end_layout \begin_deeper \begin_layout LyX-Code *.* "/dev/tty" \end_layout \end_deeper \begin_layout Enumerate 每线程一个日志,在程序运行的目录下 \end_layout \begin_deeper \begin_layout LyX-Code *.* "%T.log" \end_layout \end_deeper \begin_layout Enumerate 输出到有进程号区分的日志,每天,在$HOME/log目录,每1GB转档一次,保持5个日志文件。 \end_layout \begin_deeper \begin_layout LyX-Code *.* "%E(HOME)/log/aa.%p.%d(%F).log",1GB * 5 \end_layout \end_deeper \begin_layout Enumerate aa_及下级分类,每个分类一个日志 \end_layout \begin_deeper \begin_layout LyX-Code aa_.* "/var/log/%c.log" \end_layout \end_deeper \end_deeper \begin_layout Itemize 文件转档 \end_layout \begin_deeper \begin_layout Standard 控制文件的大小和个数。zlog根据这个字段来转档,当日志文件太大的时候。例如 \end_layout \begin_layout LyX-Code "%E(HOME)/log/out.log", 1M * 3 ~ "%E(HOME)/log/out.log.#r" \end_layout \begin_layout Standard 这三个参数都不是必填项,zlog的转档功能详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:文件转档" \end_inset \end_layout \end_deeper \begin_layout Itemize 同步IO文件 \begin_inset CommandInset label LatexCommand label name "ite:同步IO文件" \end_inset \end_layout \begin_deeper \begin_layout Standard 在文件路径前加上一个"-"就打开了同步IO选项。在打开文件(open)的时候,会以O_SYNC选项打开,这时候每次写日志操作都会等操作系统把数据写到硬盘后才返回 。这个选项极为耗时: \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 100 1000 \end_layout \begin_layout LyX-Code real 0m0.732s \end_layout \begin_layout LyX-Code user 0m1.030s \end_layout \begin_layout LyX-Code sys 0m1.080s \end_layout \begin_layout LyX-Code $ time ./test_press_zlog 100 1000 # synchronous I/O open \end_layout \begin_layout LyX-Code real 0m20.646s \end_layout \begin_layout LyX-Code user 0m2.570s \end_layout \begin_layout LyX-Code sys 0m6.950s \end_layout \end_deeper \end_deeper \begin_layout Itemize 格式名 \end_layout \begin_deeper \begin_layout Standard 是可选的,如果不写,用全局配置里面的默认格式: \end_layout \begin_layout LyX-Code [global] \end_layout \begin_layout LyX-Code default format = "%d(%F %T) %V [%p:%F:%L] %m%n" \end_layout \end_deeper \begin_layout Itemize 用户自定义输出详见 \begin_inset CommandInset ref LatexCommand ref reference "sec:用户自定义输出" \end_inset \end_layout \begin_layout Section 文件转档 \begin_inset CommandInset label LatexCommand label name "sec:文件转档" \end_inset \end_layout \begin_layout Standard 为什么需要将日志文件转档?我已经在实际的运行环境中不止一次的看到过,因为日志文件过大,导致系统硬盘被撑爆,或者单个日志文件过大而即使用grep也要花费很多时间来 寻找匹配的日志。对于日志转档,我总结了如下几种范式: \end_layout \begin_layout Enumerate 按固定时间段来切分日志。 \end_layout \begin_deeper \begin_layout Standard 例如,每天生成一个日志 \end_layout \begin_layout LyX-Code aa.2012-08-02.log \end_layout \begin_layout LyX-Code aa.2012-08-03.log \end_layout \begin_layout LyX-Code aa.2012-08-04.log \end_layout \begin_layout Standard 这种日志适合的场景是,管理员大概知道每天生成的日志量,然后希望在n个月之后能精确的找出某天的所有日志。这种日志切分最好由日志库来完成,其次的方法是用cronos plit这种软件来分析日志内容的时间字符串来进行后期的切分,较差的办法是用crontab+logrotate或mv来定期移动(但这并不精确,会造成若干条当天的日 志被放到上一天的文件里面去)。 \end_layout \begin_layout Standard 在zlog里面,这种需求不需要用日志转档功能来完成,简单的在日志文件名里面设置时间日期字符串就能解决问题: \end_layout \begin_layout LyX-Code *.* "aa.%d(%F).log" \end_layout \begin_layout Standard 或者用cronolog来完成,速度会更快一点 \end_layout \begin_layout LyX-Code *.* | cronolog aa.%F.log \end_layout \end_deeper \begin_layout Enumerate 按照日志大小切分 \end_layout \begin_deeper \begin_layout Standard 多用于开发环境,适合的场景是,程序在短时间内生成大量的日志,而用编辑器vi,ue等能快速打开的日志大小是有限的,或者大的日志打开来极慢。同样的,这种日志的切分可 以在事后用split等工具来完成,但对于开发而言会增加步骤,所以最好也是由日志库来完成。值得一提的是存档有两种模式,nlog里面称之为Sequence和Roll ing,在Sequence情况下 \end_layout \begin_layout LyX-Code aa.log (new) \end_layout \begin_layout LyX-Code aa.log.2 (less new) \end_layout \begin_layout LyX-Code aa.log.1 \end_layout \begin_layout LyX-Code aa.log.0 (old) \end_layout \begin_layout Standard 而在Rolling的情况下 \end_layout \begin_layout LyX-Code aa.log (new) \end_layout \begin_layout LyX-Code aa.log.0 (less new) \end_layout \begin_layout LyX-Code aa.log.1 \end_layout \begin_layout LyX-Code aa.log.2 (old) \end_layout \begin_layout Standard 很难说哪种更加符合人的直觉。 \end_layout \begin_layout Standard 如果只有若干个最新的文件是有意义的,需要日志库来做主动的删除旧的工作。由外部程序是很难判定哪些日志是旧的。 \end_layout \begin_layout Standard 最简单的zlog的转档配置为 \end_layout \begin_layout LyX-Code *.* "aa.log", 10MB \end_layout \begin_layout Standard 这个配置是Rolling的情况,每次aa.log超过10MB的时候,会做这样的重命名 \end_layout \begin_layout LyX-Code aa.log.2 -> aa.log.3 \end_layout \begin_layout LyX-Code aa.log.1 -> aa.log.2 \end_layout \begin_layout LyX-Code aa.log.0 -> aa.log.1 \end_layout \begin_layout LyX-Code aa.log -> aa.log.0 \end_layout \begin_layout Standard 上面的配置可以写的更加罗嗦一点 \end_layout \begin_layout LyX-Code *.* "aa.log", 10MB * 0 ~ "aa.log.#r" \end_layout \begin_layout Standard 逗号后第一个参数表示文件达到多大后开始进行转档。 \end_layout \begin_layout Standard 第二个参数表示保留多少个存档文件(0代表不删除任何存档文件)。 \end_layout \begin_layout Standard 第三个参数表示转档的文件名,其中#r表示存档文件的序号,r是rolling的缩写。还可以放#s,是sequence的缩写。转档文件名必须包含#r或者#s。 \end_layout \end_deeper \begin_layout Enumerate 按照日志大小切分,但同时加上时间标签 \end_layout \begin_deeper \begin_layout LyX-Code aa.log \end_layout \begin_layout LyX-Code aa.log-20070305.00.log \end_layout \begin_layout LyX-Code aa.log-20070501.00.log \end_layout \begin_layout LyX-Code aa.log-20070501.01.log \end_layout \begin_layout LyX-Code aa.log-20071008.00.log \end_layout \begin_layout Standard 这种情况适合于程序本身的日志一般不是很受关注,但是又在某一天想要找出来看的情况。当然,在这种情况下,万一在20070501这一天日志的量超过了指定值,例如100 MB,就又要退回到第二种状态,在文件名中加后缀。 \end_layout \begin_layout Standard zlog对应的配置是 \end_layout \begin_layout LyX-Code *.* "aa.log", 100MB ~ "aa-%d(%Y%m%d).#2s.log" \end_layout \begin_layout Standard 每到100MB的时候转档,转档文件名也支持转换字符,可以把转档当时的时间串作为转档文件名的一部分。#2s的意思是序号的长度最少为2位,从00开始编号,Seque nce转档。这是zlog对转档最复杂的支持了! \end_layout \end_deeper \begin_layout Enumerate 压缩、移动、删除旧的日志 \end_layout \begin_deeper \begin_layout Standard 首先,压缩不应该由日志库来完成,因为压缩消耗时间和CPU。日志库的任务是配合压缩。 \end_layout \begin_layout Standard 对于第一种和第三种,管理较为简单,只要符合某些文件名规则或修改日期的,可以用shell脚本+crontab轻易的压缩、移动和删除。 \end_layout \begin_layout Standard 对于第二种,其实不是非常需要压缩,只需要删除就可以了。 \end_layout \begin_layout Standard 如果一定需要转档的同时进行压缩,只有logrotate能干这活儿,毕竟他是独立的程序,能在转档同时搞压缩,不会有混淆的问题。 \end_layout \end_deeper \begin_layout Enumerate zlog对外部转档工具,例如logrotate的支持 \end_layout \begin_deeper \begin_layout Standard zlog的转档功能已经极为强大,当然也有几种情况是zlog无法处理的,例如按时间条件进行转档,转档前后调用一些自制的shell脚本……这会把zlog的配置和表达 弄得过于复杂而缺乏美感。 \end_layout \begin_layout Standard 这时候你也许喜欢用一些外部转档工具,例如logrotate来完成工作。问题是,在linux操作系统下,转档工具重命名日志文件名后,应用进程还是往原来的文件描述符 写日志,没办法重新打开日志文件写新的日志。标准的做法是给应用程序一个信号,让他重新打开日志文件,对于syslogd是 \end_layout \begin_layout LyX-Code kill -SIGHUP `cat /var/run/syslogd.pid` \end_layout \begin_layout Standard 对于zlog,因为是个函数库,不适合接受信号。zlog提供了函数接口zlog_reload(),这个函数会重载配置文件,重新打开所有的日志文件。应用程序在log rotate的信号,或者其他途径,例如客户端的命令后,可以调用这个函数,来重新打开所有的日志文件。 \end_layout \end_deeper \begin_layout Section 配置文件工具 \end_layout \begin_layout LyX-Code $ zlog-chk-conf -h \end_layout \begin_layout LyX-Code Useage: zlog-chk-conf [conf files]... \end_layout \begin_layout LyX-Code -q, suppress non-error message \end_layout \begin_layout LyX-Code -h, show help message \end_layout \begin_layout Standard zlog-chk-conf 尝试读取配置文件,检查语法,然后往屏幕上输出这些配置文件是否正确。我建议每次创建或者改动一个配置文件之后都用一下这个工具。输出可能是 这样: \end_layout \begin_layout LyX-Code $ ./zlog-chk-conf zlog.conf \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:rule.c:391) sscanf [aaa] fail, category or level is null \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:conf.c:155) zlog_rule_new fail [aaa] \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:conf.c:258) parse configure file[zlog.conf] line[126] fail \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:conf.c:306) zlog_conf_read_config fail \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:conf.c:366) zlog_conf_build fail \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:zlog.c:66) conf_file[zlog.conf], init conf fail \end_layout \begin_layout LyX-Code 03-08 15:35:44 ERROR (10595:zlog.c:131) zlog_init_inner[zlog.conf] fail \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code ---[zlog.conf] syntax error, see error message above \end_layout \begin_layout Standard 这个告诉你配置文件zlog.conf的126行,是错的。第一行进一步告诉你[aaa]不是一条正确的规则。 \end_layout \begin_layout Standard zlog-chk-conf可以同时分析多个配置文件,举例: \end_layout \begin_layout LyX-Code $ zlog-chk-conf zlog.conf ylog.conf \end_layout \begin_layout LyX-Code --[zlog.conf] syntax right \end_layout \begin_layout LyX-Code --[ylog.conf] syntax right \end_layout \begin_layout LyX-Code \end_layout \begin_layout Chapter zlog接口(API) \end_layout \begin_layout Standard zlog的所有函数都是线程安全的,使用的时候只需要 \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout Section 初始化和清理 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code int zlog_init(const char * \bar under confpath \bar default ); \end_layout \begin_layout LyX-Code int zlog_reload(const char * \bar under confpath \bar default ); \end_layout \begin_layout LyX-Code void zlog_fini(void); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard zlog_init()从配置文件 \bar under confpath \bar default 中读取配置信息到内存。如果 \bar under confpath \bar default 为NULL,会寻找环境变量ZLOG_CONF_PATH的值作为配置文件名。如果环境变量ZLOG_CONF_PATH也没有,所有日志以内置格式写到标准输出上。每个 进程只有第一次调用zlog_init()是有效的,后面的多余调用都会失败并不做任何事情。 \end_layout \begin_layout Standard zlog_reload()从 \bar under confpath \bar default 重载配置,并根据这个配置文件来重计算内部的分类规则匹配、重建每个线程的缓存、并设置原有的用户自定义输出函数。可以在配置文件发生改变后调用这个函数。这个函数使用次 数不限。如果 \bar under confpath \bar default 为NULL,会重载上一次zlog_init()或者zlog_reload()使用的配置文件。如果zlog_reload()失败,上一次的配置依然有效。所以zlo g_reload()具有原子性。 \end_layout \begin_layout Standard zlog_fini()清理所有zlog API申请的内存,关闭它们打开的文件。使用次数不限。 \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard 如果成功,zlog_init()和zlog_reload()返回0。失败的话,zlog_init()和zlog_reload()返回-1。详细错误会被写在由环境 变量ZLOG_PROFILE_ERROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section 分类(Category)操作 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code typedef struct zlog_category_s zlog_category_t; \end_layout \begin_layout LyX-Code zlog_category_t *zlog_get_category(const char * \bar under cname \bar default ); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard zlog_get_category()从zlog的全局分类表里面找到分类,用于以后输出日志。如果没有的话,就建一个。然后它会遍历所有的规则,寻找和 \bar under cname \bar default 匹配的规则并绑定。 \end_layout \begin_layout Standard 配置文件规则中的分类名匹配 \bar under cname \bar default 的规律描述如下: \end_layout \begin_layout Enumerate * 匹配任意 \bar under cname \bar default 。 \end_layout \begin_layout Enumerate 以下划线_结尾的分类名同时匹配本级分类和下级分类。例如aa_匹配aa, aa_, aa_bb, aa_bb_cc这几个 \bar under cname \bar default 。 \end_layout \begin_layout Enumerate 不以下划线_结尾的分类名精确匹配 \bar under cname \bar default 。例如aa_bb匹配aa_bb这个 \bar under cname \bar default 。 \end_layout \begin_layout Enumerate ! 匹配目前还没有规则的 \bar under cname \bar default 。 \end_layout \begin_layout Standard 每个zlog_category_t *对应的规则,在zlog_reload()的时候会被自动重新计算。不用担心内存释放,zlog_fini() 最后会清理一切。 \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard 如果成功,返回zlog_category_t的指针。如果失败,返回NULL。详细错误会被写在由环境变量ZLOG_PROFILE_ERROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section 写日志函数及宏 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code void zlog(zlog_category_t * \bar under category \bar default , \end_layout \begin_layout LyX-Code const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const char * \bar under format \bar default , ...); \end_layout \begin_layout LyX-Code void vzlog(zlog_category_t * \bar under category \bar default , \end_layout \begin_layout LyX-Code const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const char * \bar under format \bar default , va_list \bar under args \bar default ); \end_layout \begin_layout LyX-Code void hzlog(zlog_category_t * \bar under category \bar default , \end_layout \begin_layout LyX-Code const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const void * \bar under buf \bar default , size_t \bar under buflen \bar default ); \end_layout \begin_layout LyX-Code \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard 这3个函数是实际写日志的函数,输入的数据对应于配置文件中的%m。 \bar under category \bar default 来自于调用zlog_get_category()。 \end_layout \begin_layout Standard zlog()和vzlog()根据format输出,就像printf(3)和vprintf(3)。 \end_layout \begin_layout Standard vzlog()相当于zlog(),只是它用一个va_list类型的参数args,而不是一堆类型不同的参数。vzlog() 内部使用了 va_copy 宏,args的内容在vzlog()后保持不变,可以参考stdarg(3)。 \end_layout \begin_layout Standard hzlog()有点不一样,它产生下面这样的输出,长度为 \bar under buf_len \bar default 的内存 \bar under buf \bar default 以16进制的形式表示出来 \emph on 。 \end_layout \begin_layout LyX-Code hex_buf_len=[5365] \end_layout \begin_layout LyX-Code 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDE F \end_layout \begin_layout LyX-Code 0000000001 23 21 20 2f 62 69 6e 2f 62 61 73 68 0a 0a 23 20 #! /bin/bash..# \end_layout \begin_layout LyX-Code 0000000002 74 65 73 74 5f 68 65 78 20 2d 20 74 65 6d 70 6f test_hex - tempo \end_layout \begin_layout LyX-Code 0000000003 72 61 72 79 20 77 72 61 70 70 65 72 20 73 63 72 rary wrapper scr \end_layout \begin_layout Standard 参数 \bar under file \bar default 和 \bar under line \bar default 填写为__FILE__和__LINE__这两个宏。这两个宏标识日志是在哪里发生的。参数 \bar under func \bar default 填写为__func__或者__FUNCTION__,如果编译器支持的话,如果不支持,就填写为""。 \end_layout \begin_layout Standard \bar under level \bar default 是一个整数,应该是在下面几个里面取值。 \end_layout \begin_layout LyX-Code typedef enum { \end_layout \begin_deeper \begin_layout LyX-Code ZLOG_LEVEL_DEBUG = 20, \end_layout \begin_layout LyX-Code ZLOG_LEVEL_INFO = 40, \end_layout \begin_layout LyX-Code ZLOG_LEVEL_NOTICE = 60, \end_layout \begin_layout LyX-Code ZLOG_LEVEL_WARN = 80, \end_layout \begin_layout LyX-Code ZLOG_LEVEL_ERROR = 100, \end_layout \begin_layout LyX-Code ZLOG_LEVEL_FATAL = 120 \end_layout \end_deeper \begin_layout LyX-Code } zlog_level; \end_layout \begin_layout Standard 每个函数都有对应的宏,简单使用。例如: \end_layout \begin_layout LyX-Code #define zlog_fatal(cat, format, args...) \backslash \end_layout \begin_layout LyX-Code zlog(cat, __FILE__, sizeof(__FILE__)-1, \backslash \end_layout \begin_layout LyX-Code __func__, sizeof(__func__)-1, __LINE__, \backslash \end_layout \begin_layout LyX-Code ZLOG_LEVEL_FATAL, format, ##args) \end_layout \begin_layout Standard 所有的宏列表: \end_layout \begin_layout LyX-Code /* zlog macros */ \end_layout \begin_layout LyX-Code zlog_fatal(cat, format, ...) \end_layout \begin_layout LyX-Code zlog_error(cat, format, ...) \end_layout \begin_layout LyX-Code zlog_warn(cat, format, ...) \end_layout \begin_layout LyX-Code zlog_notice(cat, format, ...) \end_layout \begin_layout LyX-Code zlog_info(cat, format, ...) \end_layout \begin_layout LyX-Code zlog_debug(cat, format, ...) \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code /* vzlog macros */ \end_layout \begin_layout LyX-Code vzlog_fatal(cat, format, args) \end_layout \begin_layout LyX-Code vzlog_error(cat, format, args) \end_layout \begin_layout LyX-Code vzlog_warn(cat, format, args) \end_layout \begin_layout LyX-Code vzlog_notice(cat, format, args) \end_layout \begin_layout LyX-Code vzlog_info(cat, format, args) \end_layout \begin_layout LyX-Code vzlog_debug(cat, format, args) \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code /* hzlog macros */ \end_layout \begin_layout LyX-Code hzlog_fatal(cat, buf, buf_len) \end_layout \begin_layout LyX-Code hzlog_error(cat, buf, buf_len) \end_layout \begin_layout LyX-Code hzlog_warn(cat, buf, buf_len) \end_layout \begin_layout LyX-Code hzlog_notice(cat, buf, buf_len) \end_layout \begin_layout LyX-Code hzlog_info(cat, buf, buf_len) \end_layout \begin_layout LyX-Code hzlog_debug(cat, buf, buf_len) \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard 这些函数不返回。如果有错误发生,详细错误会被写在由环境变量ZLOG_PROFILE_ERROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section MDC操作 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code int zlog_put_mdc(const char * \bar under key \bar default , const char * \bar under value \bar default ); \end_layout \begin_layout LyX-Code char *zlog_get_mdc(const char * \bar under key \bar default ); \end_layout \begin_layout LyX-Code void zlog_remove_mdc(const char * \bar under key \bar default ); \end_layout \begin_layout LyX-Code void zlog_clean_mdc(void); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard MDC(Mapped Diagnostic Context)是一个每线程拥有的键-值表,所以和分类没什么关系。 \end_layout \begin_layout Standard \bar under key \bar default 和 \bar under value \bar default 是字符串,长度不能超过MAXLEN_PATH(1024)。如果超过MAXLEN_PATH(1024)的话,会被截断。 \end_layout \begin_layout Standard 记住这个表是和线程绑定的,每个线程有自己的表,所以在一个线程内的调用不会影响其他线程。 \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard zlog_put_mdc()成功返回0,失败返回-1。zlog_get_mdc()成功返回value的指针,失败或者没有相应的key返回NULL。如果有错误发生 ,详细错误会被写在由环境变量ZLOG_PROFILE_ERROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section dzlog接口 \begin_inset CommandInset label LatexCommand label name "sec:dzlog接口" \end_inset \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code int dzlog_init(const char * \bar under confpath \bar default , const char * \bar under cname \bar default ); \end_layout \begin_layout LyX-Code int dzlog_set_category(const char * \bar under cname \bar default ); \end_layout \begin_layout LyX-Code void dzlog(const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const char * \bar under format \bar default , ...); \end_layout \begin_layout LyX-Code void vdzlog(const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const char * \bar under format \bar default , va_list \bar under args \bar default ); \end_layout \begin_layout LyX-Code void hdzlog(const char * \bar under file \bar default , size_t \bar under filelen \bar default , \end_layout \begin_layout LyX-Code const char * \bar under func \bar default , size_t \bar under funclen \bar default , \end_layout \begin_layout LyX-Code long \bar under line \bar default , int \bar under level \bar default , \end_layout \begin_layout LyX-Code const void * \bar under buf \bar default , size_t \bar under buflen \bar default ); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard dzlog是忽略分类(zlog_category_t)的一组简单zlog接口。它采用内置的一个默认分类,这个分类置于锁的保护下。这些接口也是线程安全的。忽略了分 类,意味着用户不需要操心创建、存储、传输zlog_category_t类型的变量 \emph on \noun on 。当然也可以在用 \emph default \noun default dzlog接口的同时用一般的zlog接口函数,这样会更爽。 \end_layout \begin_layout Standard dzlog_init()和zlog_init()一样做初始化,就是多需要一个默认分类名 \bar under cname \bar default 的参数。zlog_reload()、 zlog_fini() 可以和以前一样使用,用来刷新配置,或者清理。 \end_layout \begin_layout Standard dzlog_set_category()是用来改变默认分类用的。上一个分类会被替换成新的。同样不用担心内存释放的问题,zlog_fini()最后会清理。 \end_layout \begin_layout Standard dzlog的宏也定义在zlog.h里面。更简单的写法。 \end_layout \begin_layout LyX-Code dzlog_fatal(format, ...) \end_layout \begin_layout LyX-Code dzlog_error(format, ...) \end_layout \begin_layout LyX-Code dzlog_warn(format, ...) \end_layout \begin_layout LyX-Code dzlog_notice(format, ...) \end_layout \begin_layout LyX-Code dzlog_info(format, ...) \end_layout \begin_layout LyX-Code dezlog_debug(format, ...) \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code vdzlog_fatal(format, args) \end_layout \begin_layout LyX-Code vdzlog_error(format, args) \end_layout \begin_layout LyX-Code vdzlog_warn(format, args) \end_layout \begin_layout LyX-Code vdzlog_notice(format, args) \end_layout \begin_layout LyX-Code vdzlog_info(format, args) \end_layout \begin_layout LyX-Code vdzlog_debug(format, args) \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code hdzlog_fatal(buf, buf_len) \end_layout \begin_layout LyX-Code hdzlog_error(buf, buf_len) \end_layout \begin_layout LyX-Code hdzlog_warn(buf, buf_len) \end_layout \begin_layout LyX-Code hdzlog_noticebuf, buf_len) \end_layout \begin_layout LyX-Code hdzlog_info(buf, buf_len) \end_layout \begin_layout LyX-Code hdzlog_debug(buf, buf_len) \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard 成功情况下dzlog_init()和dzlog_set_category()返回0。失败情况下dzlog_init()和 dzlog_set_category( )返回-1。详细错误会被写在由环境变量ZLOG_PROFILE_ERROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section 用户自定义输出 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code typedef struct zlog_msg_s { \end_layout \begin_layout LyX-Code char *buf; \end_layout \begin_layout LyX-Code size_t len; \end_layout \begin_layout LyX-Code char *path; \end_layout \begin_layout LyX-Code } zlog_msg_t; \end_layout \begin_layout LyX-Code typedef int (*zlog_record_fn)(zlog_msg_t * \bar under msg \bar default ); \end_layout \begin_layout LyX-Code int zlog_set_record(const char * \bar under rname \bar default , zlog_record_fn \bar under record \bar default ); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard zlog允许用户自定义输出函数。输出函数需要绑定到某条特殊的规则上。这种规则的例子是: \end_layout \begin_layout LyX-Code *.* $name, "record path %c %d"; simple \end_layout \begin_layout Standard zlog_set_record()做绑定动作。规则中输出段有$ \bar under name \bar default 的,会被用来做用户自定义输出。输出函数为 \bar under record \bar default 。这个函数需要为zlog_record_fn的格式。 \end_layout \begin_layout Standard zlog_msg_t结构的各个成员描述如下: \end_layout \begin_layout Standard \bar under path \bar default 来自规则的逗号后的字符串,这个字符串会被动态的解析,输出当前的 \bar under path \bar default ,就像动态文件路径一样。 \end_layout \begin_layout Standard \bar under buf \bar default 和 \bar under len \bar default 是zlog格式化后的日志信息和长度。 \end_layout \begin_layout Standard 所有zlog_set_record()做的绑定在zlog_reload()使用后继续有效。 \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 返回值 \end_layout \begin_deeper \begin_layout Standard 成功情况下zlog_set_record()返回0。失败情况下zlog_set_record()返回-1。详细错误会被写在由环境变量ZLOG_PROFILE_E RROR指定的错误日志里面。 \end_layout \end_deeper \begin_layout Section 调试和诊断 \end_layout \begin_layout Labeling \labelwidthstring 00.00.0000 总览 \end_layout \begin_deeper \begin_layout LyX-Code void zlog_profile(void); \end_layout \end_deeper \begin_layout Labeling \labelwidthstring 00.00.0000 描述 \end_layout \begin_deeper \begin_layout Standard 环境变量ZLOG_PROFILE_ERROR指定zlog本身的错误日志。 \end_layout \begin_layout Standard 环境变量ZLOG_PROFILE_DEBUG指定zlog本身的调试日志。 \end_layout \begin_layout Standard zlog_profile()打印所有内存中的配置信息到ZLOG_PROFILE_ERROR,在运行时。可以把这个和配置文件比较,看看有没有问题。 \end_layout \end_deeper \begin_layout Chapter 高阶使用 \end_layout \begin_layout Section MDC \begin_inset CommandInset label LatexCommand label name "sec:MDC" \end_inset \end_layout \begin_layout Standard MDC是什么?在log4j里面解释为Mapped Diagnostic Context。听起来是个很复杂的技术,其实MDC就是一个键-值对表。一旦某次你设置了, 后面库可以帮你自动打印出来,或者成为文件名的一部分。让我们看一个例子,来自于$(top_builddir)/test/test_mdc.c. \end_layout \begin_layout LyX-Code $ cat test_mdc.c \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code zlog_category_t *zc; \end_layout \begin_layout LyX-Code rc = zlog_init("test_mdc.conf"); \end_layout \begin_deeper \begin_layout LyX-Code if (rc) { \end_layout \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zc = zlog_get_category("my_cat"); \end_layout \begin_layout LyX-Code if (!zc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("get cat fail \backslash n"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return -2; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zlog_info(zc, "1.hello, zlog"); \end_layout \begin_layout LyX-Code zlog_put_mdc("myname", "Zhang"); \end_layout \begin_layout LyX-Code zlog_info(zc, "2.hello, zlog"); \end_layout \begin_layout LyX-Code zlog_put_mdc("myname", "Li"); \end_layout \begin_layout LyX-Code zlog_info(zc, "3.hello, zlog"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout Standard 配置文件 \end_layout \begin_layout LyX-Code $ cat test_mdc.conf \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code mdc_format= "%d(%F %X.%ms) %-6V (%c:%F:%L) [%M(myname)] - %m%n" \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code *.* >stdout; mdc_format \end_layout \begin_layout Standard 输出 \end_layout \begin_layout LyX-Code $ ./test_mdc \end_layout \begin_layout LyX-Code 2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:47) [] - 1.hello, zlog \end_layout \begin_layout LyX-Code 2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:51) [Zhang] - 2.hello, zlog \end_layout \begin_layout LyX-Code 2012-03-12 09:26:37.740 INFO (my_cat:test_mdc.c:55) [Li] - 3.hello, zlog \end_layout \begin_layout Standard 你可以看到zlog_put_mdc()在表里面设置键"myname"对应值"Zhang",然后在配置文件里面%M(myname)指出了在日志的哪个位置需要把值打 出来。第二次,键"myname"的值被覆盖写成"Li",然后日志里面也有相应的变化。 \end_layout \begin_layout Standard MDC什么时候有用呢?往往在用户需要在同样的日志行为区分不同的业务数据的时候。比如说,c源代码是 \end_layout \begin_layout LyX-Code zlog_put_mdc("customer_name", get_customer_name_from_db() ); \end_layout \begin_layout LyX-Code zlog_info("get in"); \end_layout \begin_layout LyX-Code zlog_info("pick product"); \end_layout \begin_layout LyX-Code zlog_info("pay"); \end_layout \begin_layout LyX-Code zlog_info("get out"); \end_layout \begin_layout Standard 在配置文件里面是 \end_layout \begin_layout LyX-Code &format "%M(customer_name) %m%n" \end_layout \begin_layout Standard 当程序同时处理两个用户的时候,打出来的日志可能是 \end_layout \begin_layout LyX-Code Zhang get in \end_layout \begin_layout LyX-Code Li get in \end_layout \begin_layout LyX-Code Zhang pick product \end_layout \begin_layout LyX-Code Zhang pay \end_layout \begin_layout LyX-Code Li pick product \end_layout \begin_layout LyX-Code Li pay \end_layout \begin_layout LyX-Code Zhang get out \end_layout \begin_layout LyX-Code Li get out \end_layout \begin_layout Standard 这样,你就可以用grep命令把这两个用户的日志分开来了 \end_layout \begin_layout LyX-Code $ grep Zhang aa.log > Zhang.log \end_layout \begin_layout LyX-Code $ grep Li aa.log >Li.log \end_layout \begin_layout Standard 或者,还有另外一条路,一开始在文件名里面做区分,看配置文件: \end_layout \begin_layout LyX-Code *.* "mdc_%M(customer_name).log"; \end_layout \begin_layout Standard 这就会产生3个日志文件。 \end_layout \begin_layout LyX-Code mdc_.log mdc_Zhang.log mdc_Li.log \end_layout \begin_layout Standard 这是一条近路,如果用户知道自己在干什么。 \end_layout \begin_layout Standard MDC是每个线程独有的,所以可以把一些线程专有的变量设置进去。如果单单为了区分线程,可以用转换字符里面的%t来搞定。 \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code \end_layout \begin_layout Section 诊断zlog本身 \begin_inset CommandInset label LatexCommand label name "sec:诊断zlog本身" \end_inset \end_layout \begin_layout Standard OK,至今为止,我假定zlog库本身是不出毛病的。zlog帮助用户程序写日志,帮助程序员debug程序。但是如果zlog内部出错了呢?怎么知道错在哪里呢?其他的 程序可以用日志库来debug,但日志库自己怎么debug?答案很简单,zlog有自己的日志——诊断日志。这个日志通常是关闭的,可以通过环境变量来打开。 \end_layout \begin_layout LyX-Code $ export ZLOG_PROFILE_DEBUG=/tmp/zlog.debug.log \end_layout \begin_layout LyX-Code $ export ZLOG_PROFILE_ERROR=/tmp/zlog.error.log \end_layout \begin_layout Standard 诊断日志只有两个级别debug和error。设置好环境变量后. 再跑test_hello程序 \begin_inset CommandInset ref LatexCommand ref reference "sec:Hello-World-代码" \end_inset ,然后debug日志为 \end_layout \begin_layout LyX-Code $ more zlog.debug.log \end_layout \begin_layout LyX-Code 03-13 09:46:56 DEBUG (7503:zlog.c:115) ------zlog_init start, compile time[Mar 13 2012 11:28:56]------ \end_layout \begin_layout LyX-Code 03-13 09:46:56 DEBUG (7503:spec.c:825) spec:[0x7fdf96b7c010][%d(%F %T)][%F %T 29][] \end_layout \begin_layout LyX-Code 03-13 09:46:56 DEBUG (7503:spec.c:825) spec:[0x7fdf96b52010][ ][ 0][] \end_layout \begin_layout LyX-Code ...... \end_layout \begin_layout LyX-Code 03-13 09:52:40 DEBUG (8139:zlog.c:291) ------zlog_fini end------ \end_layout \begin_layout Standard zlog.error.log日志没产生,因为没有错误发生。 \end_layout \begin_layout Standard 你可以看出来,debug日志展示了zlog是怎么初始化还有清理的。不过在zlog_info()执行的时候没有日志打出来,这是为了效率。 \end_layout \begin_layout Standard 如果zlog库有任何问题,都会打日志到ZLOG_PROFILE_ERROR所指向的错误日志。比如说,在zlog_info()上用一个错误的printf的语法: \end_layout \begin_layout LyX-Code zlog_info(zc, "%l", 1); \end_layout \begin_layout Standard 然后编译执行程序,ZLOG_PROFILE_ERROR的日志会是 \end_layout \begin_layout LyX-Code $ cat zlog.error.log \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:buf.c:189) vsnprintf fail, errno[0] \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:buf.c:191) nwrite[-1], size_left[1024], format[%l] \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:spec.c:329) zlog_buf_vprintf maybe fail or overflow \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:spec.c:467) a_spec->gen_buf fail \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:format.c:160) zlog_spec_gen_msg fail \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:rule.c:265) zlog_format_gen_msg fail \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:category.c:164) hzb_log_rule_output fail \end_layout \begin_layout LyX-Code 03-13 10:04:58 ERROR (10102:zlog.c:632) zlog_output fail, srcfile[test_hello.c], srcline[41] \end_layout \begin_layout Standard 这样,用户就能知道为啥期待的输出没有产生,然后搞定这个问题。 \end_layout \begin_layout Standard 运行时诊断会带来一定的性能损失。一般来说,我在生产环境把ZLOG_PROFILE_ERROR打开,ZLOG_PROFILE_DEBUG关闭。 \end_layout \begin_layout Standard 还有另外一个办法来诊断zlog。我们都知道,zlog_init()会把配置信息读入内存。在整个写日志的过程中,这块内存保持不变。如果用户程序因为某种原因损坏了这 块内存,那么就会造成问题。还有可能是内存中的信息和配置文件的信息不匹配。所以我设计了一个函数,把内存的信息展现到ZLOG_PROFILE_ERROR指向的错误日 志。 \end_layout \begin_layout Standard 代码见$(top_builddir)/test/test_profile.c \end_layout \begin_layout LyX-Code $ cat test_profile.c \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code rc = dzlog_init("test_profile.conf", "my_cat"); \end_layout \begin_layout LyX-Code if (rc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code dzlog_info("hello, zlog"); \end_layout \begin_layout LyX-Code zlog_profile(); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout Standard zlog_profile()就是这个函数。配置文件很简单。 \end_layout \begin_layout LyX-Code $ cat test_profile.conf \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code simple = "%m%n" \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.* >stdout; simple \end_layout \begin_layout Standard 然后zlog.error.log会是 \end_layout \begin_layout LyX-Code $ cat /tmp/zlog.error.log \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:zlog.c:783) ------zlog_profile start------ \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:zlog.c:784) init_flag:[1] \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:75) -conf[0x2333010]- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:76) --global-- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:77) ---file[test_profile.conf],mtime[2012-06-01 11:20:44]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:78) ---strict init[1]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:79) ---buffer min[1024]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:80) ---buffer max[2097152]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:82) ---default_format--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:format.c:48) ---format[0x235ef60][default = %d(%F %T) %V [%p:%F:%L] %m%n(0x233b810)]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:85) ---file perms[0600]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:conf.c:87) ---rotate lock file[/tmp/zlog.lock]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:rotater.c:48) --rotater[0x233b7d0][0x233b7d0,/tmp/zlog. lock,4]-- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level_list.c:37) --level_list[0x2335490]-- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x23355c0][0,*,*,1,6]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x23375e0][20,DEBUG,debug,5,7]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x2339600][40,INFO,info,4,6]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233b830][60,NOTICE,notice,6,5]- -- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233d850][80,WARN,warn,4,4]--- \end_layout \begin_layout LyX-Code 06-01 11:21:26 WARN (7063:level.c:37) ---level[0x233fc80][100,ERROR,error,5,3]-- - \end_layout \begin_layout Section 用户自定义等级 \begin_inset CommandInset label LatexCommand label name "sec:用户自定义等级" \end_inset \end_layout \begin_layout Standard 这里我把用户自定义等级的几个步骤写下来。 \end_layout \begin_layout Enumerate 在配置文件中定义新的等级 \end_layout \begin_deeper \begin_layout LyX-Code $ cat $(top_builddir)/test/test_level.conf \end_layout \begin_layout LyX-Code [global] \end_layout \begin_layout LyX-Code default format = "%V %v %m%n" \end_layout \begin_layout LyX-Code [levels] \end_layout \begin_layout LyX-Code TRACE = 30, LOG_DEBUG \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.TRACE >stdout; \end_layout \begin_layout Standard 内置的默认等级是(这些不需要写在配置文件里面) \end_layout \begin_layout LyX-Code DEBUG = 20, LOG_DEBUG \end_layout \begin_layout LyX-Code INFO = 40, LOG_INFO \end_layout \begin_layout LyX-Code NOTICE = 60, LOG_NOTICE \end_layout \begin_layout LyX-Code WARN = 80, LOG_WARNING \end_layout \begin_layout LyX-Code ERROR = 100, LOG_ERR \end_layout \begin_layout LyX-Code FATAL = 120, LOG_ALERT \end_layout \begin_layout LyX-Code UNKNOWN = 254, LOG_ERR \end_layout \begin_layout Standard 这样在zlog看来,一个整数(30)还有一个等级字符串(TRACE)代表了等级。这个整数必须位于[1,253]之间,其他数字是非法的。数字越大代表越重要。现在T RACE比DEBUG重要(30>20),比INFO等级低(30<40)。在这样的定义后,TRACE就可以在下面的配置文件里面用了。例如这句话: \end_layout \begin_layout LyX-Code my_cat.TRACE >stdout; \end_layout \begin_layout Standard 意味着等级>=TRACE的,包括INFO, NOTICE, WARN, ERROR, FATAL会被写到标准输出。 \end_layout \begin_layout Standard 格式里面的转换字符%V会产生等级字符串的大写输出,%v会产生小写的等级字符串输出。 \end_layout \begin_layout Standard 另外,在等级的定义里面,LOG_DEBUG是指当需要输出到syslog的时候,自定义的TRACE等级会以LOG_DEBUG输出到syslog。 \end_layout \end_deeper \begin_layout Enumerate 在源代码里面直接用新的等级是这么搞的 \end_layout \begin_deeper \begin_layout LyX-Code zlog(cat, __FILE__, sizeof(__FILE__)-1, \backslash \end_layout \begin_layout LyX-Code __func__, sizeof(__func__)-1,__LINE__, \backslash \end_layout \begin_layout LyX-Code 30, "test %d", 1); \end_layout \begin_layout Standard 为了简单使用,创建一个.h头文件 \end_layout \begin_layout LyX-Code $ cat $(top_builddir)/test/test_level.h \end_layout \begin_layout LyX-Code #ifndef __test_level_h \end_layout \begin_layout LyX-Code #define __test_level_h \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code enum { \end_layout \begin_deeper \begin_layout LyX-Code ZLOG_LEVEL_TRACE = 30, \end_layout \begin_layout LyX-Code /* must equals conf file setting */ \end_layout \end_deeper \begin_layout LyX-Code }; \end_layout \begin_layout LyX-Code #define zlog_trace(cat, format, ...) \backslash \end_layout \begin_layout LyX-Code zlog(cat, __FILE__, sizeof(__FILE__)-1, \backslash \end_layout \begin_layout LyX-Code __func__, sizeof(__func__)-1, __LINE__, \backslash \end_layout \begin_layout LyX-Code ZLOG_LEVEL_TRACE, format, ## __VA_ARGS__) \end_layout \begin_layout LyX-Code #endif \end_layout \end_deeper \begin_layout Enumerate 这样zlog_trace就能在.c文件里面用了 \end_layout \begin_deeper \begin_layout LyX-Code $ cat $(top_builddir)/test/test_level.c \end_layout \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "test_level.h" \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code zlog_category_t *zc; \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code rc = zlog_init("test_level.conf"); \end_layout \begin_layout LyX-Code if (rc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zc = zlog_get_category("my_cat"); \end_layout \begin_layout LyX-Code if (!zc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("get cat fail \backslash n"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return -2; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zlog_trace(zc, "hello, zlog - trace"); \end_layout \begin_layout LyX-Code zlog_debug(zc, "hello, zlog - debug"); \end_layout \begin_layout LyX-Code zlog_info(zc, "hello, zlog - info"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \end_deeper \begin_layout Enumerate 最后我们能看到输出 \end_layout \begin_deeper \begin_layout LyX-Code $ ./test_level \end_layout \begin_layout LyX-Code TRACE trace hello, zlog - trace \end_layout \begin_layout LyX-Code INFO info hello, zlog - info \end_layout \begin_layout Standard 正是我们所期待的,配置文件只允许>=TRACE等级的日志输出到屏幕上。%V和%v也显示了正确的结果。 \end_layout \end_deeper \begin_layout Section 用户自定义输出 \begin_inset CommandInset label LatexCommand label name "sec:用户自定义输出" \end_inset \end_layout \begin_layout Standard 用户自定义输出的意义是zlog放弃一些权力。zlog只负责动态生成单条日志和文件路径,但怎么输出、转档、清理等等工作由用户按照自己的需求自行写函数完成。写完函数 只要绑定到某个规则就可以。这里我把用户自定义输出的几个步骤写下来。 \end_layout \begin_layout Enumerate 在配置文件里面定义规则 \end_layout \begin_deeper \begin_layout LyX-Code $ cat test_record.conf \end_layout \begin_layout LyX-Code [formats] \end_layout \begin_layout LyX-Code simple = "%m%n" \end_layout \begin_layout LyX-Code [rules] \end_layout \begin_layout LyX-Code my_cat.* $myoutput, " mypath %c %d";simple \end_layout \end_deeper \begin_layout Enumerate 绑定一个函数到$myoutput,并使用之 \end_layout \begin_deeper \begin_layout LyX-Code #include \end_layout \begin_layout LyX-Code #include "zlog.h" \end_layout \begin_layout LyX-Code int output(zlog_msg_t *msg) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code printf("[mystd]:[%s][%s][%ld] \backslash n", msg->path, msg->buf, (long)msg->len); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code \end_layout \begin_layout LyX-Code int main(int argc, char** argv) \end_layout \begin_layout LyX-Code { \end_layout \begin_deeper \begin_layout LyX-Code int rc; \end_layout \begin_layout LyX-Code zlog_category_t *zc; \end_layout \begin_deeper \begin_layout LyX-Code rc = zlog_init("test_record.conf"); \end_layout \begin_layout LyX-Code if (rc) { \end_layout \begin_layout LyX-Code printf("init failed \backslash n"); \end_layout \begin_layout LyX-Code return -1; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zlog_set_record("myoutput", output); \end_layout \begin_layout LyX-Code zc = zlog_get_category("my_cat"); \end_layout \begin_layout LyX-Code if (!zc) { \end_layout \begin_deeper \begin_layout LyX-Code printf("get cat fail \backslash n"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return -2; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \begin_layout LyX-Code zlog_info(zc, "hello, zlog"); \end_layout \begin_layout LyX-Code zlog_fini(); \end_layout \begin_layout LyX-Code return 0; \end_layout \end_deeper \begin_layout LyX-Code } \end_layout \end_deeper \begin_layout Enumerate 最后我们发现用户自定义输出的函数能用了! \end_layout \begin_deeper \begin_layout LyX-Code $ ./test_record \end_layout \begin_layout LyX-Code [mystd]:[ mypath my_cat 2012-07-19 12:23:08][hello, zlog \end_layout \begin_layout LyX-Code ][12] \end_layout \begin_layout Standard 正如你所见,msglen是12,zlog生成的msg在最后有一个换行符。 \end_layout \end_deeper \begin_layout Enumerate 用户自定义输出可以干很多神奇的事情,就像某个用户(flw@newsmth.net)的需求 \end_layout \begin_deeper \begin_layout Enumerate 日志文件名为 foo.log \end_layout \begin_layout Enumerate 如果 foo.log 超过 100M,则生成一个新文件,其中包含的就是 foo.log 目前的内容 而 foo.log 则变为空,重新开始增长 \end_layout \begin_layout Enumerate 如果距离上次生成文件时已经超过 5 分钟,则即使不到 100M,也应当生成一个新文件 \end_layout \begin_layout Enumerate 新文件名称可以自定义,比如加上设备名作为前缀、日期时间串作为后缀 \end_layout \begin_layout Enumerate 我希望这个新文件可以被压缩,以节省磁盘空间或者网络带宽。 \end_layout \begin_layout Standard 但愿他能顺利写出这个需求的代码!在多进程或者多线程的情况下!上帝保佑他! \end_layout \end_deeper \begin_layout Chapter 尾声 \end_layout \begin_layout Standard 好酒!所有人生问题的终极起源和终极答案。 \end_layout \begin_layout Right Address 荷马.辛普森 \end_layout \end_body \end_document