本文网大多网络整理所得,出处太多,不一一列举

简介

Java 中的 Logging API 让 Java 应用可以记录不同级别的信息,它在debug过程中非常有用,如果系统因为各种各样的原因而崩溃,崩溃原因可以在日志中清晰地追溯,下面让我们来看看 Java 原生的 Logging 功能。

从1.4.2开始,Java 通过 Java.util.logging 包为应用程序提供了记录消息的可能,在 API 中的核心类为 Logger 类。理解在记录消息中的日志的不同级别是非常重要的。Java 为此定时了8个级别,它们是分别SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST 以及 ALL. 它们按照优先级降序排列,在应用运行的任何时间点,日志级别可以被更改。

通常来说,当为 Logger 指定了一个 Level, 该 Logger 会包含当前指定级别以及更高级别的日志。举例而言,如果 Level 被设置成了 WARNING, 所有的 warning 消息以及 SERVER 消息会被记录。应用可以用下列方法记录日志:Logger.warning(), Logger.info(), Logger.config() ...

工作原理和日志处理流程

几个重要类的说明

Logger 对外发布的日志记录器,应用系统可以通过该对象完成日志记录的功能

Level 日志的记录级别

LoggingMXBean 接口对象,对外发布的日志管理器

LogRecord 日志信息描述对象

LoggerManager 日志管理器

Filter 日志过滤器,接口对象,在日志被 Handler 处理之前,起过滤作用

Handler 日志处理器,接口对象,决定日志的输出方式

Formatter 日志格式化转换器,接口对象,决定日志的输出格式

工作原理

首先通过LoggerManager进行日志框架的初始化,生成Logger的根节点RootLogger. 这里需要注意的是LoggerManager的初始化工作,并没有将构建配置文件中所有的日志对象,而仅仅是构建了根节点,这种方式就是我们多例模式中经常用到的懒加载,对象只有在真正被时候的时候,再进行构建。

通过Logger.getLogger(String name) 获取一个已有的Logger对象或者是新建一个Logger对象。Logger,日志记录器,这就是在应用程序中需要调用的对象了,通过Logger对象的一系列log方法,

Logger的大致处理流程

收到应用程序的记录请求,将参数中的日志信息和运行时的信息构建出LogRecord对象,而后通过Logger对象本身设置的记录级别和调用者传递进来的日志级别,如果传递进来的日志级别低于Logger对象本身设置的记录级别(从语义上的理解,而实际上语义级别越高的级别其内部用数字表示的标志的数值越小),那么Logger对象将直接返回,因为他认为这条日志信息,在当前运行环境中,没有必要记录。

而满足以上条件的日志信息,将会通过Logger对象的filter元素的过滤校验,filter是动态的,在运行时是可以随意设置的,如果有filter对象,那么将调用filter对象,对日志对象LogRecord进行校验,只有校验通过的LogRecord对象,才会继续往下执行。

通过filter校验后,Logger对象将依次调用其配置的处理器,通过处理器来真正实现日志的记录功能,一个Logger对象可以配置多个处理器handler,所以一条日志记录可以被多个处理器处理,同时Logger对象的实现是树形结构,如果Logger对象设置其可以继承其父节点的处理器(默认),一条日志记录还会被其父节点的Logger对象处理。  而handler的处理方式就会是形形色色了,但是归根节点,会有以下几个大的步骤:

1. 级别的判定和比较,决定某条具体的日志记录是否应该继续处理

2. 将日志记录做格式化处理,以达到输出的日志在格式上统一,美观,可读性高。 3. 资源的释放,不管是以何种方式记录日志,总是会消耗一些方面的资源,所以

会涉及到资源的释放问题。比如以文件方式记录的日志的,在一定的时候需要做文件关闭操作,以报文方式发送日志的,在和远程通话的过程中,也需要涉及到网络IO的关闭操作,或者是存储在数据库等等,资源释放在程序开发过程中,是个不变的主题。

从一个示例讲起

