如何为自定义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的格式符风格:
- 实现自定义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()); } }
- 在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(映射诊断上下文)分离格式定义与业务代码:
- 修改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"); } } }
- 在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
相关产品推荐
相关产品推荐

