初记于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框架的源码(但是这格式、这文字也太丑了吧)
分析为什么打出来了日志
代码相较于之前多了如下几行,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就是复制这个文件的)。
今晚学习到使用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就可以了。
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文件。 偶然之间单步调试进入到这个类发现这些方法的。
分析为什么打印出来这种日志?
再回头看我们的logging.properties感觉也没有定义输出格式,就打印出来这种样式。 日志拖到2行展示,中文月份、日志级别夹杂着时间、类名、方法名,像极了自己的英语口语,My English 666。
日志打印格式在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的格式化有点点像。 在弄明白在哪里输出日志,之前先搞明白这串"摩斯密码是啥."
口说无凭,验证下咱们的猜想: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语言有很多相似的函数)
小笔记--JUL SimpleFormatter 日志格式化字符串
JUL SimpleFormatter输出格式其实是使用了String.format函数,如果不知道用法,参考文档给你了: 点击conversion进去.
帮助文档中给出 %[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.
就能达到这样的效果:
彩蛋.
平时常见到打印日志,都能输出某某类、某某方法,多少多少行. 打印日志时候如何知道我是在哪个类的哪个方法中获取到的呢?
获取当前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()使用、 获取当前调用方法的类以及方法名。