文章目录

  • 前言
  • 一、认识Log4j
    • 1.1、介绍Log4j
    • 1.2、第三方jar包
    • 1.3、日志等级(6个)
  • 二、Log4j的三大组件
    • Loggers
    • Appenders
    • Layouts
  • 三、入门Log4j
    • 3.1、系统初始化配置输出日志
    • 3.2、BasicConfigurator类源码分析*
  • 四、自定义配置文件
    • 4.1、LogManager、OptionConverter源码分析*
    • 4.2、PropertyConfigurator类源码分析*
    • 4.3、初次自定义配置文件(log4j.properties)
  • 五、Log4j内置的日志LogLog
    • 5.1、LogLog源码分析以及开启其debug模式*
  • 六、各种Layout案例
    • 6.1、PatternLayout(自定义格式示例)
  • 七、各种Appender示例
    • 7.1、FileAppender及其子类
      • FileAppender示例
      • DailyRollingFileAppender示例
      • RollingFileAppender示例
    • 7.2、JDBCAppender
      • 示例演示
      • 源码分析*
  • 八、自定义Logger
  • 总结
  • 参考资料

前言

本篇博客主要介绍第三方日志框架Log4j,其他日志框架内容可见日志专栏。

所有博客文件目录索引(包含日志框架系列学习):博客目录索引(持续更新)


一、认识Log4j

1.1、介绍Log4j

Log4j官网:https://logging.apache.org/log4j/2.x/

Log4j:Apache的开源项目,是一个功能强大的日志组件,提供方便的日志记录。在项目中使用Log4j可以控制日志信息输出到控制台、文件甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别可以更灵活的控制日志的输出过程,方便调试。


1.2、第三方jar包

若想要使用Log4j,那么就需要引入jar包,下面给出pom.xml的坐标:

<dependencies><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
</dependencies>

1.3、日志等级(6个)

org.apache.log4j.Level源码查看

Log4j中也有一个Level类来表示日志等级,我们来查看下该类:

public class Level extends Priority implements Serializable {final static public Level OFF = new Level(OFF_INT, "OFF", 0);final static public Level FATAL = new Level(FATAL_INT, "FATAL", 0);final static public Level ERROR = new Level(ERROR_INT, "ERROR", 3);final static public Level WARN  = new Level(WARN_INT, "WARN",  4);final static public Level INFO  = new Level(INFO_INT, "INFO",  6);final static public Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7);public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7);final static public Level ALL = new Level(ALL_INT, "ALL", 7);
}
  • 其中OFFALL不作为日志等级(可看做开关),所以有六个日志等级。

日志等级从高到低:在Log4j中有6个日志等级,JUL中是7个

  • fatal: 严重错误,一般会造成系统崩溃和终止运行。
  • error:错误信息,但不会影响系统运行。
  • warn:警告信息,可能会发生问题。
  • info:程序运行信息,数据库的连接、网络、IO操作等。
  • debug:调试信息,一般在开发阶段使用,记录程序的变量、参数等。
  • trace:追踪信息,记录程序的所有流程信息。

特殊的两个日志级别:

  • OFF,可用来关闭日志记录
  • ALL,启用所有消息的日志记录。

一般只使用四个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG


二、Log4j的三大组件

Loggers

Loggers(日志记录器):负责收集处理日志记录,获取logger实例可通过类名或者全限定名获取,并且对于名称具有继承机制(与JUL类似),例如name为xyz.changlu会继承name为xyz的logger。

  • 从log4j 1.2以来,Logger类取代了Category类,Logger类可以视作Category类的别名。

在Log4j中包含一个特殊的loggerrootlogger,它是所有logger的根,其他logger会直接或间接的继承该rootlogger,可使用Logger.getLogger()获取。

看一下调用BasicConfigurator.configure();配置方法后使用Logger.getLogger()获得实例的结构图:

  • 其中category我们现在可以看做logger。当获取到一个logger实例时,其包含一个父属性为rootlogger,该父属性日志等级为DEBUGappenderConsoleApender(用于输出到屏幕上)。

注意:图中应该是ConsoleApender


Appenders

Appender:用来指定日志输出到哪个地方(即输出目的地),不同的Appender类型有不同的输出地点。

Appender类型 作用
ConsoleAppender 将日志输出到控制台
FileAppender 将日志输出到文件中
DailyRollingFileAppender 将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender 把日志信息保存到数据库中

我们可以Log4j中的Appender相对于JUL可以输出到数据库、每天输出文件、指定文件尺寸改名。


Layouts

Layouts(布局器):用于控制日志输出内容的格式,通过使用不同的Layout类型来指定各种需要的格式。

常用的Layout如下:

格式器类型 作用
HTMLLayout 格式化日志输出为HTML表格形式
SimpleLayout 简单的日志输出格式化,打印的日志格式为(info - message)
PatternLayout 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

  • Log4j提供的其他Layout,包含XML格式以及日期格式。

专对于PatternLayout类中的pattern自定义格式说明

