haogongju、人人IT网、59n南龙、360doc不要抄我的烂博客了,私人备忘用。
erlang本身提供了日志管理,但这与其它语言中常见的log4xxx的日志框架很不同:
日志文件是二进制的,查看起来很不方便;
每条日志跨多行文本,也不方便查找和分析。
总之,这自带的日志系统让用惯log4xxx系列的人用起来很不习惯。后来陆续有log4erl这样的应用,lager算是后来者,不过在性能等方面有独到之处,这是因为它使用parse_transformation机制的原因。
一、lager使用的步骤
直接在代码中用lager:debug, lager:info, lager:warning, lager:error系列函数输出日志就行了。这些函数的参数和io:format基本一样,除了一点,日志字符串结尾不用自己手动加回车。
麻烦的是其它工作,包括编译方式、参数配置,lager应用的启动,不过好在都是一次性的:
1. 编译选项 parse_transformation
最笨的办法是在每个模块的开头使用编译指令
-compile([{parse_transform, lager_transform}]).
所有要使用lager的模块都加上这个指令代码无疑是很烦的事情,好在erlang编译器提供了一个选项,
见compile。
当然如果用rebar的话更加简单,只要在rebar.config配置文件中添加:
{erl_opts, [
{parse_transform, lager_transform}]}.
这样的编译就会自动织入lager代码,不需要给每个模块添加编译指令了。
2. lager应用的启动
这包括它依赖的两个erlang的lib应用:syntax_tools和compiler,一般用发布工具发布时会自动包含进来。但是开发中我们一般比较少直接使用发布工具,所以erl启动时lager不会也随之自动启动,lager本身作为应用要预先启动,有很多办法:
1) 最直观的,我们当然可以用 application:start(lager) 手工启动lager应用。但是lager还依赖compiler和syntax_tools,这两个应用要先启动,lager才能正常启动。虽然在lager应用的lager.app文件有在applications属性中指出这些依赖,但是这似乎只对发布有效,application:start/1不会坚持这些依赖并启动这些依赖。
2) 这里的问题是application:start/1函数只负责启动指定的application,如果它依赖的其它applications没有启动,则启动过程失败,该函数返回值:{error, {not_started, DepApp}}。
如果erlang能提供一个启动应用及其依赖的方法就好了。
后来,lager模块为此提供了一个专门的start方法,负责启动它自己依赖的这两个应用:
lager:start().
下面是它的源代码,有时候我们自己写应用直接的互相依赖可以借鉴一下:
-export([start/0]).
start() -> start(lager).
start(App) ->
start_ok(App, application:start(App, permanent)).
start_ok(_App, ok) -> ok;
start_ok(_App, {error, {already_started, _App}}) -> ok;
start_ok(App, {error, {not_started, Dep}}) ->
ok = start(Dep),
start(App);
start_ok(App, {error, Reason}) ->
erlang:error({app_start_failed, App, Reason}).
3) 如果是用工具发布的应用系统,例如rebar发布配置文件(reltool.config)中,在'rel' 列出的应用里增加lager,这样,发布后lager会自动在系统初始化时启动,如下
{sys, [
{lib_dirs, ["../../", "../deps/"]},
{rel, "rts", "1",
[
kernel,
stdlib,
sasl,
lager,
rts
]},
3. lager的配置
可以在发布的app.config(后来rebar改名成sys.config)中配置。配置参数很多,日志输出级别,日志输出方式,如果是以文件的方式,又包括日志文件名,日志文件大小,循环日志文件等等。列出这些配置参数很枯燥,还是直接看手册。
二、其它技巧
1.日志的颜色区分
当然你也可以用unix管道grep出你关心的日志,不过如果是需要参照出错附近的日志时。有时候直接在控制台上看日志更糙猛快点。
这种情况下如果能在一大堆日志中用不同颜色标出不同等级的日志信息无疑是很节省眼力的。
lager提供了这种支持,但不幸的是erlang是从R16版开始才官方支持,R15B版本erlang控制台并不直接支持ANSI ASCII字符。这需要修改erlang源代码重新编译,不过还好这不是什么难事:
修改erts/emulator/drivers/unix/ttsl_drv.c源代码(R15B03版)第915行开始的几行代码。
将如下代码
} else if (ch == '\n' || ch == '\r') {
write_buf(lbuf + buffpos, lpos - buffpos);
outc('\r');
if (ch == '\n')
outc('\n');
改成:
} else if (ch == '\e' || ch == '\n' || ch == '\r') {
write_buf(lbuf + buffpos, lpos - buffpos);
if (ch == '\e') {
outc('\e');
} else {
outc('\r');
if (ch == '\n')
outc('\n');
}
重新编译的Erlang/OTP R15B就能支持彩色ASCII的控制台了。
最后修改lager的配置(lager.app.src),colored设为true。现在可以在erlang控制台上看到各种颜色的日志信息了,debug没有变化,info是亮白色,warning是黄色,error是红色,等等
这些颜色当然也是可以自定义的,来自用term上bbs时代,或者了解ASCII字符艺术的同学对此应该并不陌生。
参考:Support ANSI in the console
http://erlang.org/pipermail/erlang-patches/2012-November/003127.html
三、常见问题和陷阱
lager:info/debug/warning/error函数未定义错误
当运行时出现有如下片断的错误:
... {'EXIT',{undef,[{lager,info, ...
(其中的info可以是 debug, warning等)
这是因为编译时的parse_transform没有“正确”设置的缘故,原因很多,以下是可能碰到的两个陷阱:
1. 只是修改配置信息(如修改rebar.config文件,加入{erl_opts, [{parse_transform, lager_transform}]} ),然后rebar compile是不会自动重新编译之前已有的编译好的代码的,必须清空已编译好的代码重新编译才行:rebar clean compile
原因其实也简单:rebar compile只会根据源代码的时间戳判断是否有必要重新编译,而编译配置选项的修改显然与之无关。
2. 对于rebar.config里的erl_opts配置,顺序很重要,{parse_transform, lager_transform} 必须在其他erl_opts的前面。