slf4j的log.warn()的参数问题研究

简介: slf4j的log.warn()的参数问题研究

一、背景

今天我们探讨一下@Slf4j的log.warn()的参数问题。

二、上源码

我先给出下面的几个测试用例,大家想想分别会输出什么?

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Test;
@Slf4j
public class WarnLogTest {
    @Test
    public void test1() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={}",
                    500, "呜呜,某某业务出错了!", "肿么办?");
        }
    }
    @Test
    public void test2() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={},e={}",
                    500, "呜呜,某某业务出错了!", JSON.toJSONString(e));
        }
    }
    @Test
    public void test3() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={},e={}",
                    500, "呜呜,某某业务出错了!", ExceptionUtils.getStackTrace(e));
        }
    }
    @Test
    public void test4() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={}",
                    500, "呜呜,某某业务出错了!", e);
        }
    }
    private void mockException() {
        throw new RuntimeException("某个运行时异常");
    }
}

image.gif

想好之后看看和我的分析是一样还是有出入。

三、分步解析

我们先看下warn的源码

/**
     * Log a message at the WARN level according to the specified format
     * and arguments.
     * <p/>
     * <p>This form avoids superfluous string concatenation when the logger
     * is disabled for the WARN level. However, this variant incurs the hidden
     * (and relatively small) cost of creating an <code>Object[]</code> before invoking the method,
     * even if this logger is disabled for WARN. The variants taking
     * {@link #warn(String, Object) one} and {@link #warn(String, Object, Object) two}
     * arguments exist solely in order to avoid this hidden cost.</p>
     *
     * @param format    the format string
     * @param arguments a list of 3 or more arguments
     */
    public void warn(String format, Object... arguments);

image.gif

发现前面是format格式化字符串,后面是对应的参数。格式化的占位符(即“{}”)和后面的参数一一对应。

@Test
    public void test0() {
        log.warn("code={},msg={}", 200, "成功!");
    }

image.gif

参数指  200(第一个参数,对应第一个占位符),第二个参数“成功!”对应第二个占位符。

输出日志时拼接在一起:code=200,msg=成功

输出结果为:

00:05:46.731 [main] WARN com.chujianyun.common.log.WarnLogTest - code=200,msg=成功!

和设想的一样(前面是自动追加的可以定制)。

这个占位符和参数和String.format()函数非常相似

public static String format(String format, Object... args) {
        return new Formatter().format(format, args).toString();
    }

image.gif

前面是格式化字符串,后面是对应占位符的参数。

等价于以下代码(我们可以对比学习):

String.format("code=%d,msg=%s", 200, "成功!");

image.gif

因此看第一个,根据上面的参数我们可以推测到,前面的{}是占位符,有两个,后面参数有三个,应该最后一个不显示

@Test
    public void test1() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={}",
                    500, "呜呜,某某业务出错了!", "肿么办?");
        }
    }

image.gif

运行结果:

23:37:18.525 [main] WARN com.chujianyun.common.log.WarnLogTest - code=500,msg=呜呜,某某业务出错了!

果然和我们想的一样!!

我们看第二个:

@Test
    public void test2() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={},e={}",
                    500, "呜呜,某某业务出错了!", JSON.toJSONString(e));
        }
    }

image.gif

根据上面的理论,三个占位符,三个参数应该没问题。

image.png

虽然和我们想的一样,但是打印的太不优雅了,好长而且都在一行里!!!

那我们换一个写法,采用工具类美化一下:

@Test
    public void test3() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={},e={}",
                    500, "呜呜,某某业务出错了!", ExceptionUtils.getStackTrace(e));
        }
    }

image.gif

效果还不错:

image.png

我们再看另外一个写法:

@Test
    public void test4() {
        try {
            mockException();
        } catch (Exception e) {
            log.warn("code={},msg={}",
                    500, "呜呜,某某业务出错了!", e);
        }
    }

image.gif

根据前面的经验,我们认为这个应该不会打印e,因为格式化占位符只有两个,这个是三个参数。

image.png

我们发现结果和我们想的不一样!!!

四、探究

它是一个接口,我们看看他的实现类

image.png

我们以Log4JLoggerAdapter为例吧,看名字就知道是适配器模式。

适配器模式的目的:将一个类的接口转换成客户希望的另外一个接口。

适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

如果想仔细研究可以看文末的参考资料。

我们继续,发现实现的代码在这里(这里是适配的函数):

org.slf4j.impl.Log4jLoggerAdapter#warn(java.lang.String, java.lang.Object...)

public void warn(String format, Object... argArray) {
        if (this.logger.isEnabledFor(Level.WARN)) {
            FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
            this.logger.log(FQCN, Level.WARN, ft.getMessage(), ft.getThrowable());
        }
    }

image.gif

上半部分调用了这里:

final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
        Throwable throwableCandidate = getThrowableCandidate(argArray);
        Object[] args = argArray;
        if (throwableCandidate != null) {
            args = trimmedCopy(argArray);
        }
        return arrayFormat(messagePattern, args, throwableCandidate);
    }

image.gif

然后调用到了这里

static final Throwable getThrowableCandidate(Object[] argArray) {
        if (argArray == null || argArray.length == 0) {
            return null;
        }
        final Object lastEntry = argArray[argArray.length - 1];
        if (lastEntry instanceof Throwable) {
            return (Throwable) lastEntry;
        }
        return null;
    }

image.gif

和这里

private static Object[] trimmedCopy(Object[] argArray) {
        if (argArray == null || argArray.length == 0) {
            throw new IllegalStateException("non-sensical empty or null argument array");
        }
        final int trimemdLen = argArray.length - 1;
        Object[] trimmed = new Object[trimemdLen];
        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
        return trimmed;
    }

image.gif

真相就浮现了!