# 占位符相关含义
%p: 输出优先级,及 DEBUG、INFO 等
%m: 输出代码中指定的日志信息
%n: 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r: 输出自应用启动到输出该 log 信息耗费的毫秒数
%d: 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy-MM-dd HH:mm:ss:SSS}  => 年月日 时分秒毫秒%l: 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)# %l即可表示下方四个修饰符%c: 输出打印语句所属的类的全名%t: 输出产生该日志的线程全名%F: 输出日志消息产生时所在的文件名称%L: 输出代码中的行号
%%: 输出一个 "%" 字符# 可在例如%m之间加入修饰符来控制最小宽度、最大宽度和文本的对其方式
%5c: 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
%-5c: 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
%.5c: 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
%20.30c: category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

比较好的搭配如下

log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n


三、入门Log4j

3.1、系统初始化配置输出日志

我们来首先尝试输出六个等级日志:

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;public class LogTest {public static void main(String[] args) {//初始化系统配置,不需要配置文件BasicConfigurator.configure();//获取logger实例,注意这里的Logger是Log4j的Logger logger = Logger.getLogger(LogTest.class);//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}
  • 第4行:与之前jul有点相似,jul是默认加载logging.properties,这里则需要手动调用方法调用方法来进行配置rootlogger,否则报错,若是不使用该方法就进行日志输出会报错,如下图:

注意:当通过调用方法进行配置时,logger实例的父属性rootloggerappenderConsoleAppender(formatterSimpleFormatter),logger实例的日志等级为DEBUG。所以trace等级的日志并没有输出。


3.2、BasicConfigurator类源码分析*

  • 单独一个类,无实现接口以及继承

前面说到BasicConfigurator.configure();是进行配置rootlogger的,我们看一下源码:

//org.apache.log4j.BasicConfigurator
public class BasicConfigurator {//1、配置方法void configure() {//获取到LogManager的rootlogger(new RootLogger((Level) Level.DEBUG))Logger root = Logger.getRootLogger();//见2//添加了ConsoleAppender以及自定义格式(%r [%t] %p %c %x - %m%n)root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));}
}//org.apache.log4j.Logger
public class Logger extends Category {//2、获取Logger实例Logger getRootLogger() {//实际上调用的是LogManager的方法获取的return LogManager.getRootLogger();//见3}
}//org.apache.log4j.LogManager
public class LogManager {//3、实际上这里获得的是一个public static Logger getRootLogger() {//这里我通过看源码得知实际上获得的是一个new RootLogger((Level) Level.DEBUG)return getLoggerRepository().getRootLogger();}
}
  • 通过看源码我们总结得到获取的logger实例的父属性rootlogger其level=DEBUGAppender=ConsoleAppenderLayout=PatternLayout(pattern="%r [%t] %p %c %x - %m%n")

四、自定义配置文件

4.1、LogManager、OptionConverter源码分析*

之前在调用Logger.getLogger()获取到Logger实例前,实际上会对LoggerManager进行初始化,看下面源码:

//org.apache.log4j.Logger
public class Logger extends Category {//1、获取name为传入clazz的类名的实例Logger getLogger(Class clazz) {//此时会调用LogManager的静态方法取得logger实例(此时首先做的动作是对LogManager进行初始化操作,执行其中的静态模块)return LogManager.getLogger(clazz.getName());//见2}
}//org.apache.log4j.LogManager
public class LogManager {//静态模块//这两个配置文件是通过类加载器的getResource()来查找到的static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  //下面三个是去查找System.getProperty获取键值对的值static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";static private Object guard = null;static private RepositorySelector repositorySelector;static {//之前的getRootLogger()就是返回的该实例中的rootloggerHierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));repositorySelector = new DefaultRepositorySelector(h);String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);if(override == null || "false".equalsIgnoreCase(override)) {String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);URL url = null;if(configurationOptionStr == null) {    //这里是去找log4j.xml这个配置文件url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);if(url == null) {//重要:若是没有log4j.xml就去查看是否有log4j.properties这个配置文件//会去classpath目录下去查找log4j.properties(Maven打包后则为target/classes目录下查找)//对于Maven项目我们将log4j.properties放置在resource目录下url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);}} else {try {url = new URL(configurationOptionStr);} catch (MalformedURLException ex) {url = Loader.getResource(configurationOptionStr); }  }//若是找到log4j.xml或者log4j.properties的配置文件时if(url != null) {//该方法默认是关闭打印提示信息的(因为LogLog的debugEnabled默认为false)LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");try {//进行读取配置方法(重要)OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());//见3} catch (NoClassDefFoundError e) {LogLog.warn("Error during default initialization", e);}} else {LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");}} else {LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); }  } //2、这里如何获取实例先不深究(主要看前面的静态模块初始化)public static Logger getLogger(final String name) {return getLoggerRepository().getLogger(name);}
}//org.apache.log4j.helpers.OptionConverter  (一个log4j的工具类)
public class OptionConverter {//3、开始进行读取xml或properteies的配置文件static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {Configurator configurator = null;//获取配置文件的名称String filename = url.getFile();//这里是判断读取的配置文件是否为log4j.xml文件if(clazz == null && filename != null && filename.endsWith(".xml")) {//若是的话则会提供一个DOMConfigurator工具类(应该是用于解析XML的)clazz = "org.apache.log4j.xml.DOMConfigurator";}//若是配置文件log4j.xml的话if(clazz != null) {LogLog.debug("Preferred configurator class: " + clazz);//使用反射技术获取到DOMConfigurator实例(用于解析xml工具类的)configurator = (Configurator) instantiateByClassName(clazz,Configurator.class,null);if(configurator == null) {LogLog.error("Could not instantiate configurator ["+clazz+"].");return;}} else {//若读取到时log4j.properties时,获取PropertyConfigurator实例(应该是用来读取properteis配置文件类)configurator = new PropertyConfigurator();}//这里使用到了多态的技术,根据使用不同的解析类来进行解析操作(重点关注)configurator.doConfigure(url, hierarchy);}
  • 从源码中我们可以看到在获取logger实例前会优先加载存在的配置文件或系统变量项,其中读取指定配置文件包含log4j.xml以及log4j.properteis文件,也就是说我们能够进行自定义配置文件来对日志进行操作。

其中的configurator实际为一个接口,包含多个实现类,其中第一个实现类中的方法用来解析XML配置,第二个实现类用来解析properties文件:

一般来说我们使用XML配置文件或者Properteis进行配置,那么我们就可以重点关注DOMConfigurator(读取xml配置文件)以及PropertyConfigurator(读取properteis配置文件)。当我们使用哪种配置文件时就可以看指定实现类的解析方法从而知道其中的配置项有哪些了!!!


4.2、PropertyConfigurator类源码分析*

前面说到了对于读取log4j.properties配置文件的操作类是PropertyConfigurator类:

  • 若是读取的是log4j.properteis,就会执行该类的doConfigure()方法。

下面部分是源码中的注释内容,其中包含了如何在log4j.properties中进行键值对的设置,下面的内容是我通过IDEATranslate插件翻译过来的:

从文件中读取配置。 现有配置不会清除也不会重置。 如果你需要一个不同的行为,然后调用resetConfiguration之前调用方法doConfigure 。
配置文件由格式为key=value的语句组成。 下文讨论了不同配置元素的语法。
整个存储库的阈值
整个存储库阈值均按级别过滤日志记录请求,而与记录器无关。 语法为:log4j.threshold=[level]级别值可以包含字符串值OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL或自定义级别值。 可以以level#classname形式指定自定义级别值。 默认情况下,存储库范围的阈值设置为最低可能值,即ALL级别。
追加配置
Appender的配置语法为:# For appender named appenderName, set its class.# Note: The appender name can contain dots.log4j.appender.appenderName=fully.qualified.name.of.appender.class# Set appender specific options.log4j.appender.appenderName.option1=value1...log4j.appender.appenderName.optionN=valueN对于每个命名的附加程序,您都可以配置其Layout 。 配置追加器布局的语法为:log4j.appender.appenderName.layout=fully.qualified.name.of.layout.classlog4j.appender.appenderName.layout.option1=value1....log4j.appender.appenderName.layout.optionN=valueN将Filter添加到附加程序的语法为:log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.classlog4j.appender.appenderName.filter.ID.option1=value1...log4j.appender.appenderName.filter.ID.optionN=valueN第一行定义了由ID标识的过滤器的类名; 具有相同ID的后续行指定过滤器选项-值巴黎。 多个过滤器按照ID的字典顺序添加到附加器。 将ErrorHandler添加到附加程序的语法为:log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.classlog4j.appender.appenderName.errorhandler.root-ref={true|false}log4j.appender.appenderName.errorhandler.logger-ref=loggerNamelog4j.appender.appenderName.errorhandler.appender-ref=appenderNamelog4j.appender.appenderName.errorhandler.option1=value1...log4j.appender.appenderName.errorhandler.optionN=valueN配置记录器
配置根记录器的语法为:log4j.rootLogger=[level], appenderName, appenderName, ...此语法意味着可以提供一个可选级别,后跟以逗号分隔的追加程序名称。
级别值可以包含字符串值OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL或自定义级别值。 可以使用level#classname形式指定自定义级别值。
如果指定了级别值,则将根级别设置为相应级别。 如果未指定级别值,则根级别保持不变。
可以为根记录器分配多个附加程序。
每个appenderName (用逗号分隔)将被添加到root记录器中。 命名的附加程序是使用上面定义的附加程序语法定义的。
对于非根目录类别,语法几乎相同:log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...上面已针对根记录器讨论了可选级别值的含义。 但是,此外,可以指定值INHERITED,这意味着命名的记录器应从记录器层次结构继承其级别。
如果未提供级别值,则命名记录器的级别保持不变。
默认情况下,类别从层次结构继承其级别。 但是,如果设置了记录器的级别,然后又决定该记录器应继承其级别,则应将INHERITED指定为该级别值的值。 NULL是INHERITED的同义词。
与root记录器语法相似,每个appenderName (用逗号分隔)将附加到命名记录器。
有关可加additivity标志的含义,请参见用户手册中的附加器可加性规则。
对象渲染器
您可以定制在记录之前将给定类型的消息对象转换为String的方式。 通过为要自定义的对象类型指定一个ObjectRenderer来完成此操作。
语法为:log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class如log4j.renderer.my.Fruit=my.FruitRendererThrowableRenderer
您可以自定义Throwable实例在记录之前转换为String的方式。 这是通过指定ThrowableRenderer来完成的。
语法为:log4j.throwableRenderer=fully.qualified.name.of.rendering.classlog4j.throwableRenderer.paramName=paramValue如log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer记录仪工厂
不鼓励使用自定义记录器工厂,并且不再记录在案。
重置层次结构
当属性文件中存在log4j.reset = true时,将在配置之前重置层次结构。
例子下面给出了示例配置。 其他配置文件示例在examples文件夹中给出。# Set options for appender named "A1".# Appender "A1" will be a SyslogAppenderlog4j.appender.A1=org.apache.log4j.net.SyslogAppender# The syslog daemon resides on www.abc.netlog4j.appender.A1.SyslogHost=www.abc.net# A1's layout is a PatternLayout, using the conversion pattern# %r %-5p %c{2} %M.%L %x - %m\n. Thus, the log output will# include # the relative time since the start of the application in# milliseconds, followed by the level of the log request,# followed by the two rightmost components of the logger name,# followed by the callers method name, followed by the line number,# the nested disgnostic context and finally the message itself.# Refer to the documentation of PatternLayout for further information# on the syntax of the ConversionPattern key.log4j.appender.A1.layout=org.apache.log4j.PatternLayoutlog4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n# Set options for appender named "A2"# A2 should be a RollingFileAppender, with maximum file size of 10 MB# using at most one backup file. A2's layout is TTCC, using the# ISO8061 date format with context printing enabled.log4j.appender.A2=org.apache.log4j.RollingFileAppenderlog4j.appender.A2.MaxFileSize=10MBlog4j.appender.A2.MaxBackupIndex=1log4j.appender.A2.layout=org.apache.log4j.TTCCLayoutlog4j.appender.A2.layout.ContextPrinting=enabledlog4j.appender.A2.layout.DateFormat=ISO8601# Root logger set to DEBUG using the A2 appender defined above.log4j.rootLogger=DEBUG, A2# Logger definitions:# The SECURITY logger inherits is level from root. However, it's output# will go to A1 appender defined above. It's additivity is non-cumulative.log4j.logger.SECURITY=INHERIT, A1log4j.additivity.SECURITY=false# Only warnings or above will be logged for the logger "SECURITY.access".# Output will go to A1.log4j.logger.SECURITY.access=WARN# The logger "class.of.the.day" inherits its level from the# logger hierarchy.  Output will go to the appender's of the root# logger, A2 in this case.log4j.logger.class.of.the.day=INHERIT有关特定于类的选项,请参见每个Appender和Layout中的setOption方法。
使用#或! 注释行的开头字符。
  • 可以看其中的示例配置,查看用法。

若是想要知道如何进行解析log4j.properties,可以去看源代码中的方法。


4.3、初次自定义配置文件(log4j.properties)

配置要求:给rootlogger设置日志等级为trace,其appenderConsoleAppender,该appenderlayoutSimpleLayout

实现过程

由于我们创建的Maven项目,将自定义配置文件log4j.properties放置到resource目录下:

# rootLogger配置,第一个参数为日志等级,第二个参数为指定的appender(使用别名)
log4j.rootLogger = trace,console
# 这里的console只是作为指定appender的别名
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 这里就是设置ConsoleAppender的layout为SimpleLayout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

测试一下:

public class LogTest {public static void main(String[] args) {//获取logger实例Logger logger = Logger.getLogger(LogTest.class);//打印6个不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}
  • 可以看到当我们使用自定义配置文件时,就不需要像之前一样调用BasicConfigurator.configure();来进行配置rootlogger了。


五、Log4j内置的日志LogLog

5.1、LogLog源码分析以及开启其debug模式*

LogLog:它是Log4j内置的Log,能够记录自身执行的过程信息,内部统一了开关的判断。

  • 目的:可以观察配置信息,便于后期维护。

引出LogLog类

LogManager类源码中我们可以看到需要LogLog进行打日志的操作,如下:

public class LogManager {//静态代码块中static{...if(url != null) {LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");try {OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());} catch (NoClassDefFoundError e) {//若是在初始化配置时出现异常打了warn级别日志LogLog.warn("Error during default initialization", e);}} else {//资源没找到,打了debug日志LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");}} else {LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); }  ...}//静态方法中static public LoggerRepository getLoggerRepository() {if (repositorySelector == null) {repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());guard = null;Exception ex = new IllegalStateException("Class invariant violation");String msg ="log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";//在判断是否是可能的安全方案中,调用了LogLog进行日志输出if (isLikelySafeScenario(ex)) {LogLog.debug(msg, ex);} else {LogLog.error(msg, ex);}}return repositorySelector.getLoggerRepository();}
}

此时我们会抛出疑问,为什么明明打了不同的日志等级在显示器中并不显示呢?我们依旧去看LogLog类源码:

LogLog源码

LogLog类:其是单独的一个类

  • 可以看到其中包含了debugerrorwarn这几个日志等级的方法。
public class LogLog {//默认都是falseprotected static boolean debugEnabled = false;  private static boolean quietMode = false;//error方法,默认会输出err问题public static void error(String msg) {if(quietMode)return;System.err.println(ERR_PREFIX+msg);}  //debug方法public static void debug(String msg) {//由于debugEnabled是false,所以不会执行方法体内容if(debugEnabled && !quietMode) {System.out.println(PREFIX+msg);}}//warn方法,默认是会输出错误情况void warn(String msg) {if(quietMode)return;System.err.println(WARN_PREFIX+msg);}
}
  • 通过看源码我们得知,对于进行debug的调试日志是默认关闭的,而对于warnerror警告及错误信息默认是会输出的。这样其实我们就能知晓为什么在第三部分没有进行配置rootlogger时的报错信息了!!!

开启LogLogdebug模式

那么我们怎么样开启调试debug信息呢?其实在LogLog类中给我们提供了一个方法可以设置debugEnabled的布尔值!源码如下:

/**Allows to enable/disable log4j internal logging.*/
static public void setInternalDebugging(boolean enabled) {debugEnabled = enabled;
}

那么我们赶紧试试调用方法将debugEnabled设置true之后日志调试的效果吧。

开启LogLog.setInternalDebugging(true);

我们使用4.3中的案例(自定义log4j.properties)进行测试:

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}

其中还有个void setQuietMode(boolean quietMode),若是设置成true,那么LogLog中的三个日志等级都失效了!!!


六、各种Layout案例

6.1、PatternLayout(自定义格式示例)

创建过程

①创建log4j.propertiesMaven项目放置到resource目录下

# 设置rootlogger日志等级为trace、指定appender为下面定义的ConsoleAppender
log4j.rootLogger = trace,console
# 指定ConsoleAppender设置别名为console
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定ConsoleAppender的layout设置为PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 设置该layout的自定义格式(解析时会调用)
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • 设置自定义格式时实际上会先解析出.后面的名称为ConversionPattern之后添加上set,最后通过反射调用void setConversionPattern(String conversionPattern)方法设置自定义格式。

源码查看是如何进行赋值的

经过一番努力,定位到通过反射来执行set方法的操作,看下去即可:

  • 最终就是通过反射来执行void setConversionPattern(String conversionPattern)操作的,不仅感慨那些编写源代码的人是真的强!!!

②执行程序,输出自定义的格式内容

public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}

  • 看框中的格式即为我们自定义的格式内容!!!

七、各种Appender示例

7.1、FileAppender及其子类

FileAppender:用于输出到文件,其有两个实现类,DailyRollingFileAppender提供了轮询功能(可根据指定时间点添加文件)、RollingFileAppender提供了根据文件大小拆分的功能。

首先看下层级图

  • WriterAppender:提供属性encoding

    • FileAppender:提供属性append(是否追加,默认true)、file(文件路径)、bufferedIO(布尔值,默认false)、bufferSize(文件大小,默认为8K)。

      • DailyRollingFileAppender:提供属性datePattern(指定间隔轮询年月日时分秒)。
      • RollingFileAppender:提供属性maxFileSize(单个文件最大容量)、maxBackupIndex(最大分割文件数量)。

使用任意实现类时,即可设置其父类的属性。


FileAppender示例

作用:向文件进行输出,其有两个实现类可实现轮询以及分段(多文件)。

示例演示

①创建log4j.properties

log4j.rootLogger = trace,file
# FileAppender
#  file为名称   其中属性file:文件路径   encoding:编码
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.file = C:/Users/93997/Desktop/projectexers/logs/log.txt
log4j.appender.file.encoding = UTF-8
#  设置自定义布局(自定义输出格式)
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

②程序测试

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}

  • 注意其中logs文件需要提前创建。

源码分析

之前我们已经通过源码看到实际赋值是通过反射调用set方法进行的,所以我们直接看其中的set方法以及默认属性即可。

说明:对于encoding编码格式的设置,是因为FileAppender继承了WriterAppender(其中有setEncoding())。


DailyRollingFileAppender示例

作用:轮询功能(根据指定是每小时,还是每天输出log日志),父类是FileAppender,包含父类的所有设置属性。

示例演示

# 日志等级为trace,指定appender为下面以roll作为别名的
log4j.rootLogger = trace,roll
# DailyRollingFileAppender 轮询
log4j.appender.roll = org.apache.log4j.DailyRollingFileAppender
log4j.appender.roll.file = /logs/log4j.log
log4j.appender.roll.encoding = UTF-8
# datePattern指的是根据分钟来进行轮询 =》可设置年月日时分秒毫秒如右: '.'yyyy-MM-dd-HH-mm-ss-SSS
log4j.appender.roll.datePattern = '.'yyyy-MM-dd-HH-mm
#  设置自定义布局(自定义输出格式)
log4j.appender.roll.layout = org.apache.log4j.PatternLayout
log4j.appender.roll.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • log4j.properties放置在resource目录下(Maven项目)。
  • 这里设置路径file/logs:指的是c:\logs目录下

测试程序

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}

  • 上图仅作测试展示,与下方规则操作并不一致。新增的文件后缀实际上就是我们指定的每隔1分钟的时间。
  • 规则说明(测试结果):我们指定路径为/logs/log4j.log,在第一次运行测试程序时会创建log4j.log以及其第二个文件(两个都输出内容),接着第二次运行测试程序时(间隔未满1分钟),则继续会追加到log4j.log中;第三次运行测试程序(超过1分钟),则会再次创建一个后缀添加日期的log文件并将日志内容输入到其中。

源码查看


RollingFileAppender示例

作用:按照指定文件大小进行拆分,拆分最大的文件数量可指定。其父类为FileAppender

示例演示

# 日志等级为trace,指定appender为下面以rollfile作为别名的#
log4j.rootLogger = trace,rollfile
# RollingFileAppender 分段
log4j.appender.rollfile = org.apache.log4j.RollingFileAppender
log4j.appender.rollfile.file = /logs/log4j.log
log4j.appender.rollfile.encoding = UTF-8
# 设置单个文件最大容量(KB、MB、GB,其他单位默认传为10MB+1)以及最大文件个数
log4j.appender.rollfile.maxFileSize = 1MB
log4j.appender.rollfile.maxBackupIndex = 5
#  设置自定义布局(自定义输出格式)
log4j.appender.rollfile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollfile.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • log4j.properties放置在resource目录下(Maven项目)。
  • 其中设置单个容量大小为1MB,最大文件数为5个。

程序测试

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);for (int i = 0; i < 1000000; i++) {//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}
  • 由于程序执行1次看不到效果,所以我们执行多次来进行查看最终效果。

  • 这些都是LogLog的debug日志,能够看到正在执行的事情。

  • 可以看到对应的5个文件后缀名与我们设置最大文件数有关!!!

规则:若是超过文件容量maxFileSize则进行拆分,最多拆分我们设置的maxBackupIndex值数量的文件个数,若是超过文件数量则按照最先记录日志的进行覆盖。

源码分析

提问:对于最大文件容量maxFileSize可以直接写单位的吗,这么舒服?看下去吧

老样子先看下对应的set方法

接着我们分析其中的set方法:看看是如何根据单位分配的:

public class RollingFileAppender extends FileAppender {//默认为10MB  1maxFileSize=1字节protected long maxFileSize = 10*1024*1024;//1、设置容量方法public void setMaxFileSize(String value) {//调用了OptionConverter辅助类中的转换方法maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);//见2}
}public class OptionConverter {//2、toFileSize()根据传入的String值来转换public static long toFileSize(String value, long dEfault) {if(value == null)return dEfault;//首先将字符串中字母全都变为大写String s = value.trim().toUpperCase();//计量单位1个字节long multiplier = 1;int index;//下面进行容量判断,如KB、MB、GBif((index = s.indexOf("KB")) != -1) {multiplier = 1024;s = s.substring(0, index);//例如15KB,则为15}else if((index = s.indexOf("MB")) != -1) {multiplier = 1024*1024;s = s.substring(0, index);}else if((index = s.indexOf("GB")) != -1) {multiplier = 1024*1024*1024;s = s.substring(0, index);}if(s != null) {try {return Long.valueOf(s).longValue() * multiplier;}catch (NumberFormatException e) {LogLog.error("[" + s + "] is not in proper int form.");LogLog.error("[" + value + "] not in expected format.", e);}}//若是其他计量单位的话,默认返回10MB+1的容量return dEfault;}
}

通过查看源码,我们得知其中对字符串中的容量单位进行查找匹配,从而换算为指定的字节容量!!!


7.2、JDBCAppender

示例演示

概述及前提准备

JDBCAppender:这个appender能够连接数据库并且执行指定的SQL语句,主要用于将日志插入到数据库中。

提前准备如下

①创建数据库表log:该表包含了日志的相关消息

CREATE TABLE `log` (
`log_id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` varchar(255) DEFAULT NULL COMMENT '优先级',
`category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
`file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
`thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
`line` varchar(255) DEFAULT NULL COMMENT '号行',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);
  • 记得先创建一个test数据库,下面会用到

②导入Mysqljar包(驱动包),在pom.xml中添加坐标

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.32</version>
</dependency>

示例演示

# rootlogger的日志等级是trace,appender为JDBCAppender,logDB是下面指定的别名
log4j.rootLogger = trace,logDB
# JDBCAppender 存储到数据库中
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('changlu','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
  • 配置信息包含layout(输出格式)、Driver(指定mysql驱动类)、URLUserpassword(连接数据库三要素)以及要执行的sql语句(其中的各种%形式都是PatternLayout中的指定内容,可见上面6.1)
  • SQL对应则为简单的插入语句。

测试程序

public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//获取logger实例Logger logger = Logger.getLogger(LogTest.class);for (int i = 0; i < 1000000; i++) {//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}
}

  • Log4jLogLog的配置信息。


源码分析*

首先依旧是set方法查看:

那么我们会发出疑问,该类是如何执行SQL语句的呢?我们看其中的execute方法。

public class JDBCAppender extends org.apache.log4j.AppenderSkeleton//类名赋值操作public void setDriver(String driverClass) {try {//类加载我们的mysql驱动,此时进行初始化操作了Class.forName(driverClass);} catch (Exception e) {errorHandler.error("Failed to load driver", e,ErrorCode.GENERIC_FAILURE);}}//获取连接操作protected Connection getConnection() throws SQLException {if (!DriverManager.getDrivers().hasMoreElements())setDriver("sun.jdbc.odbc.JdbcOdbcDriver");if (connection == null) {//获取数据库连接connection = DriverManager.getConnection(databaseURL, databaseUser,databasePassword);}return connection;}//执行sql语句方法protected void execute(String sql) throws SQLException {Connection con = null;//这里使用到Statement来进行操作Statement stmt = null;try {con = getConnection();stmt = con.createStatement();//执行sql语句方法stmt.executeUpdate(sql);} finally {if(stmt != null) {stmt.close();}closeConnection(con);}//System.out.println("Execute: " + sql);}
}

看了源码之后我们知道在setDriver()时就对Mysql驱动类进行了初始化,之后execute()方法中获取数据库连接,获取Statement来进行操作数据库。

舒服了,但是对于解析log4j.properties的源码部分没有进行深入,主要是对于现在的我来说太复杂了就先放着,真心觉得写源码的大佬真的太强了,自己真的还只是个菜鸟!加油吧!


八、自定义Logger

如何自定义Logger呢?依旧是在配置文件中进行配置。

  • 语法log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...。其中logger_name可以设置为指定的包名,若获取logger实例的name为这个包名及包名下的类(全限定名)即归属于该实例。

用途:通过使用自定义Logger能够将第三方包下调用的类以及自己定义的类进行日志区分,对于自己的类的日志信息输出到文件中,而对于第三方包的类输出到屏幕上。

实例演示

# rootLogger日志等级为trace,输出到屏幕上
log4j.rootLogger = trace,console# 设置两个自定义logger
# xyz.changlu(自己创建的包)自定义logger,日志等级为info,输出到文件
log4j.logger.xyz.changlu = info,file
# 设置org.apache(第三方包)作为一个自定义logger,日志等级为error
log4j.logger.org.apache = error# console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n# FileAppender
#  file:文件路径   encoding:编码
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.file = C:/Users/93997/Desktop/projectexers/logs/log.log
log4j.appender.file.encoding = UTF-8
#  设置自定义布局(自定义输出格式)
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
  • 两个自定义logger默认都会继承rootLogger的,当调用logger实例时不仅仅会调用自身的appender还会调用rootLoggerappender,并且logger实例的日志等级会改变rootLogger的日志等级(为logger实例的日志等级)。

程序测试

import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;public class LogTest {public static void main(String[] args) {//开启LogLog的debug模式LogLog.setInternalDebugging(true);//① 自己包下类的自定义logger实例//getLogger()参数为xyz.changlu包下的类,所以获取到配置文件中xyz.changlu的logger实例//logger实例与rootLogger的日志等级都为INFO,本身实例输出到文件,rootLogger输出到窗口Logger logger = Logger.getLogger(LogTest.class);System.out.println(logger.getName());//打印不同的日志等级logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");//② org.Apache包下的自定义logger实例,只输出到屏幕(本身实例没有设置logger)// 本身实例与rootLogger日志等级为errorLogger logger1 = Logger.getLogger(Logger.class);System.out.println(logger1.getName());logger1.fatal("fatal logger1");logger1.error("error logger1");logger1.warn("warn logger1");logger1.info("info logger1");logger1.debug("debug logger1");logger1.trace("trace logger1");}
}

总结:当设置自定义logger配置时,当你设置name为一个包名时(如xyz.changlu),若在程序中调用Logger.getLogger(LogTest.class)(LogTest类的全限定类名为xyz.changlu.LogTest,即也就是logger实例的name)就是在配置文件中配置的xyz.changlulogger实例。


总结

1、Log4jApache提供的第三方日志框架,需要引入第三方jar包,其中同样了包含了LoggerAppender以及Layout,有六个日志等级,默认日志等级为debug。—见第一、二部分。

2、Log4j的自定义配置文件名称为log4j.xml。—见第三、四部分

3、包含了一个内置日志LogLog,需要通过LogLog.setInternalDebugging(true);来进行开启debug等级日志,作用是能够在控制台显示相关的加载配置信息过程,能够清楚其中的流程。—见第五部分

4、对于Log4j必须要对rootLogger进行初始化,默认不提供初始化,若想使用默认提供的需要调用方法BasicConfigurator.configure();初始化或者我们进行自定义配置文件见四、六、七部分,主要着重介绍输出到文件、数据库以及自定义layout的格式。—见四、六、七部分


参考资料

视频:2020年Java进阶教程,全面学习多种java日志框架

[1]. 彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理


我是长路,感谢你的耐心阅读,如有问题请指出,我会听取建议并进行修正。
欢迎关注我的公众号:长路Java,其中会包含软件安装等其他一些资料,包含一些视频教程以及学习路径分享。
编程学习qq群:891507813 我们可以一起探讨学习
注明:转载可,需要附带上文章链接

02、Log4j(第三方日志框架,带源码分析)相关推荐

  1. shiro实现无状态的会话,带源码分析

    转载请在页首明显处注明作者与出处 朱小杰      http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...

  2. 视频教程-RPC服务框架(Dubbo)源码分析-Java

    RPC服务框架(Dubbo)源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大 ...

  3. Apollo 2.0 框架及源码分析(一) | 软硬件框架

    原文地址:https://zhuanlan.zhihu.com/p/33059132 前言 如引言中介绍的,这篇软硬件框架多为现有消息的整合加一些个人的想法.关于 Apollo 介绍的文章已经有许多, ...

  4. skynet 框架snax源码分析----变量注入

    skynet为了简化服务的编写,推出了snax框架,源码里也有一个例子pingserver.这是snax原创文章的第一篇,所以先就分析snax框架里的interface.lua源码,它的实现应用了一个 ...

  5. 高性能网络I/O框架-netmap源码分析

    前几天听一个朋友提到这个netmap,看了它的介绍和设计,确实是个好东西.其设计思想与业界不谋而合--因为为了提高性能,几个性能瓶颈放在那里,解决方法自然也是类似的. netmap的出现,它既实现了一 ...

  6. 阿里开源一站式分布式事务框架seata源码分析(AT模式下TM与RM分析)

    序言: 对于阿里开源分布式事务框架seata的详细了解可以参考官网,这里不会详细介绍.本章只会介绍seata中AT模式的源码分析(对阿seata有一定了解或者成功完成过demo). seata中一个事 ...

  7. Python微型Web框架Bottle源码分析

    Bottle 是一个快速,简单和轻量级的 WSGI 微型 Web 框架的 Python.它作为单个文件模块分发,除了 Python 标准库之外没有依赖关系. 选择源码分析的版本是 Release 于 ...

  8. Spring框架—SpringBean源码分析

    原文作者:Javadoop 原文地址:Spring IOC 容器源码分析 在继续往下之前,我们需要先了解 BeanDefinition.我们说 BeanFactory 是 Bean 容器,那么 Bea ...

  9. 系统性详解Redis操作Hash类型数据(带源码分析及测试结果)

    1 缘起 系统讲解Redis的Hash类型CURD, 帮助学习者系统且准确学习Hash数据操作, 逐步养成测试的好习惯, 本文较长,Hash的操作比较多,请耐心看, 既可以集中时间看,亦可以碎片时间学 ...

最新文章

  1. loadrunner结果图分析
  2. (已解决)pycharm调试报错-UnicodeDecodeError:‘utf-8‘ codec can‘t decode byte 0xe8 in position 1023
  3. Verilog初级教程(21)Verilog中的延迟控制语句
  4. 关于Spring的构造函数,init-method,和依赖注入的先后顺序
  5. LibSVM学习(一)——初识LibSVM
  6. 看了msn的站点有感
  7. linux学到了什么技术,Linux到底学什么?如何学?
  8. IOS之UIToolBar约束报错
  9. codevs1521 华丽的吊灯
  10. docker 空间满数据迁移笔记
  11. Android笔记之权限库AndPermission
  12. Docker入门之安装MySQL
  13. 2017阿里巴巴实习生招聘编程题
  14. 天空的颜色 363
  15. android虚拟机获取root权限,Android虚拟机获取root权限
  16. Qt中的UI文件介绍
  17. 项目经理的职业规划,建议收藏
  18. linux下安装 postgresql 14
  19. 稻城亚丁,从你的全世界路过
  20. 「Jetpack - Paging3使用」

热门文章

  1. struts2 mysql_Struts2连接Mysql的Crud使用
  2. 如何在mac本上安装android sdk(转-完美解决)
  3. 深入理解SELinux SEAndroid(第一部分)
  4. Navicat使用教程:使用Navicat Premium 12自动执行数据库复制(二)
  5. 小米手机能刷鸿蒙系统了!这操作太6了!
  6. 九天鸟问答社区:招募社区管理员和活跃用户
  7. oracle和sqlserver转换,ORACLE与SQLSERVER数据转换
  8. 杭州dell戴尔服务器维修点,【浙江dell杭州服务中心】报价_杭州戴尔维修中心
  9. CTFHUB-WEB--cooki注入wp
  10. imu_utils IMU内参标定工具 imu_utils安装教程 imu_utils使用教程