原文标题:Java日志通关(三) - Slf4j 介绍
原文作者:阿里云开发者
冷月清谈:
创建 Logger 实例
使用 Slf4j 需要创建一个 org.slf4j.Logger 实例,可以通过工厂函数 org.slf4j.LoggerFactory.getLogger() 创建。参数可以是字符串或 Class。
日志级别
Slf4j 定义了五个日志级别:TRACE、DEBUG、INFO、WARN 和 ERROR。日志实现层决定哪个等级的日志可以输出。
打印接口
Slf4j 提供了多种日志打印接口,包括 info()、debug() 和 error()。这些接口接受字符串模板和参数,用于构建日志消息。
Marker
Marker 用于标记日志消息,由日志实现层处理,可以用于过滤或格式化日志。
MDC
MDC(Mapped Diagnostic Context)是一个线程安全的存储,用于存储扩展字段,如跟踪 ID。
Fluent API
Slf4j 从 2.0.x 开始支持 Fluent API,它提供了一种流式的调用方式,可以方便地设置日志消息的各个属性。
怜星夜思:
2、MDC 在实际项目中的应用场景有哪些?
3、Fluent API 的优势有哪些?
原文内容
阿里妹导读
一、创建 Logger 实例
1.1 工厂函数
-
如果是字符串,这个字符串会作为返回Logger实例的名字;
-
如果是Class,会调用它的getName()获取Class的全路径,作为Logger实例的名字;
public class ExampleService { // 传 Class,一般都是传当前的 Class private static final Logger log = LoggerFactory.getLogger(ExampleService.class); // 上边那一行相当于: private static final Logger log = LoggerFactory.getLogger("com.example.service.ExampleService");
// 你也可以指定任意字符串
private static final Logger log = LoggerFactory.getLogger(“service”);
}
1.2 Lombok
@Slf4j public class ExampleService { // 注解 @Slf4j 会帮你生成下边这行代码 // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExampleService.class); }
@Slf4j(topic = “service”)
public class ExampleService {
// 注解 @Slf4j(topic = “service”) 会帮你自动生成下边这行代码
// private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(“service”);
}
二、日志级别
-
TRACE:一般用于记录调用链路,比如方法进入时打印xxx start;
-
DEBUG:个人觉得它和 trace 等级可以合并,如果一定要区分,可以用来打印方法的出入参;
-
INFO:默认级别,一般用于记录代码执行时的关键信息;
-
WARN:当代码执行遇到预期外场景,但它不影响后续执行时,可以使用;
-
ERROR:出现异常,以及代码无法兜底时使用;
@Slf4j public class ExampleService { @Resource private RpcService rpcService;
public String querySomething(String request) {
// 使用 trace 标识这个方法调用情况
log.trace(“querySomething start”);
// 使用 debug 记录出入参
log.debug(“querySomething request={}”, request);String response = null;
try {
RpcResult rpcResult = rpcService.call(a);
if (rpcResult.isSuccess()) {
response = rpcResult.getData();// 使用 info 标识重要节点
log.info(“querySomething rpcService.call succeed, request={}, rpcResult={}”, request, rpcResult);
} else {
// 使用 warn 标识程序调用有预期外错误,但这个错误在可控范围内
log.warn(“querySomething rpcService.call failed, request={}, rpcResult={}”, request, rpcResult);
}
} catch (Exception e) {
// 使用 error 记录程序的异常信息
log.error(“querySomething rpcService.call abnormal, request={}, exception={}”, request, e.getMessage(), e);
}// 使用 debug 记录出入参
log.debug(“querySomething response={}”, response);
// 使用 trace 标识这个方法调用情况
log.trace(“querySomething end”);
return response;
}
}
三、打印接口
-
public boolean info(...);
-
public boolean isInfoEnabled(...);
3.1 info 方法
3.2 isInfoEnabled 方法
if (log.isInfoEnabled()) { log.info(...) }
if (log.isInfoEnabled()) { // 有远程调用 String resource = rpcService.call(); log.info("resource={}", resource)
// 要解析大对象
Object result = …; // 一个大对象
log.info(“result={}”, JSON.toJSONString(result));
}
四、Marker
-
log.info(Marker, ...)
Marker marker = MarkerFactory.getMarker("foobar"); log.info(marker, "test a={}", 1);
-
将Marker通过%marker打印出来;
-
使用MarkerFilter[3]过滤出(或过滤掉)带有某个Marker的日志,比如把需要Sunfire监控的日志都过滤出来写到一个单独的日志文件;
五、MDC
// 和 Map<String, String> 相似的接口定义 MDC.put("key", "value"); String value = MDC.get("key"); MDC.remove("key"); MDC.clear();
// 获取 MDC 中的所有内容
Map<String, String> context = MDC.getCopyOfContextMap();
六、Fluent API (链式调用)
Marker marker = MarkerFactory.getMarker("foobar"); Exception e = new RuntimeException();
// == 以下几个示例的最终效果是完全一致的 ==
// 这是传统的调用方式
log.info(market, “request a={}, b={}”, 1, 2, e);// Fluent API 例1
log.atInfo() // 表示这是 INFO 级别。你猜对了,还有 atTrace/atDebug/atWarn/atError
.addMarker(marker)
.log(“request a={}, b={}”, 1, 2, e); // 与传统 API 很像// Fluent API 例2
log.atInfo()
.addMarker(marker)
.setCause(e)
.setMessage(“request a={}, b={}”) // 传字符串模板
.setMessage(() -> “request a={}, b={}”) // setMessage 支持传入 Supplier
.addArgument(1) // 添加与字符串模板中占们符所对应的值
.addArgument(() -> 2) // addArgument 支持传入 Supplier
.log(); // 大火收汁// == addKeyValue 的输出格式依赖日志实现层的配置,默认格式与上边示例不同 ==
// Fluent API 例3
log.atInfo()
.setMessage(“request”) // 注意这里没有占位符
.setKeyValue(“a”, 1) // 通过 setKeyValue 添加关心的变量
.setKeyValue(“b”, () -> 2) // value 支持传入 Supplier
.log();
// 通过 setKeyValue 设置的值默认会放在 message 前边,比如上边这个例子,默认会输出:
// a=1 b=2 request
-
所有add前缀的方法,都支持设置多个,比如addMarker/addArgument/addKeyValue。所以在Fluent API中是支持给一条日志添加多个Marker的,而传统API不可以。
-
所有set前缀的方法,对应的值都只有一个,比如setMessage/setCause,虽然你可以多次调用,但只有最后一次会生效。
log.info("request a={}", () -> a);
七、后记
[1]https://projectlombok.org/features/log
[2]https://juejin.cn/post/6915015034565951501
[3]https://logback.qos.ch/apidocs/ch/qos/logback/classic/turbo/MarkerFilter.html
[4]https://www.aliyun.com/product/xtrace
[5]https://www.slf4j.org/manual.html#fluent
点击查看《》
》