public class TestLogger {

public static void main(String[] args) {

Logger log = Logger.getLogger("lavasoft");

log.info("aaa");

}

}

console output:

>>> aaa

以上简单的代码背后发生那些事

LoggerManager 将会返回一个新的或者已经存在的同名的 Logger , 首先会查找是否有同名 Logger 被 namedLoggers 维护有则返回, 但是在我们这个示例中大多是重新生成一个 Logger,首先 LoggerManager 会读取系统配置,设定一个默认的的 INFO 级别的 Logger, 然后也许跟其他线程抢到一个 Logger 后返回

tips:

默认的Java日志框架将其配置存储到一个名为 logging.properties 的文件中。

在这个文件中,每行是一个配置项,配置项使用点标记(dot notation)的形式。

Java在其安装目录的lib文件夹下面安装了一个全局配置文件,但在启动一个Java程序时,

你可以通过指定 java.util.logging.config.file 属性的方式来使用一个单独的日志配置文件,

同样也可以在个人项目中创建和存储 logging.properties 文件。

Logger 中召唤 LoggerManager 片段

---------------------------

public static Logger getLogger(String name) {

LogManager manager = LogManager.getLogManager();

return manager.demandLogger(name);

}

LoggerManager 中 产生 Logger 的片段

-----------------------------

Logger demandLogger(String name) {

Logger result = getLogger(name);

if (result == null) {

Logger newLogger = new Logger(name, null);

do {

if (addLogger(newLogger)) {

return newLogger;

}

result = getLogger(name);

} while (result == null);

}

return result;

}

LoggerManager 中维护了一个有继承关系的含有弱引用的 LoggerWeakRef

-------------------------------

private Hashtable namedLoggers = new Hashtable<>();

LoggerWeakRef 类结构

-----------------

