日志是平时开发中都会用到的,这里以logback为例,总结几点笔者使用的几点经验

日志的打印

一般来说方法和远程调用的入参,出参和错误的异常栈是必须要打印的,打印异常最好同时以打印入参出参,因为有的入参出参是DEBUG的,线上不会打印,另外完成一些重要逻辑也需要打印,以便追踪当前方法执行的阶段。

入参,出参,异常这类常用的日志打印可以提供一个字符串工具,以减少重复代码,提高效率。

这是我写的工具:

public class LoggerFormat {


    private final static String INPUT = "传入";

    private final static String OUTPUT = "传出";

    private final static String EXCEPTION = "异常";


    public static String input(String desc, Object param) {
        return format(desc, INPUT, param);
    }


    public static String output(String desc, Object param) {
        return format(desc, OUTPUT, param);
    }

    public static String exception(String desc, Object param) {
        return format(desc, EXCEPTION, param);
    }

    public static String exception(String desc, Object param, Object result) {
        return format(desc, EXCEPTION, INPUT, param, OUTPUT, result);
    }


    private static String format(Object... objects) {
        if (null == objects || objects.length == 0) {
            return null;
        }

        StringBuilder stringBuilder = new StringBuilder();
        for (Object obj : objects) {
            stringBuilder.append(obj instanceof String ? String.valueOf(obj) : JsonUtils.toJSONString(obj));
        }
        return stringBuilder.toString();
    }
}

总结来说就是打印日志前先考虑好,在方法执行异常排查问题时我需要哪些关键信息,我如何才能快速定位问题,等考虑清楚了,自然就明白日志该怎么打印了。

日志格式

我常用的日志格式:

[${project.artifactId} %d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread]\(%F:%L\) - %msg%n

前面中括号里面是项目名和时间(精确到毫秒),为什么需要输出项目名呢,因为笔者常常同时会看多个项目的日志,很多时候就搞不清哪是哪的日志了,有了项目名就一目了然;

后面是一个中括号是日志级别,最大5个字符,所以使用5个占位;

接着是线程名,由于一个方法可能多个线程并发调用,排查问题时需要区分哪个线程,否则无法准确定位日志;

然后是输出日志的文件名和代码行,笔者觉得没有必要输出包名,因为太长,有的经过logback简化后也看不清是哪个包,而直接输出文件名,简单明了,搭配代码行就更容易定位问题;

最后是日志内容和换行。

日志环境控制

这个比较好说了,ROOT使用INFO。对于业务日志,测试开发环境使用DEBUG,线上使用INFO,可以使用maven的profile来做控制,由于日志的继承,控制到company.artifactId就可以了。一些三方中间件开到WARN整个世界就清净了。

动态控制日志级别

动态控制可以这样使用

// 获取日志上下文
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// 获得日志
ch.qos.logback.classic.Logger logger = loggerContext.getLogger("org.springframework");
// 设置级别
logger.setLevel(Level.toLevel("warn"));

日志具有继承的特性,也就是说

Logger logger = LoggerFactory.getLogger("com.a.XxxImpl");

这行代码创建了3个日志,依次向上继承,分别是com.a.XxxImpl,com.a,com,日志级别优先使用当前,没有再依次向父类查找直到找到了一个设置了日志级别的日志,都没有则使用根日志的级别,根日志为ROOT。

我们动态控制输出级别的时候也可以利用该特性。

另外由于线上多为分布式,所以可以使用缓存,配置系统,或者广播消息来同步配置,笔者倾向广播消息。

参考文章:

https://blog.csdn.net/totally123/article/details/78931287