日志记录原则以及logback高级用法
阅读引导:
1、良好的日志有助于快速定位问题,但是记录日志很容易臃肿膨胀,记录无用信息。
2、良好的日志,也是后续进行全链路跟踪的分析介质。
3、为自己工作,为自己的系统工作,做自己的老板,形成正循环:打磨当前工作的核心关键能力——>高效能工作——>更多时间打磨自己的系统——>更高效能工作——>打磨下个层次工作的核心关键能力……
4、核心竞争力,是指你拥有的(独特的)知识经验组合,经过你思维逻辑的组织梳理,在实践中产生无可替代的价值。打造自己的TMS系统(T:专业技术;M:沟通管理、S:行业解决方案),利用复利效应,让系统为自己工作。
目前常用的日志框架是logback、log4j2这两个。
logback是在log4j的基础上进行的升级,而log4j2是参考logback的长处进行的升级。
log4j2号称性能提升巨大。
不过目前还是选择logback框架,首先是目前记录日志并没有影响TPS,另外springboot等开源框架都在使用logback,如果不是极端情况还是使用logback。
logback的基础用法,不再赘述,请读者自行搜索。
当然,为了很快的替换,应用中应该使用SLF4J这个日志facade模式框架,而不是具体的实现。
具体来说,Logger的声明,使用SLF4J的类声明。
日志记录原则
日志,是用来查找问题的。
应用日志的核心目标,就是发现问题时,进行查询搜索。
以此定位的话,有下面几个原则
日志内容,尽量少而完整
首先是通过配置,日志框架就可以记录的静态内容:时间、日志级别、线程、class路径等。
最主要的是:上下文信息
例如,如果报错error日志,要记录入参、详细的报错异常堆栈、核心中间数据等。
达到一个目标,就是通过日志信息,可以很快的还原现场,不需要再人工去梳理推断流转到此处代码的逻辑信息。
初级程序员往往直接把Exception扔打印方法里面,导致日志中只有报错现象,而没有数据。
难以分析。
日志时机
首先是:异常、错误等地方使用error级别日志。
其次,核心组件的关键动作使用info级别日志。
第三,可以容错的处理,记录warn级别日志。
日志格式
格式一般是在logback.xml配置文件中配置。
可以按照企业内部要求,或者结合应用接口汇总涉及到的字段来记录。
日志分析
日志要经常分析。对于error级别日志,重点考虑,分析原因;对于warn级别日志,查看容错处理的情况,是否可以避免;对于info级别的日志,是否有必要,是否可以不记录?目前日志的读写到文件,需要占用I/O资源,最好尽可能的少。
log高级用法
介绍一些logback提供的功能,借以封装实现个性化需求。
logback的contextListener日志系统初始化
我们需要在logback中做一些动态配置,比如日志等级、输出路径等,这些信息可以存放在数据库中,启动时加载,可以用logback自带的contextListener。
public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle
然后配置到logback.xml中,日志系统初始化之前就会使用这个个性化的listener
<contextListener class="com.…….LoggerStartupListener" />
logback的封装使用(如果性能要求高,不推荐)
下面是普通的日志方式:
private final static Logger logger = LoggerFactory.getLogger(App.class);
logger.debug("what")
但是每一个类都这么写,真是非常繁琐。
可以做一下如下的封装,logger做一个静态工具类使用:
class LogToolFactory {
/**
* 获取Logger
* @param clazz LoggerTool的 class
* @return
*/
public static Logger getLogger(Class clazz) {
String invokerClassName = getInvokerClassName(clazz);
// 获取前一个stackTrace,即LoggerTool的调用者类
try {
return LoggerFactory.getLogger(Class.forName(invokerClassName));
} catch (ClassNotFoundException e) {
throw new RuntimeException("找不到:" + invokerClassName + ",初始化logger失败: " + e.getStackTrace());
}
}
/**
* 通过堆栈,获取调用者类名
* @param clazz 被调用者class
* @return 调用者类名
*/
private static String getInvokerClassName(Class clazz) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// 获取LoggerTool 在堆栈中的索引
int loggerToolIndex = 0;
for (int i=0; i<stackTrace.length; i++) {
String className = stackTrace[i].getClassName();
if(clazz.getName().equals(className)) {
loggerToolIndex= i;
}
}
String invokerClassName = stackTrace[loggerToolIndex + 1].getClassName();
return invokerClassName;
}
private LogToolFactory() {
}
}
public class LogTool {
private static final Class clazz = LogTool.class;
public static String getName() {
Logger logger = LogToolFactory.getLogger(clazz);
return logger.getName();
}
public static void trace(String msg) {
Logger logger = LogToolFactory.getLogger(clazz);
logger.trace(msg);
}
……
但是,使用了StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
在交易量非常大的情况下,会有性能问题。
对于普通的应用可以尝试。