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

如何通过自定义工具类将指定类日志写入common.log(不修改log4j配置)

实现方案

1. 澄清配置类型

首先需要明确:你提供的配置是Logback(基于ch.qos.logback.core相关类)而非Log4j,后续实现基于Logback框架完成。

2. 实现线程安全的CustomUtility类

该类负责写入common.log,通过同步机制保证多线程环境下日志不混乱,同时保持与app.log一致的日志格式:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomUtility {
    // 从系统变量读取common.log路径,与原有配置保持统一
    private static final String COMMON_LOG_PATH = System.getProperty("home") + "/logs/common.log";
    // 同步锁,避免多线程写入时日志错乱
    private static final Object WRITE_LOCK = new Object();
    // 复用原有日志时间格式
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM.dd HH:mm:ss.SSS");

    public static void writeCommonLog(String loggerName, String methodName, String level, String message) {
        synchronized (WRITE_LOCK) {
            try (PrintWriter writer = new PrintWriter(new FileWriter(COMMON_LOG_PATH, true))) {
                String timestamp = DATE_FORMAT.format(new Date());
                // 严格对齐app.log的日志格式
                String logContent = String.format("%s [%2.2t][%s.%s] %-5s - %s%n",
                        timestamp, Thread.currentThread().getId(),
                        loggerName.substring(loggerName.lastIndexOf('.') + 1), methodName,
                        level, message);
                writer.write(logContent);
            } catch (IOException e) {
                // 写入失败时降级到原有日志系统记录错误,避免信息丢失
                org.slf4j.LoggerFactory.getLogger(CustomUtility.class).error("Failed to write to common.log", e);
            }
        }
    }
}

3. 配置Logback TurboFilter拦截指定类日志

添加自定义TurboFilter,拦截目标类的日志请求,调用CustomUtility写入common.log,同时阻止这些日志进入原有app.log的输出链路,无需新增appender或设置additivity="false":

<!-- 原有app.log的appender配置保持不变 -->
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${home}/logs/app.log</File>       
    <encoder>
        <pattern>%d{MM.dd HH:mm:ss.SSS} [%2.2t][%c{1}.%M] %-5p - %m%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
        <maxIndex>20</maxIndex>
        <FileNamePattern>${home}/logs/app.log.%i</FileNamePattern>
    </rollingPolicy>
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
        <MaxFileSize>25MB</MaxFileSize>
    </triggeringPolicy>
</appender>

<!-- 添加自定义TurboFilter,拦截指定类的日志 -->
<turboFilter class="com.yourpackage.CommonLogInterceptor">
    <!-- 配置需要拦截的目标类全限定名,多个类用逗号分隔 -->
    <targetClasses>com.yourpackage.TargetService,com.yourpackage.TargetDao</targetClasses>
</turboFilter>

<!-- 根日志配置保持原有指向logFile appender -->
<root level="info">
    <appender-ref ref="logFile"/>
</root>

实现TurboFilter类

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.Marker;

public class CommonLogInterceptor extends TurboFilter {
    private String targetClasses;
    private String[] targetClassList;

    @Override
    public void start() {
        if (targetClasses == null || targetClasses.trim().isEmpty()) {
            addError("targetClasses property must be configured");
            return;
        }
        // 拆分配置的目标类列表,去除空格
        targetClassList = targetClasses.split(",");
        for (int i = 0; i < targetClassList.length; i++) {
            targetClassList[i] = targetClassList[i].trim();
        }
        super.start();
    }

    @Override
    public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format, Object[] params, Throwable t) {
        String loggerName = logger.getName();
        // 判断当前日志是否来自目标类
        for (String targetClass : targetClassList) {
            if (loggerName.equals(targetClass)) {
                // 从堆栈中获取日志调用的方法名
                String methodName = extractCallingMethod();
                // 调用CustomUtility写入common.log
                CustomUtility.writeCommonLog(loggerName, methodName, level.toString(), String.format(format, params));
                // 返回DENY阻止日志进入原有appender
                return FilterReply.DENY;
            }
        }
        // 非目标类日志正常放行
        return FilterReply.NEUTRAL;
    }

    private String extractCallingMethod() {
        // 遍历堆栈,跳过框架和工具类的调用,找到业务代码的方法名
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement element : stackTrace) {
            String className = element.getClassName();
            if (!className.equals(this.getClass().getName())
                    && !className.equals(CustomUtility.class.getName())
                    && !className.startsWith("ch.qos.logback")
                    && !className.startsWith("org.slf4j")) {
                return element.getMethodName();
            }
        }
        return "unknown";
    }
}

4. 关键注意事项

  • 线程安全保障:CustomUtility通过synchronized锁确保多线程写入common.log时不会出现日志串线,也可替换为FileLock或高性能线程安全日志组件优化。
  • 格式一致性:严格对齐app.log的日志格式,避免日志风格混乱。
  • 错误降级:当common.log写入失败时,自动降级到原有日志系统记录错误,防止日志丢失。
  • 类名精确匹配:TurboFilter配置的targetClasses必须是目标类的全限定名,多个类用逗号分隔。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 03:55:29