java: 自定义java.util.logging.Logger的日志输出格式,输出IDE(ECLIPSE)能自动识别行号的格式

本文探讨了Java内置日志工具java.util.logging.Logger的不足,并介绍了如何通过自定义Formatter类实现类似log4j的日志输出格式,包括行号和源码链接,以提升开发调试效率。

不好用的 java.util.logging.Logger

我们知道在java环境有很不少第三方提供的日志记录库,比如常用的log4j,其实JDK (1.4 or above)本身也提供了日志输出工具,就是 java.util.logging.Logger.但这个工具吧,用也能用,却不好用,聊胜于无。
对于用惯了log4j的我来说,它不好用,
下是用java.util.logging.Logger输出的日志

import org.junit.Test;
import java.util.logging.Logger;

public class LoggerTest {
	@Test
	public void test() {
		Logger logger = Logger.getLogger(LoggerTest.class.getSimpleName());
		logger.info("hello,world");
	}

}

日志输出了两行,还没有显示行号,在开发环境中我找这行日志的输出位置很麻烦啊。

十一月 09, 2022 5:21:39 下午 net.gdface.utils.SimpleConsoleFormatterTest test
信息: hello,world

同样的代码用log4j输出,

import org.apache.log4j.Logger;
import org.junit.Test;

public class LoggerTest {

    @Test
    public void testLog4j() {
        Logger logger = Logger.getLogger(LoggerTest.class);
        logger.info("hello,world");
    }

}

[main][INFO ] (LoggerTest.java:13) hello,world

比较就可以看出log4j提供的输出内容更短,但提供了更多有效内容,即代码行号,并且在eclipse等IDE中这个格式的输出可以提供超链直接定位到对应的源码位置,这在开发中就方便多了。

不得不用 java.util.logging.Logger 的时候

所以在一般的开发中我肯定更愿意使用log4j,但是 java.util.logging.Logger 也有它存在的意义,它是JDK内置的,它不需要第三方库支持,这在一些偏底层的中间件开发时就有优势了。

但是 java.util.logging.Logger 的输出实在太不友好了,怎么办?我还是习惯log4j的输出格式啊,可不可以自定义输出格式呢?google一查,还真可以(参见 《How do I create a custom logger Formatter?》)。
java.util.logging.Logger 知道自己不能满足各种环境的输出格式需求,所以它的日志输出格式本来就是可以自定义的,

java.util.logging.Formatter 就是用于提供日志输出格式控制的类,要实现自定义的日志输出格式,只要继承此类,重写format(LogRecord record)方法就可以了。

以下就是我基于Formatter实现的日志输出格式的自定义格式类,它可以输出与log4j完全一样的日志格式。

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * 为 {@link java.util.logging.Logger}实现自定义的日志输出,可以输出IDE(eclipse)自动识别源码位置的日志格式。方便调试
 * @author guyadong
 * @since 2.7.0
 */
public class SimpleConsoleFormatter extends Formatter {

	@Override
	public String format(LogRecord record) {
		String message = formatMessage(record);
		String throwable = "";
		if (record.getThrown() != null) {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			pw.println();
			record.getThrown().printStackTrace(pw);
			pw.close();
			throwable = "\n" + sw.toString();
		}
		Thread currentThread = Thread.currentThread();
		StackTraceElement stackTrace = currentThread.getStackTrace()[8];
		return String.format("[%s] (%s:%d) %s%s\n",
				Thread.currentThread().getName(),
				stackTrace.getFileName(),
				stackTrace.getLineNumber(),
				message,
				throwable);
	}
	/**
	 * 将{@link SimpleConsoleFormatter}实例指定为{@link Logger}的输出格式
	 * @param logger
	 * @return always logger
	 */
	public static Logger installFormatter(Logger logger){
		if(null != logger){
			/** 禁用原输出handler,否则会输出两次 */
			logger.setUseParentHandlers(false);
			ConsoleHandler consoleHandler = new ConsoleHandler();
			consoleHandler.setFormatter(new SimpleConsoleFormatter());
			logger.addHandler(consoleHandler);
		}
		return logger;
	}
}

写个测试代码看看效果:

import org.junit.Test;

import net.gdface.logger.SimpleConsoleFormatter;

import java.util.logging.Logger;
public class LoggerTest {
	@Test
	public void test2Logger() {
		/** 
		 * 调用 SimpleConsoleFormatter.installFormatter 
		 * 将SimpleConsoleFormatter实例设置为Logger格式输出控制对象+ 
		 */
		Logger logger = SimpleConsoleFormatter.installFormatter(Logger.getLogger(LoggerTest.class.getSimpleName()));
		logger.info("hello,world");
	}

}

输出与log4j完全一样,eclipse会自动识别日志中的代码行号,生成跳转的链接,完美。

[main] (LoggerTest.java:12) hello,world

SimpleConsoleFormatter 实现参见码云仓库:
(https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/logger/SimpleConsoleFormatter.java)

参考资料

《How do I create a custom logger Formatter?》

《How to get Eclipse Console to hyperlink text to source code files?》

七月 17, 2025 5:08:23 下午 org.apache.catalina.startup.TldConfig execute 信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. 七月 17, 2025 5:08:25 下午 org.apache.catalina.core.ContainerBase addChildInternal 严重: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/pty]] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652) at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1092) at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1984) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:750) Caused by: java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at java.util.Arrays$ArrayList.<init>(Arrays.java:3813) at java.util.Arrays.asList(Arrays.java:3800) at com.pty.pa.license.service.PtyAppLicenseServiceImpl.checkRepetitionFile(PtyAppLicenseServiceImpl.java:508) at com.pty.pa.license.service.PtyAppLicenseServiceImpl.loadLicense(PtyAppLicenseServiceImpl.java:176) at com.pty.pa.license.ModuleEnvironmentProperty.postProcessEnvironment(ModuleEnvironmentProperty.java:30) at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:100) at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:86) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131) at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:82) at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:63) at java.util.ArrayList.forEach(ArrayList.java:1259) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:117) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:111) at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:62) at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:362) at org.springframework.boot.SpringApplication.run(SpringApplication.java:320) at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:173) at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:153) at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:95) at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:174) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5632) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145) ... 10 more 七月 17, 2025 5:08:25 下午 org.apache.catalina.startup.HostConfig deployWAR 严重: Error deploying web application archive /var/lib/tomcat/webapps/pty.war java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/pty]] at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:903) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652) at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1092) at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1984) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:750) 七月 17, 2025 5:08:25 下午 org.apache.catalina.startup.HostConfig deployWAR 信息: Deployment of web application archive /var/lib/tomcat/webapps/pty.war has finished in 23,253 ms 七月 17, 2025 5:08:25 下午 org.apache.coyote.AbstractProtocol start 信息: Starting ProtocolHandler ["http-bio-8080"] 七月 17, 2025 5:08:25 下午 org.apache.coyote.AbstractProtocol start 信息: Starting ProtocolHandler ["ajp-bio-8009"] 七月 17, 2025 5:08:25 下午 org.apache.catalina.startup.Catalina start 信息: Server startup in 23329 ms 帮忙分析一下问题
07-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值