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

如何使用java.util.logging在运行时为日志添加小程序名称前缀?

问题

我有一个运行在Tomcat上的Java Web应用,它可解释并运行其他小型Java应用(Beanshells)。主应用与小程序均使用默认的java.util.logging,主应用通过logging.properties文件配置将日志写入独立于Tomcat主日志的文件中。

我希望在小程序解释器运行时,将当前小程序名称添加到每条日志语句开头。使用默认的java.util.logging能否实现这一需求?

我曾尝试在logging.properties的格式字符串中使用名为app.name的系统属性,在运行时设置和清除该属性,但未成功——日志内容前未出现预期前缀。相关配置与代码如下:

主应用logging.properties配置

handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

org.apache.juli.FileHandler.level=INFO
org.apache.juli.FileHandler.directory=${catalina.base}/logs
org.apache.juli.FileHandler.prefix=apprunner.
org.apache.juli.FileHandler.encoding=UTF-8
org.apache.juli.FileHandler.formatter=java.util.logging.SimpleFormatter

java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

java.util.logging.SimpleFormatter.format=${app.name} %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n

com.apprunner.App类的evalJava方法代码

logger.info("### BEGIN JAVA EXECUTION ###");
System.setProperty("app.name", ui_object_name);
logger.info(System.getProperty("app.name"));

实际日志输出(apprunner.[date].log)

Jul 19, 2023 4:09:29 PM com.apprunner.App evalJava
INFO: ### BEGIN JAVA EXECUTION ###
Jul 19, 2023 4:09:29 PM com.apprunner.App evalJava
INFO: testapp/app/testapp.java
解决方案

可以实现,但直接用系统属性的方式行不通——java.util.logging.SimpleFormatter的格式字符串在Handler初始化阶段就会解析系统属性,后续运行时修改System.setProperty不会触发格式字符串的重新解析,所以你的尝试无法生效。以下是两种可行方案:

方案一:自定义Formatter结合ThreadLocal

这种方式适合多并发场景,通过线程隔离的方式存储当前小程序名称,避免干扰其他请求。

  1. 编写自定义Formatter类
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

public class AppNameFormatter extends Formatter {
    // ThreadLocal存储当前线程的小程序名称,线程隔离避免并发冲突
    private static final ThreadLocal<String> CURRENT_APP_NAME = new ThreadLocal<>();

    // 设置当前小程序名称的静态方法
    public static void setCurrentAppName(String appName) {
        CURRENT_APP_NAME.set(appName);
    }

    // 清除当前线程的小程序名称,防止线程池复用导致的残留
    public static void clearCurrentAppName() {
        CURRENT_APP_NAME.remove();
    }

    @Override
    public String format(LogRecord record) {
        String appName = CURRENT_APP_NAME.get();
        // 无小程序名称时用空前缀,也可设置默认值如"[UNKNOWN]"
        String prefix = appName != null ? appName + " " : "";

        // 按需求拼接日志格式
        return String.format(
            "%s%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n",
            prefix,
            record.getMillis(),
            record.getLoggerName(),
            record.getLevel().getLocalizedName(),
            formatMessage(record),
            record.getThrown() != null ? "\n" + getStackTrace(record.getThrown()) : ""
        );
    }

    // 辅助方法:转换异常堆栈为字符串
    private String getStackTrace(Throwable thrown) {
        StringWriter sw = new StringWriter();
        thrown.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }
}
  1. 修改logging.properties配置
    将原有的SimpleFormatter替换为自定义的AppNameFormatter:
handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

org.apache.juli.FileHandler.level=INFO
org.apache.juli.FileHandler.directory=${catalina.base}/logs
org.apache.juli.FileHandler.prefix=apprunner.
org.apache.juli.FileHandler.encoding=UTF-8
org.apache.juli.FileHandler.formatter=com.apprunner.AppNameFormatter

java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=com.apprunner.AppNameFormatter
  1. 调整evalJava方法逻辑
    在小程序执行前后设置并清除ThreadLocal值,务必在finally块中清除,避免线程池复用导致的名称残留:
logger.info("### BEGIN JAVA EXECUTION ###");
try {
    AppNameFormatter.setCurrentAppName(ui_object_name);
    logger.info("小程序开始运行");
    // 执行小程序核心逻辑
} finally {
    AppNameFormatter.clearCurrentAppName();
}

方案二:为每个小程序创建独立Logger

如果小程序数量不多且有唯一标识,可以为每个小程序创建专属Logger,利用Logger名称作为日志前缀。

  1. 修改evalJava方法
logger.info("### BEGIN JAVA EXECUTION ###");
Logger appLogger = Logger.getLogger(ui_object_name);
appLogger.info("小程序开始运行");
// 执行小程序逻辑,使用appLogger输出日志
  1. 调整logging.properties格式
    在格式字符串中加入Logger名称(即小程序名称),%2$s对应Logger名称占位符:
java.util.logging.SimpleFormatter.format=%2$s %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %5$s%6$s%n

注意事项

  • 方案一中的ThreadLocal必须在finally块清除,Tomcat使用线程池复用线程,残留的名称会导致后续请求日志异常。
  • 自定义Formatter需放在主应用类路径下,确保Tomcat能正常加载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 15:13:23