final class LoggerWeakRef extends WeakReference {

private String name; // for namedLoggers cleanup

private LogNode node; // for loggerRef cleanup

private WeakReference parentRef; // for kids cleanup

以上两者维护了JVM中弱引用的 Loggers 父子结构

log.info()

Logger 中的 info(String msg) 方法

-----------------------------

public void info(String msg) {

if (Level.INFO.intValue() < levelValue) {

return;

}

log(Level.INFO, msg);

}

上面说过默认 LoggerManager 产生的 Logger 日志级别默认为 INFO ,所以这里默认的

levelValue 为 Level.INFO.intValue()

如果这里 Level.INFO.intValue() 低于 levelValue 的 , 将 do nothing

调用 log(Level level, String msg) 方法

----------------------------------

public void log(Level level, String msg) {

if (level.intValue() < levelValue || levelValue == offValue) {

return;

}

LogRecord lr = new LogRecord(level, msg);

doLog(lr);

}

上面的 log.info 方法只是 log(Level level, String msg) 方法简单封装,在这里日志级别

为 Level.OFF.intValue() 也 do nothing 了,否则创建真正的 LogRecord 对象

调用 doLog(LogRecord lr) 方法

-------------------------

private void doLog(LogRecord lr) {

lr.setLoggerName(name);

String ebname = getEffectiveResourceBundleName();

if (ebname != null) {

lr.setResourceBundleName(ebname);

lr.setResourceBundle(findResourceBundle(ebname));

}

log(lr);

}

getEffectiveResourceBundleName() 将一直上溯查找有效的 resourceBundleName , 有可能返回 null

调用 log(LogRecord lr) 方法

-----------------------

public void log(LogRecord record) {

if (record.getLevel().intValue() < levelValue || levelValue == offValue) {

return;

}

Filter theFilter = filter;

if (theFilter != null && !theFilter.isLoggable(record)) {

return;

}

// Post the LogRecord to all our Handlers, and then to

// our parents' handlers, all the way up the tree.

Logger logger = this;

while (logger != null) {

for (Handler handler : logger.getHandlers()) {

handler.publish(record);

}

if (!logger.getUseParentHandlers()) {

break;

}

logger = logger.getParent();

}

}

在这里我们可以看到了 Filter 与 Handler 的出现,我们可以使用 setFilter(Filter newFilter)

与 addHandler(Handler handler) 来为 Logger 添加 Filter 与 Handler

这里我们可以看出在 while 循环中会先对当前所有 handler 输出,在上溯所有父 Logger 所有 Handler

输出,至此两句代码解析结束。

话说 Filter

作为一个接口, Filter:为所记录的日志提供日志级别控制以外的细粒度控制。

public interface Filter {

/**

* Check if a given log record should be published.

* @param record a LogRecord

* @return true if the log record should be published.

*/

public boolean isLoggable(LogRecord record);

}

我们可以实现一个 Filter 接口的的对象来使用,下面是示例代码

public class MyFilter implements Filter {

public boolean isLoggable(LogRecord record) {

// TODO: 在这里我们可以添加自己的一些逻辑进去

return false;

// 返回 false 则不被记录日志, true 则被记录日志

}

}

然后我们为 Logger 对象设定 Filter 对象

Filter filter = new MyFilter();

logger1.setFilter(filter);

或者我们也可以为 Handler 对象设定 Filter 对象

Filter filter = new MyFilter();

ConsoleHandler consoleHandler = new ConsoleHandler();

consoleHandler.setLevel(Level.ALL);

consoleHandler.setFilter(filter);

话说 Handler

先上一张 java.util.logging 包中有关 Handler 的类图

Handler负责从Logger中取出日志消息并将消息发送出去,比如发送到控制台、文件、网络上的其他日志服务或操作系统日志等。

Handler也具有级别概念,用于判断当前Logger中的消息是否应该被发送出去,可以使用定义好的各种日志级别(如Level.OFF表示关闭等)。

除了级别概念,一个Handler还可以具有自己的过滤器(Filter)、格式化器(Formatter)、错误管理器(ErrorManager)以及编码字符集等,这些属性借助LogManager中的配置信息进行设置。

Handler是一个抽象类,需要根据实际情况创建真正使用的具体Handler(如ConsoleHandler、FileHandler等),实现各自的publish、flush以及close等方法。

对几种具体实现 Handler 类的类做简单说明

MemoryHandler,将当前日志信息写入内存缓冲区中同时丢弃缓存中以前的内容。将内存缓冲区中的信息转发至另一个Handler

StreamHandler所有基于I/O流的Handler的基类,将日志信息发送至给定的java.io.OutputStream中

ConsoleHandler,将消息发送至System.err(而非System.out),默认配置与其父类StreamHandler相同。

FileHandler,将消息发送至单个一般文件或一个可回滚的文件集合。可回滚文件集中的文件依据文件大小进行回滚,久文件名称通过当前文件名附加编号0、1、2等方式依次进行标示。默认情况下日志信息都存放在I/O缓冲中,但如果一条完整的日志信息会触发清空缓冲的动作。与其父类StramHandler不同的是,FileHandler的默认格式器是java.util.logging.XMLFormatter:

SocketHandler,负责将日志信息发送至网络,默认情况下也采用java.util.logging.XMLFormatter格式。

关于 MemoryHandler

MemoryHandler 使用了典型的“注册 - 通知”的观察者模式。MemoryHandler 先注册到对自己感兴趣的 Logger 中(logger.addHandler(handler)),在这些 Logger 调用发布日志的 API:log()、logp()、logrb() 等,遍历这些 Logger 下绑定的所有 Handlers 时,通知触发自身 publish(LogRecord)方法的调用,将日志写入 buffer,当转储到下一个日志发布平台的条件成立,转储日志并清空 buffer。

这里的 buffer 是 MemoryHandler 自身维护一个可自定义大小的循环缓冲队列,来保存所有运行时触发的 Exception 日志条目。同时在构造函数中要求指定一个 Target Handler,用于承接输出;在满足特定 flush buffer 的条件下,如日志条目等级高于 MemoryHandler 设定的 push level 等级(实例中定义为 SEVERE)等,将日志移交至下一步输出平台。从而形成如下日志转储输出链:

MemoryHandler 使用方式

以上是记录产品 Exception 错误日志,以及如何转储的 MemoryHandler 处理的内部细节;接下来给出 MemoryHandler 的一些使用方式。

直接使用 java.util.logging 中的 MemoryHandler

// 在 buffer 中维护 5 条日志信息

// 仅记录 Level 大于等于 Warning 的日志条目并

// 刷新 buffer 中的日志条目到 fileHandler 中处理

int bufferSize = 5;

f = new FileHandler("testMemoryHandler.log");

m = new MemoryHandler(f, bufferSize, Level.WARNING);

myLogger = Logger.getLogger("com.ibm.test");

myLogger.addHandler(m);

myLogger.log(Level.WARNING, “this is a WARNING log”);

自定义(反射)

思考自定义 MyHandler 继承自 MemoryHandler 的场景,由于无法直接使用作为父类私有属性的 size、buffer 及 buffer 中的 cursor,如果在 MyHandler 中有获取和改变这些属性的需求,一个途径是使用反射。清单 5 展示了使用反射读取用户配置并设置私有属性。

int m_size;

String sizeString = manager.getProperty(loggerName + ".size");

if (null != sizeString) {

try {

m_size = Integer.parseInt(sizeString);

if (m_size <= 0) {

m_size = BUFFER_SIZE; // default 1000

}

// 通过 java 反射机制获取私有属性

Field f;

f = getClass().getSuperclass().getDeclaredField("size");

f.setAccessible(true);

f.setInt(this, m_size);

f = getClass().getSuperclass().getDeclaredField("buffer");

f.setAccessible(true);

f.set(this, new LogRecord[m_size]);

} catch (Exception e) {

}

}

自定义(重写)

直接使用反射方便快捷,适用于对父类私有属性无频繁访问的场景。思考这样一种场景,默认环形队列无法满足我们存储需求,此时不妨令自定义的 MyMemoryHandler 直接继承 Handler,直接对存储结构进行操作,可以通过清单 6 实现。

public class MyMemoryHandler extends Handler{

// 默认存储 LogRecord 的缓冲区容量

private static final int DEFAULT_SIZE = 1000;

// 设置缓冲区大小

private int size = DEFAULT_SIZE;

// 设置缓冲区

private LogRecord[] buffer;

// 参考 java.util.logging.MemoryHandler 实现其它部分

...

}

logging.properties 文件

默认的 logging.properties 存放在 jre/lib/logging.properties,截取有效的配置项

handlers= java.util.logging.ConsoleHandler

.level= INFO

java.util.logging.FileHandler.pattern = %h/java%u.log

java.util.logging.FileHandler.limit = 50000

java.util.logging.FileHandler.count = 1

java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

java.util.logging.ConsoleHandler.level = INFO

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

com.xyz.foo.level = SEVERE

java util logging_Java 日志系列篇一 原生 Java.util.logging相关推荐

