2

AI 自动补全的这句日志能正常打印吗?

 1 year ago
source link: https://mazhuang.org/2023/05/10/can-this-log-print-work/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

AI 自动补全的这句日志能正常打印吗?

2023/05/10 java 共 2474 字,约 8 分钟

最近用上了 GitHub Copilot,它的能力不时让我惊叹,于是越来越多地面向 tab 编程,机械键盘的损耗都小了许多:-p

这天,它给我自动生成了一句像这样的日志打印代码:

try {
    // ...
} catch (Exception e) {
    log.error("Xxx 操作出错,订单号 {},操作人 {}", orderNumber, operatorName, e);
}

我盯着这行熟悉又陌生的代码——没错平时我自己也会这么写,但此时竟然产生了一丝不确定,它真的能按期望的效果,先打印出这句话,然后完整打印异常堆栈吗?

既然有疑惑,那就刨根问底一下。

为什么疑惑?

问了自己这个问题之后,我回想了一下,可能是因为以前遇到过这个:

less-arguments-than-placeholders.png

如果最后一个参数不是 Throwable 类型,那 IDEA 会给出警告:

More arguments provided (3) than placeholders specified (2)

那为什么最后多出来的那个参数是 Throwable,IDE 就认为正常了呢?这就是本文要探索的问题。

遇事不决,command + click 一下。可以看到方法的定义是这样的:

public void error(String format, Object... arguments);

可惜想看具体实现的时候发现实现类太多,索性写一个测试用例 debug 跟一下,一路 F7 进去(这里用的日志框架是 log4j2):

org.apache.logging.slf4j.Log4jLogger#error(java.lang.String, java.lang.Object...)
org.apache.logging.log4j.spi.AbstractLogger#logIfEnabled(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, java.lang.String, java.lang.Object...)
org.apache.logging.log4j.spi.AbstractLogger#logMessage(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, java.lang.String, java.lang.Object...)
org.apache.logging.log4j.message.ParameterizedMessageFactory#newMessage(java.lang.String, java.lang.Object...)
org.apache.logging.log4j.message.ParameterizedMessage#ParameterizedMessage(java.lang.String, java.lang.Object...)
org.apache.logging.log4j.message.ParameterizedMessage#init

秘密就在这里了:

// org.apache.logging.log4j.message.ParameterizedMessage

private void init(final String messagePattern) {
    this.messagePattern = messagePattern;
    final int len = Math.max(1, messagePattern == null ? 0 : messagePattern.length() >> 1); // divide by 2
    this.indices = new int[len]; // LOG4J2-1542 ensure non-zero array length
    // 计算占位符个数
    final int placeholders = ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices);
    initThrowable(argArray, placeholders);
    this.usedCount = Math.min(placeholders, argArray == null ? 0 : argArray.length);
}

private void initThrowable(final Object[] params, final int usedParams) {
    if (params != null) {
        final int argCount = params.length;
        // 如果占位符个数比参数个数少,且最后一个参数是 throwable 类型,
        // 则将最后一个参数赋值给 Message 的成员
        if (usedParams < argCount && this.throwable == null && params[argCount - 1] instanceof Throwable) {
            this.throwable = (Throwable) params[argCount - 1];
        }
    }
}

然后在调用堆栈回溯几步有:

// org.apache.logging.log4j.spi.AbstractLogger

protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
        final Object... params) {
    final Message msg = messageFactory.newMessage(message, params);
    logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
}

至此基本上清晰了。

经过分析及实际运行验证:

  • AI 生成的代码可以按期望效果打印;
  • 如果有比占位符多的非 Throwable 类型参数,会被忽略掉。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK