Log4j2结合Spring MessageSource简化日志代码实现求助
解决方案
要实现log.debug("message-2", logArgs)这种简化写法,同时保留延迟加载消息的性能优势,我们可以通过扩展Log4j2的消息处理机制,结合Spring的MessageSourceAccessor来实现。
步骤1:创建MessageSourceAccessor静态持有类
由于自定义的Log4j2消息需要访问Spring管理的MessageSourceAccessor,我们用一个静态Holder类来持有它,确保Spring启动时完成注入:
import org.springframework.context.MessageSourceAccessor; import org.springframework.stereotype.Component; @Component public class MessageSourceAccessorHolder { public static MessageSourceAccessor accessor; @Autowired public void setAccessor(MessageSourceAccessor accessor) { MessageSourceAccessorHolder.accessor = accessor; } }
步骤2:实现自定义Log4j2 Message类
这个类会延迟构造本地化消息,仅当日志级别满足时才调用MessageSourceAccessor获取消息内容:
import org.apache.logging.log4j.message.Message; public class LocalizedMessage implements Message { private final String messageKey; private final Object[] args; private String formattedMessage; public LocalizedMessage(String messageKey, Object[] args) { this.messageKey = messageKey; this.args = args; } @Override public String getFormattedMessage() { if (formattedMessage == null) { // 延迟加载:仅当需要输出日志时才构造消息 formattedMessage = MessageSourceAccessorHolder.accessor.getMessage(messageKey, args); } return formattedMessage; } @Override public String getFormat() { return messageKey; } @Override public Object[] getParameters() { return args; } @Override public Throwable getThrowable() { return null; } @Override public void formatTo(StringBuilder buffer) { buffer.append(getFormattedMessage()); } }
步骤3:实现自定义MessageFactory
这个工厂类负责创建我们的LocalizedMessage实例,替代Log4j2默认的消息处理:
import org.apache.logging.log4j.message.AbstractMessageFactory; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ParameterizedMessage; public class LocalizedMessageFactory extends AbstractMessageFactory { @Override public Message newMessage(String messageKey, Object... args) { // 可选:区分本地化消息和普通参数化消息 // 如果消息包含{},则使用默认的参数化消息处理 if (messageKey.contains("{}")) { return new ParameterizedMessage(messageKey, args); } // 否则当作本地化消息键处理 return new LocalizedMessage(messageKey, args); } // 重载基础方法以支持不同参数数量的调用 @Override public Message newMessage(String messageKey, Object p0) { return newMessage(messageKey, new Object[]{p0}); } @Override public Message newMessage(String messageKey, Object p0, Object p1) { return newMessage(messageKey, new Object[]{p0, p1}); } }
步骤4:配置Log4j2使用自定义MessageFactory
有两种方式让Log4j2使用我们的工厂:
方式A:通过Lombok配置自动生成(推荐)
在项目根目录创建lombok.config文件,添加以下内容,让Lombok的@Log4j2注解自动使用自定义工厂:
lombok.log.log4j2.messageFactory=com.yourpackage.LocalizedMessageFactory
替换com.yourpackage为你实际的包路径。
方式B:手动创建Logger实例
如果不想修改Lombok配置,可以在每个类中手动创建Logger:
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; @Component public class MessageSourceAccessorRunner implements ApplicationRunner { private static final Logger log = LogManager.getLogger(MessageSourceAccessorRunner.class, new LocalizedMessageFactory()); @Override public void run(ApplicationArguments args) throws Exception { Object[] logArgs = {Instant.now()}; // 现在可以直接用简化写法,性能和Supplier方式一致 log.debug("message-2", logArgs); } }
关键说明
- 延迟加载机制:Log4j2会先判断日志级别是否允许输出,只有当级别满足时才会调用
getFormattedMessage()方法,此时才会触发MessageSourceAccessor的消息构造逻辑,完全等价于log.debug(() -> accessor.getMessage("message-2", logArgs))的性能效果。 - 兼容普通消息:如果需要使用Log4j2默认的参数化消息(如
log.info("Hello {}", name)),可以在LocalizedMessageFactory的newMessage方法中通过判断消息是否包含{}来切换处理逻辑,确保两种消息格式都能正常使用。
内容的提问来源于stack exchange,提问作者Stefano Riffaldi
相关产品推荐
相关产品推荐

