JDK从1.4开始提供Logging实现,据说当初JDK打算采用Log4J的,后来因为某些原因谈判没谈拢,然后就自己开发了一套,不知道是为了报复而故意不沿用Log4J的命名方式和抽象方式,还是开发这个模块的人水平不够,或没用心,亦或是我用Commons
Logging和Log4J习惯了,看JDK的Logging实现怎么看怎么不爽~~~吐个槽额~~~~

JDK Logging将日志打印抽象成以下几个类之间的交互:

1.    Level,定义日志的级别,类似Log4J中的Level类。

JDK Logging采用了完全不同于Log4J中对级别的抽象。在JDK
Logging中,默认定义了以下几个级别:SEVERE(对应Log4J中的ERROR或FATAL)、WARNING(对应Log4J中的WARN)、INFO(对应Log4J中的INFO)、CONFIG(对应Log4J中的DEBUG)、FINE(对应Log4J中的TRACE)、FINER(对应Log4J中的TRACE)、FINEST(对应Log4J中的TRACE)。另外,类似Log4J,JDK
Logging也定义了两个特殊的级别:ALL和OFF,分别对应打印所有级别的日志和关闭日志打印。

Level中包含三个字段:name、value、resourceBundleName。其中name指定级别名称,value指定该级别对应的一个int值,其值从SEVERE开始依次递减,resourceBundleName定义本地化后的级别名称,默认是sun.util.logging.resources.logging,即我们在日志中看到警告、信息等级别字段就是通过调用Level的getLocalizedName()方法,读取resourceBundleName对应的resource值来获得的,这也是Log4J中没有听过的。

Level还定义了一个parse()方法,它可以支持解析name字符串、代表级别的int值(以字符串的形式)、以及对应的Localized名称,如果所有的都不满足需求,则抛出IllegalArgumentException。

最后,Level还实现了readResolve()方法,从而确保反序列化后的Level只是Level类中定义的几个实例(出了自定义的Level实例)。

2.    LogRecord,封装了打印一条一直所包含的所有数据,类似Log4J中的LoggingEvent。

它包含了以下信息:

a.    日志级别(level)

b.    全局标识号(sequenceNumber,即没创建一个LogRecord,sequenceNumber都会自增1)

c.    打印这条日志语句所在的类的名称(sourceClassName),可以通过调用inferCaller()方法解析出来。解析实现则是通过实例化一个Throwable实例,通过解析该实例的Call
Stack即可得出打印这条日志所在的类的名称和方法名称,这里貌似不支持行号、文件名等信息。并且通过设置needToInferCaller字段(不可序列化)来判断sourceClassName和sourceMethodName是否已经取得,从而不用每次调用的时候都去解析而提升性能。

d.    打印这条日志语句所在调用方法的名称(sourceMethodName),它也是通过调用inferCaller()方法解析出来。

e.    打印消息(message)

f.     线程号(threadID),对LogRecord本身而言,从0开始对每个线程自增1

g.    创建LogRecord的时间(millis)

h.    打印日志中的异常(thrown)

i.      日志名称(loggerName)

j.     资源名称(resourceBundleName)

k.    参数列表(parameters数组),在Formatter中通过MessageFormat使用这些参数列表,并在序列化是手动调用其toString()方法序列化,而不是采用默认的序列化方式。

l.      ResourceBundle实例,获取本地消息,不可序列化。

3.    Formatter,根据配置格式化一条日志记录,类似Log4J中的Layout。

JDK Logging支持两种类型的Formatter:SimpleFormatter和XMLFormatter,默认采用SimpleFormatter,它先打印日期和时间、LoggerName或source
ClassName、方法名称,然后换行,在打印日志级别、本地化后的消息,然后换行,打印异常信息。而XMLFormatter实现getHead()、getTail()方法,并且将每条记录写成一条<record></record>记录。吐槽一下,它的实现是在是太不专业了~~。

4.    Handler,实现将日志写入指定目的地,如ConsoleHandler、FileHandler、SocketHandler即对应将日志写入控制台、文件、Socket端口。它类似Log4J中的Appender。

Handler包含对Formatter、Level、Filter的引用,其中Formatter将LogRecord格式化成String字符串;Level定义当前Handler支持的日志级别;而Filter则提供一个用户自定义的过滤一些日志的扩展点。

public interface Filter
{

public boolean isLoggable(LogRecord
record);

}

用户可以定义自己的Filter类以实现用户自定义的过滤逻辑。Handler还定义了encoding属性,以配置底层日志的输出格式。

Handler中最终要的方法是publish(),它实现了真正打印日志消息的逻辑。

public abstract void close() throws SecurityException;

