背景
大量项目在使用 logback 记日志,有部分项目使用日志混乱,格式不统一,多数人搞不懂配置文件,导致配置错误,现在需要开发一套统一的、少配置的日志组件,使用方便。
设计思路
尽量采用 0 配置,无 logback.xml
日志格式统一,方便后续日志分析系统
只有两个日志级别,一个是正常日志,一个是异常日志
提供 log4j、jcl、logback、commons-log 等桥接方案及版本兼容方案
提子线程、json 格式化输出、map 格式化、数组格式化、请求响应参数(供耗时)等便捷日志输出方法
支持 redis、db、http 自动开关配置 **
新增日志类型(logger)
api 采用流式结构,类似 StringBuffer
概要设计
零配置
调研代码
java
static LoggerContext lc;
static {
lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// 对应配置中的 appender
ConsoleAppender ca = new ConsoleAppender();
ca.setContext(lc);
ca.setName(“console”);
// 格式
PatternLayoutEncoder pl = new PatternLayoutEncoder();
pl.setContext(lc);
pl.setPattern(“%d{MMddHHmmss.SSS} [%thread] %-5level %logger{36} – %msg%n”);
pl.start();
ca.setEncoder(pl);
ca.start();
// 对应配置中的 logger
ch.qos.logback.classic.Logger rootLogger = lc.getLogger(“com.test”);
rootLogger.addAppender(ca);}
上面代码等价于下面的 xml
%d{MMddHHmmss.SSS} [%thread] %-5level %logger{36} – %msg%n
由此可以随意把配置文件中的内容以代码形式编写,理论已经可以实现 0 配置。
输出路径
约定固定将日志输出到,相对路径 log/xxx.yyyy-MM-dd-HH.log,其中 xxx 为 logger 的 name
日志格式
格式固定:MMddHHmmss.SSS||id||【交易名★子步骤】||context ||level 例:150000.311||N-XrUTQzIc1531897200311||【CiTeeFilter★ci 拦截器】||ci 拦截器 请求的完整参数为:{“merchantId”:[“0012444″],”userId”:[“13112341232”]} ||INFO 固定格式的核心代码,拦截到日志请求,按照格式拼装,主要方法为继承 ThrowableProxyConverter 和 MessageConverter 来实现对日志的拦截,并修改为想要的格式,其中使用的例如 id 等放到本地变量内,核心是对 MDC 的使用
基础 logger
所有日志都默认输出到这里 logger name:service 系统初始化时,定义这个 Logger 和 appender,即这个 Logger 为 root log
自定义的 logger
提供 addLogger 方法,参数 packageName 包名,例如:com.test 必输参数 如果 name 未设置时,name 默认为包名最后一个. 后面的字符 name 名字,决定日志文件的名字 非必输 path 日志路径 非必输 additivity 是否输出到 root log 内
特殊的 log 提供特殊组件的 log 配置,例如:redis 默认 ERROR http 默认 ERROR db 连接池 默认 ERROR kafka 默认 ERROR schedul 默认 ERROR spring 默认 ERROR
异常、换行日志处理
提供 exception 异常栈格式打印 提供带换行的格式化打印 代码思路:继承 ThrowableProxyConverter,获取异常栈,在每行的前面插入固定格式文本
普通日志 api(VirgoLog)
方法
方法描述
setUniqKey(id)
设置当前线程 id,线程开始时设置即可,后面无需设置
updateStep(trade, step)
更新当前 id 的步骤信息
log(msg, param)
记录普通日志,msg 替换规则,普通替换为{},如果想替换为业务日志 api 中的格式,使用 “ 替换
logErr(msg, e)
记录异常日志
log(trade, step, msg, param)
记录普通日志,此方法会自动更新 id、trade、step,不建议使用
logErr(trade, step, msg, e)
记录异常日志
log(cid, trade, step, msg, param)
记录普通日志,此方法会自动更新 id、trade、step,不建议使用
logErr(cid, trade, step, msg, e)
记录异常日志
debug(msg, param)
记录 debug 级别日志,不建议使用
业务日志 api(VirgoLog)
平时记日志时,如果某个类没有时间 toString 方法,会无法正确打印出数据,此时提供替换方法,直接将 object 替换为 json 打印,核心代码思路为
MessageFormatter 是处理 {} 替换的类,重新写个类,稍加改动即支持 {} 也支持 “,并判断替换为 json 还是 toString api 如下
方法
方法描述
begin(msg)
记录开始
end(msg)
记录完成,会打印本线程内上一个 begin 到现在的耗时
logJson(json, format)
记录 json 格式化日志,format 表示是否换行
logMap(map, format)
记录 map 格式化日志
logCollection(list, format)
记录集合格式化日志
logArray(array, format)
记录数组格式化日志
logObjct(obj, format)
记录 Object 格式化日志
系统 api(LoggerHelper)
方法
方法描述
getLogger()
获取 logger,用于记日志
getLogger(name)
通过 name 获取 logger
addLogger()
参考自定义 Logger,如果 logger 已经创建,则不再创建,一般不使用,除非想自定义日志名等
consoleOpen()
打开控制台日志,系统启动时默认配置控制台日志
commonOpen(name, level)
默认的组件都是 error 级别,这个方法可以变更日志级别,例如 redis http 等
特殊的格式化
map: 即转化为 json,然后再格式化 collection: 同上 array: 也同上 object: 同上
问题
1、密码脱敏、加解密有必要单独提取方法吗
2、提供父线程打印开关
maven 依赖
<dependency>
<groupId>com.cdc.ecliptic</groupId>
<artifactId>virgo</artifactId>
<version>1.5_1.6-SNAPSHOT</version>
</dependency>
demo
public static void main(String[] args) throws InterruptedException {
// 启动
VirgoLancher.start(“hahaha”, “com.cdc.virgo”, “D:/test/hahah.log”);
LoggerHelper.commonOpen(“hahaha”, LogLevel.DEBUG);
Logger logger1 = LoggerFactory.getLogger(“druid”);
// VirgoLancher.commonStart(“abc”, “com.cdc.virgo”);
// 打开控制台
LoggerHelper.consoleOpen();
// 设置 cid
VirgoLog.setUniqKey(null);
// 设置步骤名和交易名
VirgoLog.updateStep(“adfa”, “saf”);
// 获取 Logger
VirgoLog logger = VirgoLog.getLogger();
// 打开 debug 级别(只有在开发阶段可以打开)
// logger.changeLevel(LogLevel.DEBUG);
// 记录换行
logger.log(“a”);
logger1.info(“dddddddddd”);
logger1.error(“dddddddddd”);
// logger1.info(“sfdasfaf” +
// “\nafafdasfd” +
// “\nasfdasf”);
logger.log(“sfdasfaf” +
“\nafafdasfd” +
“\nasfdasf”);
// logger1.info(“b”);
// 正常日志
// logger.log(“ 我只有一行 ”);
Map<String, String> map = new HashMap();
map.put(“asdf”, “1”);
map.put(“asdf2”, “2”);
map.put(“asdf3”, “13”);
map.put(“asdf4”, “14”);
map.put(“asdf5”, “15”);
map.put(“asdf6”, “16”);
// // 异常日志也支持格式化
// logger.logErr(“ 我错了:{}, 你没错:~~”, new Exception(“asdfsaflk”), “ 啊 ”, map);
// logger.log(“———————————————-“);
// // {}替换普通对象,调用 toString() ~~ 把对象转换为 json 并且格式化输出 “ 把对象转换为 json 不格式化输出
logger.log(“ 你好{}, 你是谁~~“,sd~xx {}”, map, map, map, “tttt”);
VirgoLog.updateStep(“saf2”);
// // 把对象转换为 json 输出
// logger.logJson(map, false);
// // 更新步骤名和交易名
// VirgoLog.updateStep(“bbbbb”, “ccccc”);
// // 耗时日志打印
logger.begin(“ 处理内容 ”);
logger.begin(“ 处理第二个 ”);
logger.begin(“ 处理第三个 ”);
Thread.sleep(3000L);
logger.end();
Thread.sleep(1000L);
logger.end();
VirgoLog.updateStep(“saf3”);
logger.end();
// // 记录 debug 日志,一般调试用
// logger.logDebug(“jajajajaja”);
// List l = new ArrayList();
// B b = new B();
// try {
// b.b();
// } catch (Exception e) {
// logger.logErr(“woqu”, e);
// }
}