让log4j和rsyslog在一起

  前两天用Java写了一个代付通道代理,因为合作方给的Demo样例就是Java的,所以基本现成的程序调试验证起来也很欢快,而且同主服务用Thrift进行耦合也毫无悬念。写好的代码可以同依赖库全部打包成一个jar文件,在生产上部署也是挺方便的。现在发现在金融领域Java的使用规模要比C++广泛的多,而我们之前处理这类需求,基本都是用C++把别人的样例翻译一遍,其中涉及到的签名加密算法都是用OpenSSL来实现的,有时候这玩意儿和Java有些差异性很难调试,而且OpenSSL本身技术性就强、文档也确实,导致这玩意儿不是一般的难写,更有甚者稍不注意就给你开个core dump或者内存泄漏。
  在Java下面开发,使用log4j基本是日志库事实上的标准了,而且通过配置文件可以自定义日志记录的字段、显示样式、日志文件的保存和管理等操作,可谓是功能强大、使用灵活。如果是本地日志处理基本是够用的,但是我们有也有专用的日志服务器来收集和归档,因此这个异类的服务也必须要能把日志转发到日志服务器才算是需求完整的。之前说过,我们系统重构之后使用了rsyslog,而log4j也对rsyslog提供了对接支持,由此可见选择一个主流的、标准化的组件比自造轮子更科学,特别是在公司研发能力有限、时间精力不足的时候,否则这种插屁股的事情真的是无底洞……
  关于log4j同rsyslog的对接,网上也有很多参考,但初次配置的时候还是有一些折腾的,这里描述一下配置过程和并且给出一个完整的配置,方便后面直接拿来用就可以了。
  log4j现在官方维护有1.x和2.x两个版本,并且他们之间的接口是不兼容的,虽然2.x对syslog提供了更友好的支持,但是为了和已有的程序、库相配合,我们选择了log4j 1.2.17最新的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
log4j.rootLogger=DEBUG, A1, SYSLOG

log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=/data/logs/log4j/java_agent_service.log
log4j.appender.A1.DatePattern='_'yyyy-MM-dd
log4j.appender.A1.Threshold=debug
log4j.appender.A1.encoding=UTF-8
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=java_agent_service [%d{ISO8601}]<%p> [%l][%C{1}][%t] - %m%n

log4j.appender.SYSLOG=org.apache.log4j.net.SyslogAppender
log4j.appender.SYSLOG.header=true
log4j.appender.SYSLOG.SyslogHost=127.0.0.1:8999
log4j.appender.SYSLOG.Facility=LOCAL6
log4j.appender.SYSLOG.Threshold=debug
log4j.appender.SYSLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSLOG.layout.ConversionPattern=java_agent_service[%l][%C{1}][%t] - %m%n

  上面的A1配置是让log4j的DailyRollingFileAppender直接将日志保存在本地,而且使用了按照日期rotate的方式将日志切分成每天一个文件。后面的SYSLOG配置是将log4j的SyslogAppender将日志转发一份到本机的rsyslog服务,需要注意的是log4j的SyslogAppender是使用UDP的方式进行转发的,虽然对接本机的rsyslog使用UDP问题不会太大,为了可能出现的日志丢失而使用A1将日志更可靠地存储一份看情况还是必要的。
  这里尤其需要注意的是在ConversionPattern前面添加一个自定义的字符串(比如服务或者程序的名字),因为我们在rsyslog中是按照programname进行分组的,所以如果用了后面的rsyslog配置但是这里没有添加标识串,那么默认的日志名称就是空字符串再加上自定义后缀的。实际验证上面的那个字符串在rsyslog中使用programname或者syslogtag都可以引用。当然新版本的log4j 2.x在配置文件中单独支持了SysTag了,只是老版本的话我们现在还没法直接设置。同时rsyslog本身就带有了日期、等级信息了,所以SYSLOG和A1的配置在ConversionPattern还是有一些细微的差别的,当然这是定制化的东西可以自己慢慢琢磨,只是此处力求和其他服务统一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ModLoad imudp
$RuleSet localudp8999

$template SyslogFormat,"[%fromhost-ip%][%timegenerated:::date-rfc3339%]<%syslogseverity-text%>%msg%\n"

$template LGDailyErrorFileLogs,"/data/java_agent_service/rsyslog/%$YEAR%%$MONTH%/%syslogtag%.error.log_%$YEAR%%$MONTH%%$DAY%"
$template LGDailyInfoFileLogs,"/data/java_agent_service/rsyslog/%$YEAR%%$MONTH%/%syslogtag%.info.log_%$YEAR%%$MONTH%%$DAY%"
$template LGDailyNoticeFileLogs,"/data/java_agent_service/rsyslog/%$YEAR%%$MONTH%/%syslogtag%.notice.log_%$YEAR%%$MONTH%%$DAY%"
$template LGDailyDebugFileLogs,"/data/java_agent_service/rsyslog/%$YEAR%%$MONTH%/%syslogtag%.debug.log_%$YEAR%%$MONTH%%$DAY%"
if $syslogfacility-text contains 'local6' and $syslogseverity <= 3 then ?LGDailyErrorFileLogs;SyslogFormat
if $syslogfacility-text contains 'local6' and $syslogseverity <= 6 then ?LGDailyInfoFileLogs;SyslogFormat
if $syslogfacility-text contains 'local6' and $syslogseverity <= 5 then ?LGDailyNoticeFileLogs;SyslogFormat
if $syslogfacility-text contains 'local6' then ?LGDailyDebugFileLogs;SyslogFormat

# tcp forward to remote 1.188
local6.* @@192.168.1.188:8999
& ~

# bind local udp log server
$InputUDPServerBindRuleset localudp8999
$UDPServerRun 8999

  在本机的rsyslog服务中,我们使用相同的facility(LOCAL6)来识别日志并进行存档。同时,我们在配置的最后使用TCP的方式将日志再转发一份到远端的日志服务器,远程的日志服务器公用之前的配置不用做任何修改。自此log4j就可以和rsyslog算是完美的合作了。我们没有直接使log4j把日志用UDP的方式转发到中心日志服务器,而是本地的rsyslog使用UDP接收,然后在用TCP发送到中心日志服务器,觉得这样的可靠性会高一些,否则中心服务器日志负载过高的话可能会发生UDP日志丢失的情况。
  当然这里log4j本身的的日志配置比较粗糙,但是已经习惯关注rsyslog了,那就这样吧。

本文完。