getThrowableCandidate函数会判断数组最后一个是不是Throwable的子类型,如果是转成Throwable返回给前面,否则返回null.

image.gif

而trimmedCopy(Object[] argArray)函数只复制了参数长度-1个长度,忽略最后一个元素。

最终调用org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[], java.lang.Throwable)去构造打印对象FormattingTuple。

然后调用log4j的

org.apache.log4j.Category#log(java.lang.String, org.apache.log4j.Priority, java.lang.Object, java.lang.Throwable)来实现打印。

public void log(String FQCN, Priority p, Object msg, Throwable t) {
        int levelInt = this.priorityToLevelInt(p);
        this.differentiatedLog((Marker)null, FQCN, levelInt, msg, t);
    }

image.gif

另外我们还可以打断点验证一下(具体自己可单步跟下去)。

image.png

另外特别提示,善用左下角的调用栈,可以看到整个调用链路,双击可以切到上层源码。

因此结论是:

使用org.slf4j.Logger#warn(java.lang.String, java.lang.Object...)时,最后一个参数如果是异常可自动追加到日志中。

这得益于适配器模式,底层实现提供了这种兼容。

另外为什么会调用到适配器这里,可以看文末的我的另外一篇文章《Slf4j的优势与原理》。

五、总结

一、遇到和自己预期不符的代码,一定要借机研究一下,能够学到更多。可能发现自己没注意的点,没掌握好的点,也可能会发现一个隐患或者BUG。

二、遇到问题尽量跟到源码里,从源码角度去分析为什么,是快速成长的一个途径。

三、验证代码是否运行,可以断点,这是一个经验。

附录:

适配器模式  https://www.runoob.com/design-pattern/adapter-pattern.html

Slf4j的优势与原理:https://blog.csdn.net/w605283073/article/details/89683847

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
数据库 应用服务中间件 Dubbo
|
15天前
|
人工智能 自然语言处理 API
阿里云tokens免费体验,新用户超7000万配额申领指南,入口全额度全与相关规则解析
本文详解阿里云百炼面向新用户推出的超7000万Tokens免费体验活动,权益覆盖70余款主流大模型,通过三大官方入口开通即可自动发放额度,明确90天有效期、华北2地域专属、多模型额度独立等核心使用规则。内容同步梳理全品类模型的免费额度标准,配套按量折扣、夜间错峰优惠、Token Plan订阅等后续低成本用模方案,帮助用户从零门槛体验平滑过渡到规模化落地。
|
1月前
|
前端开发 测试技术 Linux
【2026最新】Apifox官网安装和使用保姆级教程
Apifox 是国产免费 API 一体化协作平台,支持 Win/macOS/Linux,全中文界面。集成文档、调试、Mock 数据与自动化测试,一份定义全链路复用,大幅提升开发效率,个人及中小团队零成本上手。
|
6月前
|
人工智能 搜索推荐 数据库
Rokid AI 赛道铜奖 [奖金10000元]:烹饪实时辅助智能体开发总结分享
本项目基于Rokid眼镜与灵珠智能体平台,打造专为厨房场景优化的AI烹饪助手。聚焦高频痛点,采用单Agent架构实现灵活交互、上下文记忆与技能扩展,集成拍照识材、实时指导、多菜统筹等8大功能,并针对小屏显示精简提示、优化进度跟踪,兼顾实用性与创新性。(239字)
330 1
|
2月前
|
数据采集 人工智能 自然语言处理
跨境代购中小卖家生存法则 + 淘宝代购系统降本增效完整方案
本方案为跨境代购中小卖家量身打造,提炼五大生存法则:轻资产纯代采、数字化服务突围、严控成本、合规风控、垂直深耕。配套淘宝代购系统,实现采购、集运、仓储、财务、营销全链路自动化,降本增效,助力稳定盈利。(239字)
|
3月前
|
人工智能 运维 安全
聚力 AI 创新,共启智能新篇|阿里云 AI 创新活动中心,全链路赋能开发者与企业落地 AI
阿里云AI创新活动中心上线!提供免费AI资源、百万级Token补贴、技术赛事及生态对接,覆盖智能体、AIGC、代码生成等场景,助力个人开发者、创业者与企业低成本、低门槛落地AI应用。
|
4月前
|
存储 人工智能 定位技术
企业GEOAI搜索优化实战:高德地图接口适配与位置权重提升避坑指南
当品牌在AI搜索中“隐身”,问题常出在GEOAI优化底层逻辑。本文基于高德地图最新接口,拆解数据接入、语义对齐、权重联动三步法,破解关键词堆砌、字段失配、验证缺失等典型误区,提供可落地、可验证的优化路径。(
|
4月前
|
自然语言处理 搜索推荐 数据挖掘
2026年电商行业有哪些agent应用?从客服、营销到数据决策的实战指南
本文详解电商智能体(Agent)实战应用:瓴羊Quick Service实现客服从应答到经营跃迁;Quick Audience推动营销从圈人到共情进化;Quick BI“智能小Q”助力数据决策从看报表到问答案革命。三者协同构建闭环智能体系,为电商企业提供可复用的分阶段落地指南。(239字)
|
11月前
|
机器学习/深度学习 人工智能 安全
当AI开始自己写AI:自主AI系统的时代正在到来
当AI开始自己写AI:自主AI系统的时代正在到来
1000 92
|
7月前
|
人工智能 算法 机器人
100亿、18个知识点,读懂今晚这场AI春节档
农历2025年除夕,一场近100亿的AI营销大战正酣,却遭遇监管“重拳”——市监总局约谈阿里、腾讯等七大平台,发布《反垄断合规指引》。监管意在遏制内卷式竞争,推动AI竞争从流量争夺转向价值创新。
341 1

热门文章

最新文章