【日志技术-Java原生日志实现JUL】

时间:2022-05-12 00:38:09
  • 目录:
  • 一、日志的概念
  • 二、Java日志框架
  • 三、JUL日志框架
  • 四、JUL日志框架代码实现
    1、默认配置日志输出
    2、直接对应日志级别输出
    3、自定义编码形式日志输出
    4、Logger对象的父子关系
    5、加载自定义配置文件
  • 五、JUL日志配置文件
    1、JDK默认的日志配置文件
    2、自定义日志配置文件
    3、自定义配置文件的详细解读

tips:Ctrl+F快速定位到自己所需内容阅读吧。


一、日志的概念

日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志。

  • 1、日志文件简单分类

①调试日志
软件开发中,我们经常需要去调试程序,做一些信息,状态的输出便于我们查询程序的运行状况。日志主要是为了更方便的去重现问题。
②系统日志
系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。系统日志包括系统日志、应用程序日志和安全日志。


二、Java日志框架

  • 1、现有的日志框架

JUL(java util logging)、logback、log4j、log4j2
JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)
①日志门面
JCL、slf4j
②日志实现
JUL、logback、log4j、log4j2


三、JUL日志框架

  • 1、构成要素

①Loggers
应用程序访问日志系统的入口程序,用户使用Logger来进行日志记录;
②Appenders(Handlers)
每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录;
Handlers的具体实现决定了日志存放位置(如:FileHandler对应文件、ConsoleHandler对应控制台,等等);
③Layouts(Formatters)
负责对日志事件中的数据进行转换和格式化,它决定了数据在一条日志记录中的最终形式;
④Level
每条日志消息都有一个关联的日志级别,可以将Level和Loggers,Handlers做关联以便于我们过滤消息;
⑤Filters
过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。


四、JUL日志框架代码实现

  • 1、默认配置日志输出

①获取 Logger 对象

②日志记录输出 info(String msg)//直接输出 log(Level level, String msg)//设置日志级别输出 log(Level level, String msg, Object params[])//通过占位符 输出变量值


//1.获取日志记录器对象
//命名:通常使用当前类的全限定类名(包名+类名)
Logger logger = Logger.getLogger("com.stone.JULTest");
//2.日志记录输出
//2.1.直接输出日志
logger.info("hello jul");
//2.2.设置级别 输出日志
logger.log(Level.INFO, "level info msg");
//2.3.通过占位符方式 输出变量值
String msg = "hello world";
Integer num = 123;
logger.log(Level.INFO, "user message:{0}, {1}", new Object[]{msg, num});
  • 2、直接对应日志级别输出

public void severe(String msg) {
log(Level.SEVERE, msg);
}


//1.获取日志记录器对象
Logger logger = Logger.getLogger("com.stone.JULTest");
//2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");//默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
  • 3、自定义编码形式日志输出

①获取日志记录对象Logger

②用该对象关闭其系统默认配置

③创建Handler

④创建Formatter

⑤通过Handler设置Formatter

⑥通过Logger对象设置Handler(一个Logger对象可以设置多个Handler)

⑦设置Handler、Logger日志级别


//1.获取日志记录器对象
Logger logger = Logger.getLogger("com.stone.JULTest");

//1.1.关闭系统默认配置
logger.setUseParentHandlers(false);

//1.2.自定义配置日志级别(关联处理器&转换器)
//1.2.1.创建ConsoleHandler
ConsoleHandler consoleHandler = new ConsoleHandler();
//1.2.2.创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
//1.2.3.进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);

//1.3.设置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);

//场景FileHandler 文件输出
FileHandler fileHandler = new FileHandler("D:/logs/jul.log");
//进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);

//2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");//默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
  • 4、Logger对象的父子关系