默认JDK Logging实现了StreamHandler,而StreamHandler有三个子类:ConsoleHandler、FileHandler、SocketHandler。StreamHandler支持的配置有:

java.util.logging.StreamHandler.level 设置当前Handler支持的级别,默认为FINE

java.util.logging.StreamHandler.filter 设置当前Handler的Filter,默认为null

java.util.logging.StreamHandler.formatter 设置当前Handler的Formatter类,默认为SimpleFormatter

java.util.logging.StreamHandler.encoding 设置当前Handler的编码方式,默认为null

ConsoleHandler只是将OutputStream设置为System.err,其他实现和StreamHandler类似。

而SocketHandler将OutputStream绑定到对应的端口号中,其他也和StreamHandler类似。另外它还增加了两个配置:java.util.logging.SocketHandler.port和java.util.logging.SocketHandler.host分别对应端口号和主机。

FileHandler支持指定文件名模板(java.util.logging.FileHandler.pattern),文件最大支持大小(java.util.logging.FileHandler.limit,字节为单位,0为没有限制),循环日志文件数(java.util.logging.FileHandler.count)、对已存在的日志文件是否往后添加(java.util.logging.FileHandler.append)。

FileHandler支持的文件模板参数有:

/     目录分隔符

%t   系统临时目录

%h 系统当前用户目录

%g 生成的以区别循环日志文件名

%u 一个唯一的数字以处理冲突问题

%% 一个%

5.    LogManager类,读取配置文件和管理Logger实例,类似Log4J的LogRepository。

在LogManager类初始化时,用户可以通过指定java.util.logging.manager系统属性以自定义LogManager,默认使用LogManager类本身。初始化完成后创建RootLogger,并将新创建的RootLogger实例加入到LogManager中。LogManager中将所有创建的Logger缓存在loggers字段中(Hashtable,name作为key,WeakReference<Logger>作为value)。RootLogger的name为空,因而所有对RootLogger的配置都从”.”开始。

对Logger的配置支持一下几种方式:

<name>.level=INFO|CONFIG….

handers=<handlerName1>,<handlerName2>…. (以”,”分隔或以空格、TAB等字符分隔,全局Handler)

config=<configClassName1>,<configClassName2>….(自定义类,实现在其构造函数中实现自定义配置)

<name>.userParentHandlers=true|false|1|0

<name>.handlers=<handlerName1>,<handlerName2>…(以”,”分隔或以空格、TAB等字符分隔)

<handlerName>.level=INFO|CONFIG….

以及各自Handler本身支持的配置,具体各自的Handler。

用户可以通过以下方式自定义配置文件:

a.    设置系统属性java.util.logging.config.class,由自定义类的构造函数实现自定义配置,如调用LogManager、Logger中的一些静态方法。

b.    设置系统属性java.util.logging.config.file,自定义配置文件路径。读取该文件中的内容作为配置信息。

c.    默认使用${java.home}/lib/logging.properties文件作为配置文件(JDK已经提供了一些默认配置,一般是${JRE_HOME}/lib/logging.properties文件)

类似Log4J,LogManager也将Logger构建成树状结构,并且对那些暂时没有真正Logger实例的节点,使用LogNode,同样构建成树,这个实现也类似Log4J的ProvisionNode。

6.    Logger类,用户打印log接口,类似Log4J中的Logger。

Logger包含name、Handlers、resourceBundleName、useParentHandlers、filter、anonymous、levelObject、parent、kids等字段。其中其他字段都比较容易理解,anonymous比较难理解,按注释,它是用来表达当前Logger是一个匿名Logger,即不会被加入到LogManager中,因而也不需要安全检查,匿名Logger一般在Applet中使用,对Applet不了解,因而也无法做更详细的解释。在构建树时,该Logger同时保持了父节点和子节点的所有引用,对JDK这个Logging的实现一直无力吐槽。

Logger提供getLogger()接口,这个也是一般用户直接打交道的接口,传入name,返回缓存的Logger实例或新创建一个Logger实例。

对匿名Logger,Logger提供getAnonymousLogger()接口。log(LogRecord)方法是对打印日志的真正实现:

public void log(LogRecord
record) {

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

return;

}

synchronized (this)
{

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

return;

}

}

Logger logger = this;

while (logger != null)
{

Handler targets[] = logger.getHandlers();

if (targets != null)
{

for (int i
= 0; i < targets.length; i++) {

targets[i].publish(record);

}

}

if (!logger.getUseParentHandlers())
{

break;

}

logger = logger.getParent();

}

}

