# log-slf4j-logback
**Repository Path**: pullwind/log-slf4j-logback
## Basic Information
- **Project Name**: log-slf4j-logback
- **Description**: 日志处理框架,具体功能如下
1.统一日志框架:slf4j+logback
2.统一日志配置:方便多项目和分布式对日志管理
3.统一日志戳:支持为每个处理线程设置前缀并由日志框架统一输出,方便分布式环境下查日志
4.统一敏感信息处理:通过自定义注入敏感词,再通过格式化输出工具日志时,框架会自动处理屏蔽敏感信息
5.可支持标准化json扩展附加信息,结合ELK集群完成日志收集与分析并输出图表
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2018-04-24
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 目录
* [日志常见问题](#日志常见问题)
* [问题解决方案](#问题解决方案)
* [统一日志框架slf4j+logback](#统一日志框架slf4jlogback)
* [统一日志配置](#统一日志配置)
* [固定编码格式](#固定编码格式)
* [固定模板输出](#固定模板输出)
* [日志扩展](#日志扩展)
* [统一日志戳](#统一日志戳)
* [原理](#原理)
* [设置日志戳](#设置日志戳)
* [注意事项](#注意事项)
* [程序集成](#程序集成)
* [统一敏感信息处理](#统一敏感信息处理)
* [原理](#原理-1)
* [敏感信息字典key维护](#敏感信息字典key维护)
* [敏感信息默认key](#敏感信息默认key)
* [标准格式日志](#标准格式日志)
* [注意事项](#注意事项)
* [日志框架使用方法](#日志框架使用方法)
* [排除其它日志依赖](#排除其它日志依赖)
* [依赖log-slf4j-logback](#依赖log-slf4j-logback)
* [配置logback.properties](#配置logbackproperties)
* [web加载方式](#web加载方式)
* [spring加载方式](#spring加载方式)
* [其它加载方式](#其它加载方式)
* [替换其它日志类引用(建议)](#替换其它日志类引用建议)
* [应用日志可视化方案](#应用日志可视化方案)
* [ELK架构](#elk架构)
* [建立业务模型](#建立业务模型)
* [收集json扩展数据](#收集json扩展数据)
* [制作图表](#制作图表)
* [图表监控](#图表监控)
# 日志常见问题
1. 日志框架多且多不兼容
1. 日志配置格式很难统一
1. 日志分布式下查找困难
1. 日志敏感信息很难屏蔽
1. 日志很难转化为有价值的数据
# 问题解决方案
1. 统一日志框架:slf4j+logback
1. 统一日志配置:方便多项目和分布式对日志管理
1. 统一日志戳:支持为每个处理线程设置前缀并由日志框架统一输出,方便分布式环境下查日志
1. 统一敏感信息处理:通过自定义注入敏感词,再通过格式化输出工具日志时,框架会自动处理屏蔽敏感信息
1. 支持标准化json扩展附加信息,结合ELK集群完成日志收集与分析并输出图表
# 统一日志框架slf4j+logback
**_slf4j+logback优势_**
1. 面向接口,解耦,使用时不关心日志的实现
1. org.slf4j.Logger
1. 告别了if(logger.isDebugEnable()) 时代
1. Logback拥有更好的性能,包括判定是否记录一条日志语句的操作、创建记录器(logger)的速度、获取已存在的记录器的速度等
1. slf4j支持参数化,可读性和性能更好
```
//log4j
logger.info("帐号ID: "+ userId+"不存在");
//slf4j
logger.error("帐号ID:{}不存在", userId);
```
# 统一日志配置
## 固定编码格式
```
UTF-8
```
## 固定模板输出
用|分隔日志域,运维配置统一解析
```
%date{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%logPreFix|%class.%method:%line|%extjson|%sensitiveMsg%n
```
**_模板输出说明_**
```
时间(yyyy-MM-dd HH:mm:ss.SSS)|日志级别|日志戳-线程id|代码路径|扩展json(可空)|脱敏消息
```
**_内部应用模板_**
包含 debug、info、error日志文件,对应级别日志输出到对应文件
```
kalvan/log/logback/template/logback.xml
```
**_外部应用模板_**
包含 debug、info、error日志文件,额外增加msg日志文件,对应级别日志输出到对应文件,外部通讯报文则通过获取msg来写入
```
kalvan/log/logback/template/openapi/logback.xml
org.slf4j.Logger MSG = org.slf4j.LoggerFactory.getLogger("msg");
```
## 日志扩展
支持扩展的域为第五域使用需要使用json格式
```
String reqInfo = objectMapper.writeValueAsString(context);
// 扩展一个域记录图表分析需要的数据json格式,会追加到后面第一行日志的第5域
ExtJsonConverter.setExtJson(reqInfo);
log.info("接收请求");
```
举例
```
2017-09-2516:58:52.745|INFO |F201709254000008-24|kalvan.openapi.request|{"merchant":"pinganyinhang02","business":"ysepay.df.batch.normal.accept","ip":"10.211.55.4"}|接收请求
2017-09-2516:58:53.865|INFO |F201709254000008-24|kalvan.openapi.response|{"merchant":"pinganyinhang02","business":"ysepay.df.batch.normal.accept","processState":"BATCH_ACCEPT_SUCCESS","processTime":1120,"ip":"10.211.55.4"}|结束请求
```
# 统一日志戳
## 原理
利用线程本地变量ThreadLocal存储日志戳,在线程开始的地方注入日志戳,在该线程后面的所有日志输出时都动态带上日志戳
## 设置日志戳
```
kalvan.log.logback.layout.converter.LogPreFixConverter.setLogPreFix(String);
```
## 注意事项
线程在执行完业务后会被回收去处理新的业务,这时会导致日志戳还是之前那笔业务的,所以我们要在线程使用完后、或者线程开始工作的时候清理掉日志戳。
```
kalvan.log.logback.layout.converter.LogPreFixConverter.resetLogPreFix();
```
## 程序集成
- Web 服务可以通过拦截器处理,请求进来时先对线程日志戳进行清理
- Rpc服务可以通过rpc框架的实现层处理,也可以考虑在aop中统一处理
- 也可以直接在方法实现类入口手动注入清理(不推荐)
# 统一敏感信息处理
## 原理
日志框架在向文件写日志时会匹配是否存在配置的敏感词key: 匹配到则会对value进行对应的脱敏处理, 兼容匹配 xxxkey:
**优点:** 只要日志内容符合key:value结构能全部处理掉,由于是日志底层实现,对开发人员限制较少
**缺点:** 每行日志都会去匹配对应的敏感词,性能会受影响。单笔交易影响不大,但大批量交易影响可能会相对明显,如清结算跑批
**优化:** 待完成。定义注解支持扩展。根据需要给属性打上注解,再由对象转换成字符串这个过程完成,日志框架底层就不用去每行都匹配了。
## 敏感信息字典key维护
```
kalvan.log.logback.layout.converter.SensitiveMessageConverter.sensitiveInfoMap
//注入敏感词
kalvan.log.logback.layout.converter.SensitiveMessageConverter.sensitiveInfoMap.put("key",kalvan.log.util.SensitiveType);
```
## 敏感信息默认key
默认配置的敏感词,如果不需要可以在启动时clear(), 也可以增加额外的字典。
```
// 银行卡账号信息
put("accountNo", SensitiveType.BANK_CARD);
put("card_no", SensitiveType.BANK_CARD);
put("cvv", SensitiveType.ALL);
// 有效截止日期
put("effdate", SensitiveType.ALL);
put("expdate", SensitiveType.ALL);
put("validdate", SensitiveType.ALL);
// 客户信息,如果启用 name去查找 则 xxxname:
put("name", SensitiveType.CHINESE_NAME);
put("certifino", SensitiveType.ID_CARD);
put("certifyno", SensitiveType.ID_CARD);
put("id_no", SensitiveType.ID_CARD);
put("mobile", SensitiveType.MOBILE_PHONE);
put("phone", SensitiveType.MOBILE_PHONE);
put("phoneNum", SensitiveType.MOBILE_PHONE);
put("tel", SensitiveType.MOBILE_PHONE);
put("address", SensitiveType.ADDRESS);
put("addr", SensitiveType.ADDRESS);
put("email", SensitiveType.EMAIL);
```
## 标准格式日志
敏感信息匹配时需要依赖该格式
- 可使用提供的工具类处理(建议)
```
//输出bean对象
kalvan.log.util.LogFormatUtil.formatBean(Object);
//输出map对象
kalvan.log.util.LogFormatUtil.formatMap(Map);
//输出list对象
kalvan.log.util.LogFormatUtil.formatCollection(Collection);
//输出数组对象
kalvan.log.util.LogFormatUtil.formatObjectArray(Object[]);
```
- 也可以自己按key:value 结构输出
```
1 T1002[ Order[ orderId:20170928000658803681amount:0.62busiCode:00050000shopDate:20170928cur:CNY note:s remark: extraData: timeout: supportCards: bgUrl: bankType: merCharset: remark2: merOutsideUserId: accountType: payType: state: orderTradesn: orderPaydetailsn: foreignAmount: foreignCur: rate: ]PersonalInfo[ flag:03protocolNo: userCode: custId: name: amount:0.62bankAccountType:13bankType:3085840bankname:招************* bankaddr:1*** bankcode:308100005019accountNo:330104*********6673accountname:云** extraData: phoneNum: proxyPW: agentCustid: accountId: feeAccountId: bankProtocolNo: payerBankprotocolflag: dslevel: dsscope: protocolType: payerPayType: payeeRecvType: onceDeduct_Amount: protocolPayType: ifInsideProtocol: accountType: ]PersonalInfo[ flag: protocolNo: userCode:zhangxianglei custId: name:深*** amount:0.62bankAccountType: bankType: bankname: bankaddr: bankcode: accountNo: accountname: extraData: phoneNum: proxyPW: agentCustid: accountId: feeAccountId: bankProtocolNo: payerBankprotocolflag: dslevel: dsscope: protocolType: payerPayType: payeeRecvType: onceDeduct_Amount: protocolPayType: ifInsideProtocol: accountType: ]:nullrecordBusicode: recordType: tradeSource:02]TBase[ ver:1.0src:zhangxianglei srcCustId:2016072705261222msgCode:S1002 time:20170928091720srcIP:10.211.55.15srcDomian: deviceFingerPrint: imei: tradeType: srcAccountId: ]
```
## 注意事项
- 使用敏感信息匹配,必须使用日志格式化工具输出对象日志或者能保持一致格式输出
- 如果每个应用有不同的敏感词,可以在应用启动时将自已需要的词典注入,可以将默认用不到的字典给清除,提高处理性能
- 尽量避免注入太多敏感词,日志性能可能造成影响。
- 系统定义属性建议标准一些,不要代表同一个属性却在不同java对象定义不同的属性名,这样在敏感信息处理时浪费不必要的资源
如: phone、phone_no、phone_number、mobile,如果有你系统里没有规范好自己的定义,那么会需要 对一件事去处理多次。
- 考虑到系统交互中可能存在 json数据格式数据,目前默认的字典是不支持,如果需要则需要自己注入key”:”
# 日志框架使用方法
## 排除其它日志依赖
1. 需要排除的包有 slf4j-log4j12、slf4j-jdk14、log4j,建议通过eclise的pom.xml的dependency hierarchy 进行检查和排除
1. 已知还会和mq的依赖 activemq-all中的日志类有冲突,如果有用到也需要排除
1. 如果还有其它依赖有用到也需要排除
```
org.slf4j
slf4j-log4j12
log4j
log4j
org.slf4j
slf4j-jdk14
```
如果想确保自己的工程不包含这些有冲突的依赖,则可以在maven 的pom.xml中加入约束,编译时会自动检查
```
org.apache.maven.plugins
maven-enforcer-plugin
enforce-banned-dependencies
enforce
org.slf4j:slf4j-log4j12
org.slf4j:slf4j-jdk14
log4j:log4j
true
```
## 依赖log-slf4j-logback
```
kalvan.log
log-slf4j-logback
1.0.0-SNAPSHOT
```
需要自己打包发布到自己的私服
## 配置logback.properties
```
#将logback的日志路径注入配置文件,日志文件路径
logback.logpath=${filter.log.path}
#将logback的root日志级别注入配置文件 ,生产配置为info,其它环境可以配置为debug
logback.rootLoggerLevel=${filter.log.rootLoggerLevel}
# 将logback的分隔大小注入配置文件 ,生产默认配置为500MB,测试环境可以配置为50MB,一定要加单位
logback.maxFileSize=${filter.log.maxFileSize}
```
## web加载方式
```
logbackConfigLocation
classpath:kalvan/log/logback/template/logback.xml
logbackProperties
conf/logback.properties
kalvan.log.logback.spring.web.LogbackConfigListener
```
## srping加载方式
```
```
## 其它加载方式
```
// 获取日志配置文件
ResourceBundle resource = ResourceBundle.getBundle("logback");
// 设置日志配置
System.setProperty(LogbackConfigListener.LOGPATH,
resource.getString(LogbackConfigListener.LOGPATH).trim());
System.setProperty(LogbackConfigListener.ROOTLOGGER_LEVEL,
resource.getString(LogbackConfigListener.ROOTLOGGER_LEVEL).trim());
System.setProperty(LogbackConfigListener.MAXFILESIZE,
resource.getString(LogbackConfigListener.MAXFILESIZE).trim());
// 日志框架加载
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
try {
String path = TestLog.class.getResource("/kalvan/log/logback/template/openapi/logback.xml").getFile();
configurator.doConfigure(path);// 加载logback配置文件
} catch (JoranException e) {
e.printStackTrace();
}
```
## 替换其它日志类引用(建议)
可以将其它日志替换为新的日志org.slf4j.Logger
```
jcl:org.apache.commons.logging.Log
jul:java.util.logging.Logger
log4j:org.apache.log4j.Logger
```
# 应用日志可视化方案
## ELK架构
ELK是一套解决方案而不是一款软件, 三个字母分别是三个软件产品的缩写。 E代表Elasticsearch,负责日志的存储和检索; L代表Logstash, 负责日志的收集,过滤和格式化;K代表Kibana,负责日志的展示统计和数据可视化。

## 建立业务模型
1. 业务请求:根据业务做时间的曲线图
1. 业务响应:根据商户号生成前10交易量商户、根据耗时做时间的平均耗时曲线图 、根据业务类型生成饼图(各业务总数和占比)、根据业务处理状态生成饼图(成功、失败、异常)
1. 异常监控图:根据商户和状态=Error生成前10异常交易商户的饼图
1. ....
## 收集json扩展数据
**_请求数据收集_**
```
kalvan.log.datamodel.RequestModelContext.RequestModelContext( merchant,business,ip);
=>
2017-09-25 16:58:52.745|INFO |F201709254000008-24|kalvan.openapi.request|{"merchant":"pinganyinhang02","business":"ysepay.df.batch.normal.accept","ip":"10.211.55.4"}|接收请求
```
**_响应数据收集_**
```
kalvan.log.datamodel.RequestModelContext.RequestModelContext( merchant,business,ip,processState,processTime);
=>
2017-09-25 16:58:53.865|INFO |F201709254000008-24|kalvan.openapi.response|{"merchant":"pinganyinhang02","business":"ysepay.df.batch.normal.accept","processState":"BATCH_ACCEPT_SUCCESS","processTime":1120,"ip":"10.211.55.4"}|结束请求
```
## 制作图表


## 图表监控
