日志处理平台可以分成三个部分:
shipper:日志收集 broker:中间件/队列 indexer:日志存储
日志的功能侧重点:
1.快速定位线上log,快速差错,
2.对日志进行数据处理,提取整理日志中的重要数据
3.提取用户请求整个访问流程的日志,便于分析
最开始考虑了几种架构方案elk+kafka或者reids+mongodb,但后来根据实际业务场景考虑了一下,目前项目平台每日产生的日志不足1G,往极端方向考虑,假设用户每天都集中在4个小时处理相关业务,即1024/4*60*60=0.07M/S,每秒日志的吞吐量还不到100kb,使用kafka过于重量级,就算考虑业务爆发,日志吞吐量扩充10倍也不过0.7M/S,单台redis完全够用。
mongodb也比较适合日志存储,但不如elk来的专业,而且还需要手写查询接口对外发布,比较麻烦,检索功能及吞吐量比es查了几个量级,因此不予采用。
最后采用了slf4J+logstash+redis+logstash+elasticSearch+kibana,这里redis最好做一下主从,毕竟用redis做队列可靠性不是那么高,日志丢了就没了,不像es可以分片,当然如果日志不是那么重要也无所谓了。
logstash号称支持一切input,日志组件、tcp、udp、各种队列都可以作为日志输入源,logstash支持多输入源,一个logstash实例可以监控整台web服务器。logstash主要分为三部分input、filter、output,input和output格式可以自定义,可以通过codec进行编码或直接使用ruby库解析键值对进行输入输出。filter插件也特别强大,grok插件解析时间或字符串,mutate解析数据格式,同时还支持ruby插件,导入ruby的库,直接在配置文件里用ruby解析数据,可以说是无所不能了。
规范整个日志业务之前第一步就是先把以前的日志格式统一起来,如果日志格式不统一,那下一步的日志解析存储就毫无意义了。elk搭好之后需要应用于多个项目,每个项目日志又分为业务日志、dao日志,也可能会把nginx的请求日志也导入es中,
另外老项目的dao层比较乱,最老的代码直接从mapper文件解析sql运行的,而新代码已经把dao层封装起来了,提供一些关键字拼接的api,最后也是通过mapper文件发送sql到db里执行的。这里会有一个兼容性的问题,即老的dao层日志是直接用
mybatis的日志模块输出的,和业务日志分成两个文件。而新的dao层日志应该和业务日志整合到一个文件,按天分割。所以这里在导入es时要考虑也要为mybatis日志模块输出的log配置映射模板。
最终采用的log格式为:
调用时间-log等级-类名-web容器线程ID-hostName/ip-appName--requestIP-requestID-accountID-操作-出入参数
hostName/IP用于定位线上的web实例,当hsostName未设置时取服务器的局域网ip
appName用于区分多个项目
requestID是为用户请求生成的唯一ID,用于追踪用户的整个访问流程。生成策略为request的hashcode+当前时间戳&16进制
上面的log格式中需要手动填入的只有log操所和出入参数,其他均集成到log4j2中。
log4j里通过代码方式动态追加参数可以通过重写patternConverter实现:
public class ExtPatternConverter extends PatternConverter {
private String cfg;
@Autowired
public ExtPatternConverter(FormattingInfo fi, String cfg) {
super(fi);
this.cfg = cfg;
}
//重写patternConverter的日志参数转换方法
@Override
protected String convert(LoggingEvent event) {
System.out.println("start char convert");
Map<String, Object> valueMap = ExtPatternParser.TH_LOCAL.get();
if (valueMap != null) {
Object value = valueMap.get(cfg);
if (value != null) {
return String.valueOf(value);
}
}
return "no exist";
}
}
public class ExtPatternLayout extends PatternLayout {
public ExtPatternLayout(String pattern) {
super(pattern);
System.out.println("pattern is null ???:" + pattern);
}
public ExtPatternLayout() {
super(DEFAULT_CONVERSION_PATTERN);
}
//获取pattern标签的日志格式,包含了我们自定义的字符
@Override
protected PatternParser createPatternParser(String pattern) {
System.out.println("pattern is:" + pattern);
return new ExtPatternParser(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern);
}
}
public class ExtPatternParser exte