五、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类:其是单独的一个类
可以看到其中包含了debug、error、warn这几个日志等级的方法。
public class LogLog { //默认都是false protected 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的调试日志是默认关闭的,而对于warn、error警告及错误信息默认是会输出的。这样其实我们就能知晓为什么在第三部分没有进行配置rootlogger时的报错信息了!!!
开启LogLog的debug模式
那么我们怎么样开启调试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.properties,Maven项目放置到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、GB if((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数据库,下面会用到
②导入Mysql的jar包(驱动包),在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驱动类)、URL、User、password(连接数据库三要素)以及要执行的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"); } }
Log4j的LogLog的配置信息。
源码分析*
首先依旧是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还会调用rootLogger的appender,并且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日志等级为error Logger 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、Log4j是Apache提供的第三方日志框架,需要引入第三方jar包,其中同样了包含了Logger、Appender以及Layout,有六个日志等级,默认日志等级为debug。—见第一、二部分。
2、Log4j的自定义配置文件名称为log4j.xml。—见第三、四部分
3、包含了一个内置日志LogLog,需要通过LogLog.setInternalDebugging(true);来进行开启debug等级日志,作用是能够在控制台显示相关的加载配置信息过程,能够清楚其中的流程。—见第五部分
4、对于Log4j必须要对rootLogger进行初始化,默认不提供初始化,若想使用默认提供的需要调用方法BasicConfigurator.configure();初始化或者我们进行自定义配置文件见四、六、七部分,主要着重介绍输出到文件、数据库以及自定义layout的格式。—见四、六、七部分





