①三个对象:RootLogger、loggerParent 、loggerChild
②loggerParent.setUseParentHandlers(false);//关闭使用系统默认配置
③loggerChild.severe("severe");//日志记录输出
注意:由于loggerChild并没有setUseParentHandlers(false),故而此处loggerChild会默认使用loggerParent的自定义配置。 父子关系:由命名的层级决定,如com是com.stone的父对象。
RootLogger名称:默认其name为""。


Logger loggerChild = Logger.getLogger("com.stone");
Logger loggerParent = Logger.getLogger("com");
//默认按照 命名目录层级关系 来设置父子关系
System.out.println(loggerParent == loggerChild.getParent());//true
//所有日志记录器的*父元素 LogManager$RootLogger,默认的name:""。
System.out.println("loggerParent's default parent: " + loggerParent.getParent() + ", name: "
+ loggerParent.getParent().getName());

//1.1.关闭系统默认配置
loggerParent.setUseParentHandlers(false);

//1.2.自定义配置日志级别(关联处理器&转换器)
//1.2.1.创建ConsoleHandler
ConsoleHandler consoleHandler = new ConsoleHandler();
//1.2.2.创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
//1.2.3.进行关联
consoleHandler.setFormatter(simpleFormatter);
loggerParent.addHandler(consoleHandler);

//1.3.设置日志具体级别
loggerParent.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);

//2.日志记录输出
//loggerChild会按照loggerParent设置的日志级别输出对应的日志
loggerChild.severe("severe");
loggerChild.warning("warning");
loggerChild.info("info");//默认日志输出级别
loggerChild.config("config");
loggerChild.fine("fine");
loggerChild.finer("finer");
loggerChild.finest("finest");
  • 5、加载自定义配置文件

①通过类加载器 读取配置文件
②创建LogManager对象
③通过LogManger加载配置文件
④创建日志输出类
⑤日志记录输出


//1.通过类加载器 读取配置文件
InputStream inputStream = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
//2.创建LogManager
LogManager logManager = LogManager.getLogManager();
//3.通过LogManger加载配置文件
logManager.readConfiguration(inputStream);

//创建日志记录器
Logger logger = Logger.getLogger("com.stone.JULTest");
//日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");//默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");

五、JUL日志配置文件

  • 1、JDK默认的日志配置文件

可以通过LogManager#readConfiguration()方法进行定位,可以在IDEA中将断电打在以下代码处:


// row:1298  
f = new File(f, "logging.properties");

然后我们运行代码,获取Logger对象的时候就能定位到JDK默认配置文件位置。Windows系统下默认目录地址为:C:\Program Files\Java\jdk1.8.0_311\jre\lib

【日志技术-Java原生日志实现JUL】

断点调试定位logging.properties文件.png

  • 2、自定义日志配置文件

核心思想其实跟上述编码的思想一致,将配置文件抽离出来可以做到一定程度的解耦,也提高了配置文件复用性。
关键步骤:
①配置handlers

②配置.level

③关联handlers与formatter

④指定日志消息格式

⑤自定义日志形式


# RootLogger 配置*父元素指定的默认处理器为: ConsoleHandler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler

# RootLogger *父元素默认的日志级别为:ALL
.level= ALL

# 向日志文件输出的 handler 对象
# 指定日志文件输出路径:D:/logs/java%u.log
java.util.logging.FileHandler.pattern = D:/logs/java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象 默认为:XMLFormatter,此处修改为:SimpleFormatter
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加的方式添加日志内容
java.util.logging.FileHandler.append = true

# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定 handler 对象的日志消息格式对象 SimpleFormatter
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8

# 指定日志消息格式
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%n

# 此处设置会使以 com.stone. 开头命名的 Logger 对象采用此配置
# 自定义 Logger 使用
# For example, set the com.stone logger to use ConsoleHandler as handlers
com.stone.handlers = java.util.logging.ConsoleHandler
# For example, set the com.stone logger to only log CONFIG
com.stone.level = CONFIG
# 关闭默认配置
com.stone.useParentHandlers = false
  • 3、自定义配置文件的详细解读

