本文网大多网络整理所得,出处太多,不一一列举java
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() ...node
Logger 对外发布的日志记录器,应用系统能够经过该对象完成日志记录的功能数据库
Level 日志的记录级别缓存
LoggingMXBean 接口对象,对外发布的日志管理器网络
LogRecord 日志信息描述对象框架
LoggerManager 日志管理器函数
Filter 日志过滤器,接口对象,在日志被 Handler 处理以前,起过滤做用this
Handler 日志处理器,接口对象,决定日志的输出方式编码
Formatter 日志格式化转换器,接口对象,决定日志的输出格式spa
首先经过LoggerManager进行日志框架的初始化,生成Logger的根节点RootLogger. 这里须要注意的是LoggerManager的初始化工做,并无将构建配置文件中全部的日志对象,而仅仅是构建了根节点,这种方式就是咱们多例模式中常常用到的懒加载,对象只有在真正被时候的时候,再进行构建。
经过Logger.getLogger(String name) 获取一个已有的Logger对象或者是新建一个Logger对象。Logger,日志记录器,这就是在应用程序中须要调用的对象了,经过Logger对象的一系列log方法,
收到应用程序的记录请求,将参数中的日志信息和运行时的信息构建出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<String,LoggerWeakRef> namedLoggers = new Hashtable<>(); LoggerWeakRef 类结构 ----------------- final class LoggerWeakRef extends WeakReference<Logger> { private String name; // for namedLoggers cleanup private LogNode node; // for loggerRef cleanup private WeakReference<Logger> 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:为所记录的日志提供日志级别控制之外的细粒度控制。
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);
先上一张 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、一、2等方式依次进行标示。默认状况下日志信息都存放在I/O缓冲中,但若是一条完整的日志信息会触发清空缓冲的动做。与其父类StramHandler不一样的是,FileHandler的默认格式器是java.util.logging.XMLFormatter:
SocketHandler,负责将日志信息发送至网络,默认状况下也采用java.util.logging.XMLFormatter格式。
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)等,将日志移交至下一步输出平台。从而造成以下日志转储输出链:
以上是记录产品 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 存放在 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