java logging 格式化_Spring源码使用java.util.logging打印日志

初记于2020/9/16日晚间不加班。前几天搭建了gradle下spring5.1系列源码,发现跑起来也没有框架任何日志打印,看到spring中有个模块为spring-jcl, 联想到JUL(java util logging),于是有了此文记录。这篇文章偏随性,晚间学习了啥记啥。

先能打出来日志

之前的文章里已经简单能用 AnnotationConfigApplicationContext跑起来了, 只要加上如下的配置,以及 logging.properties文件即可打印出JUL的日志(JUL JCL目前傻傻分不清楚).

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

import java.util.logging.LogManager;

public class ContextTests {

private static final Log LOGGER = LogFactory.getLog(ContextTests.class);

public static void main(String[] args) {

LOGGER.debug("步骤一");

try {

LogManager.getLogManager().readConfiguration(new ClassPathResource("logging.properties").getInputStream());

} catch (IOException e) {

e.printStackTrace();

}

LOGGER.debug("步骤二");

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

Service service = context.getBean(Service.class);

System.out.println(service);

}

}

logging.properties文件如下:

handlers= java.util.logging.ConsoleHandler

.level= ALL

java.util.logging.ConsoleHandler.level = ALL

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

再次调试源码查看控制台:如愿以偿看到了spring框架的源码(但是这格式、这文字也太丑了吧)

5e6dedf1869ef475a59a4429d0f2289b.png

分析为什么打出来了日志

代码相较于之前多了如下几行,LOGGER对象是证明就是在 LogManager.getLogManager().readConfiguration(new ClassPathResource("logging.properties").getInputStream());这一行生效之后才打印日志。 加载了自定义的classpath下的 loggging.properties文件。

private static final Log LOGGER = LogFactory.getLog(ContextTests.class);