  1. java commons logging_Java日志介绍(5)-commons-logging

    Apache Commons Logging(JCL) 提供了一个简单的日志抽象,允许开发人员使用特定的日志实现.JCL可以使用其他的日志实现,包括Log4J.Avalon LogKit(Avalon ...

  2. java重复造轮子系列篇-----时间date

    2019独角兽企业重金招聘Python工程师标准>>> 时间操作工具类 package org.jeecgframework.core.util;import java.beans. ...

  3. java重复造轮子系列篇------发送邮件sendEmail

    2019独角兽企业重金招聘Python工程师标准>>> 发邮件 package org.jeecgframework.core.util;import java.io.File; i ...

  4. 希尔排序的java算法_Java算法系列篇 【希尔排序】

    什么? 搞Java不会算法? 由于个人兴趣原因以及工作所需,最近了解Java算法的相关案例 及时分享 感兴趣的欢迎交流 希尔排序 描述: 基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直 ...

  5. android java框架_【阿里P8大牛教你Android入门之路(java篇)】——Java集合框架(系列篇1)...

    一.前言 本部分内容主要包含以下: Java集合 Java反射 Java注解 Java反射 Java IO 其他面试点 以上内容都是Java中的基础知识,对于Java的学习很有帮助.其中集合.反射.I ...

  6. Java源码解析系列(一) Java集合框架

    版权声明:本文为博主原创文章,欢迎大家转载! 但是转载请标明出处: https://blog.csdn.net/t000818/article/details/82785664 ,本文出自:[唐宏宇的 ...

  7. java去掉mongodb日志_如何禁用mongoDB java驱动程序日志记录?

    我试图禁用mongo- java-driver-3.0.0的日志输出. 我试图在我的应用程序开始之前设置它们,然后加载mongo驱动程序,但它没有帮助. // Enable MongoDB loggi ...

  8. java 事务处理 是不是aop思想_理解原生JAVA AOP思想

    一路走来,遇到好些后生来问我:AOP该如何理解?我一开始是丢个度娘给他们的.但是现在回头想想,培养新人不能这么草率,丢个度娘给他们,让他们花了大量的时间去阅读无用的文章外,一不小心还走火入魔了.所以现 ...

  9. java运行异常日志_使用log4j记录Java中的运行时异常

    小编典典 我不确定这是否是您要寻找的,但是有一个终止线程的异常处理程序.它是线程异常未明确捕获的任何异常的处理程序. 默认"未捕获的异常处理程序"只是调用printStackTra ...

最新文章

  1. 《预训练周刊》第15期:Bengio, Lecun, Hinton | 人工智能深度学习、用于图像分类的全局过滤网络...
  2. win2003 IIS6,能访问html页 但是不能访问aspx页解决办法汇总
  3. 给容器中注册组件 || @Scope -- @Lazy -- @Conditional({Condition}) -- @Import--使用Spring提供的 FactoryBean
  4. Struts2返回JSON对象的方法总结
  5. 日本惊现神操作!偷偷研究飞刀方程致使厕所爆炸......
  6. linux用户、用户组 增删改查专栏
  7. 炒股炒成亿万富翁? 胡润财富报告称人数还不少
  8. 蔚来发布首款自动驾驶车型ET7 补贴前起售价44.8万元
  9. pringMVC上传图片
  10. 更改MyEclipse的默认编码
  11. Spring框架之演示JDBC的模板类
  12. 解决MySQL报错[Err] 1093 - You can't specify target...
  13. MySQL 2021 个税计算公式,自定义函数
  14. emmc5.1, ufs2.0, ufs3.0
  15. 尚德机构2020年Q4财报:净收入5.85亿元,管理费用同比大幅下降近五成
  16. 人工智能学习笔记:基本遗传算法及其改进算法
  17. IntelliJ IDEA自定义菜单(Menus)、任务栏(toolbars)详细教程(即Customize Menus and Toolbars...)
  18. Fourier Neural Operator for Parametric Partial Differential Equations
  19. bp神经网络的主要功能,一文搞定bp神经网络
  20. IntelliJ IDEA 设置编码为utf-8编码(各种中文乱码问题解决)

热门文章

  1. new relic 官方简介
  2. 怎么一键备份服务器系统还原,电脑怎么一键还原
  3. 湮灭尽头的一点光---炮粒子
  4. [Python]--Anaconda Resources Collection
  5. fastreport文本字数太多换行_Flutter实战】文本组件及五大案例
  6. 全志h3linux移植教程,全志H3启动分析,移植主线UBOOT
  7. 【Paper】2022_基于无人驾驶地面车辆的多Agent系统仿真平台的设计及编队控制协议的研究
  8. 揭开雷达的面纱(科普) 发射机
  9. 4、以太网基础知识——ICMP协议详解
  10. C++引用(Reference)