# 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,负责日志的展示统计和数据可视化。 ![ELK架构](img/elk.png "ELK架构") ## 建立业务模型 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"}|结束请求 ``` ## 制作图表 ![图形化](img/kibana.png "图形化") ![饼图](img/visualization.png "饼图") ## 图表监控 ![显示](img/show.png "显示")