①、配置RootLogger的handlers
Logger内部使用CopyOnWriteArrayList集合存储handlers,具体源码如下:


private final CopyOnWriteArrayList<Handler> handlers =
new CopyOnWriteArrayList<>();

所以我们可以给Logger对象设置多个handlers,只需用","分隔开即可,如下:


# RootLogger 配置*父元素指定的默认处理器为: ConsoleHandler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler

②、配置RootLogger的日志级别level
RootLogger对象的默认名称为"",具体源码如下:


private RootLogger() {
// We do not call the protected Logger two args constructor here,
// to avoid calling LogManager.getLogManager() from within the
// RootLogger constructor.
super("", null, null, LogManager.this, true);
}

Logger(String name, String resourceBundleName, Class<?> caller, LogManager manager, boolean isSystemLogger) {
this.manager = manager;
this.isSystemLogger = isSystemLogger;
setupResourceInfo(resourceBundleName, caller);
this.name = name;
levelValue = Level.INFO.intValue();
}

另外需要注意的是,此处需要使用ALL大写字母形式,因为Level类的具体实现是如此,源码如下:


/**
* ALL indicates that all messages should be logged.
* This level is initialized to <CODE>Integer.MIN_VALUE</CODE>.
*/
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);

所以我们给RootLogger对象的level属性设置日志级别时使用如下配置:


# RootLogger *父元素默认的日志级别为:ALL
.level= ALL

③、配置FileHandler相关属性
pattern 输出路径

limit 内容大小

count 文件数量

formatter 编辑器

append 内容追加


# 向日志文件输出的 handler 对象
# 指定日志文件输出路径:D:/logs/java%u.log
java.util.logging.FileHandler.pattern = D:/logs/java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象 默认为:XMLFormatter,此处修改为:SimpleFormatter
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加的方式添加日志内容
java.util.logging.FileHandler.append = true

④、配置ConsoleHandler相关属性
level 日志级别
formatter 编辑器
encoding 字符集


# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定 handler 对象的日志消息格式对象 SimpleFormatter
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8

⑤指定日志消息格式
配置SimpleFormatter的format属性,以下为JDK官方样例:


Some example formats: 
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
This prints 1 line with the log level (4$), the log message (5$) and the timestamp (1$) in a square bracket.
WARNING: warning message [Tue Mar 22 13:11:31 PDT 2011]

java.util.logging.SimpleFormatter.format="%1$tc %2$s%n%4$s: %5$s%6$s%n"
This prints 2 lines where the first line includes the timestamp (1$) and the source (2$); the second line includes the log level (4$) and the log message (5$) followed with the throwable and its backtrace (6$), if any:
Tue Mar 22 13:11:31 PDT 2011 MyClass fatal
SEVERE: several message with an exception
java.lang.IllegalArgumentException: invalid argument
at MyClass.mash(MyClass.java:9)
at MyClass.crunch(MyClass.java:6)
at MyClass.main(MyClass.java:3)

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%n"
This prints 2 lines similar to the example above with a different date/time formatting and does not print the throwable and its backtrace:
Mar 22, 2011 1:11:31 PM MyClass fatal
SEVERE: several message with an exception

我们可以自定义相关内容,格式就像:String.format(format, date, source, logger, level, message, thrown);
或者我们也可以选择官方样例其一做配置,如下:


# 指定日志消息格式
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%n

⑥、对指定Logger对象做配置
使用Logger的名称,以及其属性来具体配置其内容即可,如下:


# 此处设置会使以 com.stone. 开头命名的 Logger 对象采用此配置
# 自定义 Logger 使用
# For example, set the com.stone logger to use ConsoleHandler as handlers
com.stone.handlers = java.util.logging.ConsoleHandler
# For example, set the com.stone logger to only log CONFIG
com.stone.level = CONFIG
# 关闭默认配置
com.stone.useParentHandlers = false

关键的步骤是com.stone.useParentHandlers = false,否则会失效。


六、结尾

以上即为JUL日志技术的基础内容