You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Log4j2中按需创建带类名的同步/异步日志器问题

解决方案:类名Logger绑定自定义同步/异步Appender组合

你的需求核心是让Logger名称为类名(满足%logger输出要求),同时根据选择灵活绑定同步/异步的文件/数据库Appender,不用再依赖配置里的固定名称Logger。下面是具体的实现方案:

第一步:修改log4j2.xml配置

先把原来的固定名称Logger替换为「基础父Logger」,每个父Logger对应一种同步/异步+目标的组合,这样所有类名Logger都可以继承这些父Logger的配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%sn] [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <RandomAccessFile name="randomAccessFile" fileName="temp\logs.log" immediateFlush="false" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </RandomAccessFile>
        <JDBC name="databaseAppender" tableName="app_logs" bufferSize="1000">
            <ConnectionFactory class="com.mc.loggerProvider.ConnectionFactory" method="getDatabaseConnection" />
            <Column name="LOG_ID" pattern="%u{TIME}" isUnicode="false" />
            <Column name="LOG_TIMESTAMP" isEventTimestamp="true" isUnicode="false" />
            <Column name="LOG_LEVEL" pattern="%level" isUnicode="false" />
            <Column name="LOGGER_NAME" pattern="%logger" isUnicode="false" />
            <Column name="MESSAGE" pattern="%msg %ex{full}" isUnicode="false" />
        </JDBC>
    </Appenders>
    <Loggers>
        <!-- 基础父Logger:对应不同类型的同步/异步+Appender组合 -->
        <AsyncLogger name="base.asyncDB" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="databaseAppender" />
        </AsyncLogger>
        <Logger name="base.syncDB" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="databaseAppender" />
        </Logger>
        <AsyncLogger name="base.asyncFile" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="randomAccessFile" />
        </AsyncLogger>
        <Logger name="base.syncFile" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="randomAccessFile" />
        </Logger>
        <AsyncLogger name="base.asyncDBasyncFile" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="databaseAppender" />
            <AppenderRef ref="randomAccessFile" />
        </AsyncLogger>
        <Logger name="base.syncDBsyncFile" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="databaseAppender" />
            <AppenderRef ref="randomAccessFile" />
        </Logger>

        <Root level="info" includeLocation="false">
            <AppenderRef ref="console" />
        </Root>
    </Loggers>
</Configuration>

第二步:改造LogProvider类

通过Log4j2的API动态给类名Logger绑定对应的父Logger,这样类名Logger会继承父Logger的同步/异步特性和Appender配置,同时保留类名作为Logger名称:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.LoggerConfig;

public class LogProvider {
    // 基础父Logger的前缀,和配置里的name对应
    private static final String BASE_LOGGER_PREFIX = "base.";

    public static Logger registerFor(Class<?> clazz, AppendersType type) {
        // 用类的全限定名作为Logger名称
        String loggerName = clazz.getName();
        // 获取对应类型的基础父Logger名称
        String parentLoggerName = BASE_LOGGER_PREFIX + getParentLoggerSuffix(type);

        // 获取Log4j2的全局上下文
        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        // 获取类名对应的Logger配置(如果不存在则是默认的Root配置)
        LoggerConfig loggerConfig = loggerContext.getConfiguration().getLoggerConfig(loggerName);

        // 如果是默认配置,创建新的LoggerConfig绑定类名
        if (loggerConfig.getName().equals(LoggerConfig.ROOT)) {
            loggerConfig = new LoggerConfig(loggerName, null, false);
            loggerContext.getConfiguration().addLogger(loggerName, loggerConfig);
        }

        // 获取基础父Logger的配置
        LoggerConfig parentLoggerConfig = loggerContext.getConfiguration().getLoggerConfig(parentLoggerName);
        // 设置类名Logger的父Logger,继承其Appender和同步/异步特性
        loggerConfig.setParent(parentLoggerConfig);
        // 关闭additivity,避免日志重复输出到Root的Appender(按需调整)
        loggerConfig.setAdditivity(false);
        // 更新上下文配置
        loggerContext.updateLoggers();

        // 返回类名对应的Logger
        return LogManager.getLogger(clazz);
    }

    // 映射AppendersType到基础父Logger的后缀
    private static String getParentLoggerSuffix(AppendersType type) {
        return switch (type) {
            case DATABASE_ASYNC -> "asyncDB";
            case DATABASE_SYNC -> "syncDB";
            case FILE_ASYNC -> "asyncFile";
            case FILE_SYNC -> "syncFile";
            case FILE_DATABASE_SYNC -> "syncDBsyncFile";
            case FILE_DATABASE_ASYNC -> "asyncDBasyncFile";
            default -> throw new IllegalArgumentException("Unsupported AppendersType: " + type);
        };
    }
}

第三步:业务代码调用方式

现在你可以直接在业务类中按规范调用,同时指定需要的日志类型:

public class MyBusinessClass {
    // 选择异步数据库日志,Logger名称为MyBusinessClass
    private static final Logger logger = LogProvider.registerFor(MyBusinessClass.class, AppendersType.DATABASE_ASYNC);

    public void doBusiness() {
        // %logger会输出MyBusinessClass,日志异步写入数据库
        logger.info("Processing business logic...");
    }
}

关键原理说明

  1. 基础父Logger:把不同同步/异步+Appender的组合抽象成父Logger,避免重复配置每个类的Logger。
  2. 动态绑定父Logger:通过Log4j2的API给类名Logger设置父Logger,让它继承父Logger的所有配置(包括异步特性、Appender、日志级别等)。
  3. 保留类名作为Logger名称:最终返回的Logger是用LogManager.getLogger(clazz)获取的,所以%logger会输出类的全限定名,满足你的日志规范要求。

内容的提问来源于stack exchange,提问作者lovelyim92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:08:42