其他log方法只是对该方法中LogRecord中不同字段参数的组合。其他logp()系类方法只是提供给用户自定log所在的方法名和类名;而entering、existing、thrown等几个方法只是几个傻逼的命名而已。

最后给张JDK Logging的类图吧:

深入源码之JDK Logging的更多相关文章

  1. 【JDK】Linux安装源码包JDK完整步骤

    [JDK]Linux安装源码包JDK完整步骤 1.检查一下系统中的jdk版本 [root@localhost software]# java -version 显示: openjdk version ...

  2. ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  3. ASP.NET Core 源码学习之 Logging[2]:Configure

    在上一章中,我们对 ASP.NET Logging 系统做了一个整体的介绍,而在本章中则开始从最基本的配置开始,逐步深入到源码当中去. 默认配置 在 ASP.NET Core 2.0 中,对默认配置做 ...

  4. ASP.NET Core 源码学习之 Logging[3]:Logger

    上一章,我们介绍了日志的配置,在熟悉了配置之后,自然是要了解一下在应用程序中如何使用,而本章则从最基本的使用开始,逐步去了解去源码. LoggerFactory 我们可以在构造函数中注入 ILogge ...

  5. 从Mybatis源码理解jdk动态代理默认调用invoke方法

    一.背景最近在工作之余,把开mybatis的源码看了下,决定自己手写个简单版的.实现核心的功能即可.写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题. ...

  6. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  7. 【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构

    本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...

  8. 【ASP.NET Core 】ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  9. JAVA源码之JDK(二)——Integer、Long、Double

    这篇文章继续java.lang包下的源码学习,笔者也是找了几个比较常用的来阅读.下面针对Integer.Long.Double这样的基本类型的封装类,记录一些比较经典.常用的方法的学习心得,如toSt ...

  10. JAVA源码之JDK(三)——String、StringBuffer、StrinBuilder

    Java中,除了8种基本类型,最长用的应该就是String类了.那么我们来看看JDK中的源码是怎么建造String.StringBuffer.StrinBuilder一系列类的. java.lang. ...

随机推荐

  1. JVM笔记九-GC收集器日志信息学习

    在上一篇文章中,我们通过代码运行结果,查看到JVM的堆内存逻辑上分区是三部分,物理上分区是2部分,以及是新生代分区三部分,占比分布是8/1/1.而且我们还通过代码和堆JVM参数配置,制造出了OOM异常 ...

  2. C++ const常量指针

    const常量指针 const是C++关键字,译为常量,const指针即为常量指针. 分为三类 指向const的指针 const指针 指向const的const指针 指向const的指针 表示指向区域 ...

  3. QT6 Widgets深入剖析

    QT6 Widgets深入剖析 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C++扩展开发视频课程 免费QT视频课程 您可以看免费100 ...

  4. [Tkey] [IOI 2018] werewolf

    注意看,我耗时五个小时 AK 了 IOI 题意 给你一个图,每次给定若干询问 \((s,t,l,r)\),请你完成下述要求: 定义 \(S\) 为到 \(s\) 的最短路径不小于 \(l\) 的点构成 ...

  5. Linux安装miniforge

    miniforge 安装 协议条款:bsd-3-clause 支持conda和mamba命令,两者效果一样,推荐使用mamba # Setup Miniforge wget "https:/ ...

  6. 使用SourceTree管理仓库代码

    1.首先我们需要下载sourcetree,你可以去官网下载自己需要的版本. 2.安装完毕之后,我们需要获取ssh密钥与github关联上才能使用 按下面的操作开始执行.确定之后,会出现一个字符界面,输 ...

  7. 调查报告解读之国外数据库篇:MySQL国内使用率第一,多少企业有意替换国外产品?

    为了解数据库行业以及从业人员的现状.数据库选型.中国数据库的发展趋势等,墨天轮于2022年开始进行问卷收集,历时24天,共征集到有效问卷3476份,并于2月10日整理发布了<2022年墨天轮数据 ...

  8. vue前端使用nexus配置npm私有仓库

    当我们运行前端项目的时候,常常在解决依赖的时候会加上一个参数npm install --registry=https://registry.npm.taobao.org将源指定为淘宝的源,以期让速度加 ...

  9. 1. C#面试题 - Webservice和WebApi的区别

    1. Webservice : 基于SOAP协议的,数据格式时XML,只支持http协议,不是开源的,只能部署在IIS上 2. Webapi 开源的,.net 平台

  10. 从围绕API到围绕数据-使用流式编程构建更简洁的架构

    背景 在服务刚刚搭建时,通常的思维就是根据API编写业务逻辑: // SendStream ... func (d *Svc) SendStream(stream MyApi_data.ProxyDi ...