public static void main(String[] args) {

LOGGER.debug("步骤一");

try {

LogManager.getLogManager().readConfiguration(new ClassPathResource("logging.properties").getInputStream());

} catch (IOException e) {

e.printStackTrace();

}

LOGGER.debug("步骤二");

小笔记--logging.properties

logger.properties文件有点眼熟,用EveryThing或Listary工具搜索电脑上这个文件,居然在JDK/jre/lib目录下发现了该文件,肯定有其用武之处(我上面的logging.properties就是复制这个文件的)。

2964ee82b849a3a051aca0a9b2600edb.png

今晚学习到使用logging.properties文件有三种方式:

1.修改jdk_home/jre/lib目录下的logging.properties,默认配置文件打印级别是INFO,spring源码框架日志级别都是INFO以下(TRACE DEBUG),所以启动项目看不到任何代码。

如果不知道JUL日志级别,搜索LEVEL这个类,日志级别从下往上越来越高。 jre/lib/logging.properties修改两处即可.level 以及 java.util.logging.ConsoleHandler.level ,改成什么级别不多说了,ALL就可以了。

0a3f0dc30d632cdbb0a6853b9616f75c.png

2.修改JVM启动参数

修改的JVM参数是 java.util.logging.config.file, 比如我本地 -Djava.util.logging.config.file=F:\SourceCode\0909\sf\springlearning\src\main\resources\logging.properties,

亲测可行哦.

3.手动去读取配置文件

像我一样,在代码里去主动读取指定目录(工程目录下也行)配置文件,这样子虽然要自己写一些代码,但可以放到static代码块、父类中去完成。

try {

LogManager.getLogManager().readConfiguration(new ClassPathResource("logging.properties").getInputStream());

} catch (IOException e) {

e.printStackTrace();

}

以上三种方式在本地均可行,知识来源如下。 没错就是 LogManager这个类,document中详细描述了如何自定义加载 logging.properties文件。 偶然之间单步调试进入到这个类发现这些方法的。

3c4cb4bdea62d00c88b889f68bf4bd5d.png

分析为什么打印出来这种日志?

再回头看我们的logging.properties感觉也没有定义输出格式,就打印出来这种样式。 日志拖到2行展示,中文月份、日志级别夹杂着时间、类名、方法名,像极了自己的英语口语,My English 666。

f1619d65cb92582cb63b87ee12a24e6f.png

日志打印格式在logging.properties指定了,默认给我们指定了. java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter , SimpleFormatter中第一行就是格式化打印出来的日志,默认值通过查看LoggingSupport.getSimpleFormat(), 优先级 命令参数中java.util.logging.SimpleFormatter.format > 配置文件(指定配置文件、jre\lib目录下都算)中 java.util.logging.SimpleFormatter.format > "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n" , 最后那串密码一样的字符串就是默认的输出格式了,看起来和以前写的log4j的格式化有点点像。 在弄明白在哪里输出日志,之前先搞明白这串"摩斯密码是啥."

226161af48907abcbe74f3f91833fc8b.png

口说无凭,验证下咱们的猜想:JUL SimpleFormatter打印日志的方式:

public static void main(String[] args) {

System.out.println(String.format("%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n",

new Date(),

"org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory createBean",

ContextTests.class.getName(),

Level.FINEST.getLocalizedName(),

"Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@54bedef2, started on Wed Sep 16 22:21:06 CST 2020",

""));

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

Service service = context.getBean(Service.class);

System.out.println(service);

}

结果显而易见(我只改了第二行输出内容相同,所以第二行看起来除了颜色以外一模一样)。 恍然大悟,原来JUL输出格式其实是使用String.format函数(这个和C语言有很多相似的函数)

9e826a3f47a7580b898b987d40ead7c2.png

小笔记--JUL SimpleFormatter 日志格式化字符串

JUL SimpleFormatter输出格式其实是使用了String.format函数,如果不知道用法,参考文档给你了: 点击conversion进去.

160469436ea262368d3fc0c20b41daf3.png

帮助文档中给出 %[argument_index$][flags][width][.precision]conversion这样一个通用的格式,每个位置都有具体的描述,IDEA中按F2即可查看类帮助文档。

好记性不如烂笔头,简单记几种情形:

Java代码

标准控制台输出

解释

String.format("%s %s", "Hello", "World")

Hello World

C语言的意思有点,%s代表后面的第一个参数(toString输出)

String.format("%2$s %1$s", "World", "Hello")

Hello World

1$代表后面变量的下标,2$代表后面变量下标为2,下标从1开始。变量下标我觉得用于输入参数重复使用的情况,或者顺序颠倒。

String.format("%1$tY %1$tB %1$td", new Date())

2020 九月 16

输出当前日期,1$主要用于重复使用第一个参数,%t代表时间,不能单独使用,需要跟conversion,Y年份,B月份名字,d天数,

String.format("%1$tD", new Date())

09/16/20

等价于%tm/%td/%ty

String.format("%tF",new Date())

2020-09-16

等价于%tY-%tm-%td

String.format("%tc",new Date())

星期三 九月 16 23:01:49 CST 2020

等价于Date and time formatted as "%ta %tb %td %tT %tZ %tY", e.g. "Sun Jul 20 16:17:00 EDT 1969".

String.format("%s%n%s","HEllo","World")

HEllo手动换行World

%n换行输出

String.format("%Tp",new Date())

下午

输出上午或是下午,不同区域不同的显示,比如new Formatter(Locale.US).format("%Tp",new Date())就会显示PM

结合上面,我们也不难理解控制台输出那样了

%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n

九月 16, 2020 11:03:53 下午 org.springframework.beans.factory.support.AbstractBeanFactory doGetBean

非常详细: Returning cached instance of singleton bean 'service'

打印自己风格的一种日志

想打印一些自己喜欢风格的日志,log4j logback都能实现各种各样的,我只是单纯写个JUL的风格日志,

import java.util.Date;

import java.util.logging.Formatter;

import java.util.logging.LogRecord;

public class MySimpleFormatter extends Formatter {

private static final Date dat = new Date();

@Override

public String format(LogRecord record) {

dat.setTime(record.getMillis());

String className = record.getSourceClassName();

String[] strings = className.split("\\.");

StringBuilder sb = new StringBuilder();

for (int i=0;i

if (i==strings.length-1){

sb.append(strings[i]);

}else{

sb.append( strings[i].charAt(0)).append(".");

};

}

sb.append(record.getSourceMethodName());

return String.format("%1$tF %1$tT [%2$s] %3$s: %4$s%n",

dat,

record.getLevel().getName(),

sb.toString(),

record.getMessage()

);

}

}

配置文件loggging.properties中使用自己定义的格式

handlers= java.util.logging.ConsoleHandler

.level= ALL

java.util.logging.ConsoleHandler.level = ALL

java.util.logging.ConsoleHandler.formatter =MySimpleFormatter

见证效果: 格式起码我还能接受, 但是红色又像是报错了,将配置文件里所有的java.util.logging.ConsoleHandler替换成自己的Handler, 比如我继承ConsoleHandler, 不过setOutputStream设置成System.out.

e2424b6f897a84edfc421e5827152077.png

就能达到这样的效果:

53ba29efbe293d5989391b5e99c2daf6.png

彩蛋.

平时常见到打印日志,都能输出某某类、某某方法,多少多少行. 打印日志时候如何知道我是在哪个类的哪个方法中获取到的呢?

获取当前StackTrace,以前只在异常e.getStackTrace()中用到过。stackTrace数组下标0-length-1,0代表当前方法对应的StackTraceElement,length-1处元素为方法最开始调用的地方,其中记录了调用的className、methodName、LineName。 JUL中判断 stackTrace数组中元素的className为JavaUtilLog,下一个不为JavaUtilLog的元素 就是调用日志的地方。

public class CallerTests {

public static void main(String[] args) {

System.out.println("1");

callA();

}

public static void callA(){

callB();

}

public static void callB(){

System.out.println("callB()...");

StackTraceElement[] stackTrace = new Throwable().getStackTrace();

// stackTrace堆栈下标从0-length, 0代表当前调用的方法,1代表父类方法(如果有的话)

if (stackTrace.length>=1){

StackTraceElement caller = stackTrace[1];

System.out.println(caller.getClassName()+"."+caller.getMethodName());

}

}

}

总结记于2020/09/18,学习了Spring源码调试时候如何输出日志、 String.format()使用、 获取当前调用方法的类以及方法名。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值