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

如何为自定义Service类实现日志输出的自定义格式化?

自定义日志格式化:将Service实例作为单个参数格式化

问题背景

我正在为包含不同元素的服务开发Logger,现有代码如下:

抽象Service类:

public abstract class Service{
    String name = "";
    String uId = "";
    int counter = 0;

    Logger logger = new Logger();

    public void info(String message) {
        logger.info(this, message);
    }
}

ServiceA实现类:

public class ServiceA extends Service{

    public ServiceA() {
        this.name = ...;
        this.uId = ...;
        this.counter = ...;
    }
}

期望日志输出格式:

[INFO] Timestamp - name (uId) #counter: message

其中Timestamp为当前时间,name、uId、counter和message由方法调用提供。目前的实现是用String.format硬编码Service的三个字段:

public class Logger {

    public void info(Service service, String message){
        String loggingMessage = String.format("[INFO] %s - %s (%s) #%d: %s", timer.getTs(), service.name, 
                service.uId, service.counter, message);
        logMessage(loggingMessage);
    }
}

希望实现类似Java Calendar的格式化方式,仅传入Service实例作为单个参数,通过格式字符串定义输出样式,而非硬编码多个字段,比如:

Calendar c = new GregorianCalendar(1995, MAY, 23);
String s = String.format("Duke's Birthday: %1$tb %1$te, %1$tY", c);
// -> s == "Duke's Birthday: May 23, 1995"

请问是否可以定义自定义格式化器实现这种效果?或者有其他更合适的日志格式化方案?


解决方案

方案一:自定义Formatter实现参数化格式化

Java的String.format支持通过扩展Formatter实现自定义对象的格式化,模拟Calendar的格式符风格:

  1. 实现自定义ServiceFormatter
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

public class ServiceFormatter extends Formatter {
    @Override
    public Formatter format(String format, Object... args) {
        // 替换自定义格式符:%tn=name,%tu=uId,%tc=counter
        String modifiedFormat = format;
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Service) {
                String idx = String.valueOf(i + 1);
                modifiedFormat = modifiedFormat.replaceAll("%" + idx + "\\$tn", "%s")
                        .replaceAll("%" + idx + "\\$tu", "%s")
                        .replaceAll("%" + idx + "\\$tc", "%d");
            }
        }

        // 重新组装参数,拆分Service实例的字段
        List<Object> argList = new ArrayList<>();
        for (Object arg : args) {
            if (arg instanceof Service) {
                Service s = (Service) arg;
                argList.add(s.name);
                argList.add(s.uId);
                argList.add(s.counter);
            } else {
                argList.add(arg);
            }
        }
        return super.format(modifiedFormat, argList.toArray());
    }
}
  1. 在Logger中使用自定义格式化
public class Logger {
    private final ServiceFormatter formatter = new ServiceFormatter();
    private final Timer timer = new Timer(); // 假设已有Timer类

    public void info(Service service, String message) {
        String loggingMessage = formatter.format("[INFO] %s - %1$tn (%1$tu) #%1$tc: %s",
                timer.getTs(), service, message);
        logMessage(loggingMessage);
    }

    private void logMessage(String msg) {
        System.out.println(msg);
    }
}

这种方式可以通过格式字符串灵活调整字段顺序或显示逻辑,完全复用Java格式化语法的特性。

方案二:利用成熟日志框架的MDC机制

如果不想自己实现格式化逻辑,推荐使用SLF4J+Logback/Log4j2这类框架,通过MDC(映射诊断上下文)分离格式定义与业务代码:

  1. 修改Service类注入MDC
import org.slf4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class Service{
    String name = "";
    String uId = "";
    int counter = 0;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    public void info(String message) {
        // 将Service字段放入MDC上下文
        MDC.put("serviceName", name);
        MDC.put("serviceUId", uId);
        MDC.put("serviceCounter", String.valueOf(counter));
        try {
            logger.info(message);
        } finally {
            // 清理MDC避免内存泄漏
            MDC.remove("serviceName");
            MDC.remove("serviceUId");
            MDC.remove("serviceCounter");
        }
    }
}
  1. 在Logback配置文件中定义格式
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[INFO] %d{yyyy-MM-dd HH:mm:ss.SSS} - %X{serviceName} (%X{serviceUId}) #%X{serviceCounter}: %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

这种方式完全解耦格式定义与业务代码,还能享受框架提供的异步日志、滚动归档等高级功能。

方案三:重写Service的toString()(简化版)

如果只是想简化参数传递,无需灵活调整格式,可以重写Service的toString()方法:

public abstract class Service{
    String name = "";
    String uId = "";
    int counter = 0;

    Logger logger = new Logger();

    public void info(String message) {
        logger.info(this, message);
    }

    @Override
    public String toString() {
        return String.format("%s (%s) #%d", name, uId, counter);
    }
}

Logger代码简化为:

public class Logger {
    private final Timer timer = new Timer();

    public void info(Service service, String message){
        String loggingMessage = String.format("[INFO] %s - %s: %s", timer.getTs(), service, message);
        logMessage(loggingMessage);
    }

    private void logMessage(String msg) {
        System.out.println(msg);
    }
}

这种方式实现成本最低,但格式固定,无法动态调整。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 06:05:02