空了的时候,我都会在群里偷偷摸摸地潜水,对小伙伴们的一举一动、一言一行筛查诊断。一副班主任的即时感,让我感到很是的快乐,略微夹带一丝丝的枯燥。java
这不,我在战国时代读者群里发现了这么一串聊天记录:程序员
居然有小伙伴不知道“打日志”是什么意思,不知道该怎么学习,还有小伙伴回答说,只知道 Log4j!数据库
有那么一刻,我遭受到了一万点暴击,心里莫名的伤感,犹如一匹垂头丧气的狗。由于网络上总有一些不怀好意的人不停地攻击我,说我写的文章入门,毫无深度——他们就是我命中注定的黑子,不信你到脉脉上搜“沉默王二”,就能看到他们毫无新意的抨击。apache
我就想问一下,怎么了,入门的文章有入门的群体须要,而我刚好帮助了这么一大批初学者,我应该受到褒奖好很差?安全
(说好的不在意,怎么在意起来了呢?手动狗头)服务器
管他呢,我行我素吧,保持初心不改就对了!这篇文章就来讲说 Log4j,这个打印日志的鼻祖。Java 中的日志打印实际上是个艺术活,我保证,这句话毫不是忽悠。微信
事实证实,打印日志绝逼会影响到程序的性能,这是不能否认的,毕竟多作了一项工做。尤为是在交易很是频繁的程序里,涌现大量的日志确实会比较低效。网络
基于性能上的考量,小伙伴们颇有必要认认真真地学习一下如何优雅地打印 Java 日志。毕竟,性能是一个程序员优不优秀的重要考量。多线程
System.out.println()
恐怕是咱们在学习 Java 的时候,最经常使用的一种打印日志的方式了,几乎每一个 Java 初学者都这样干过,甚至一些老鸟。app
之因此这样打印日志,是由于很方便,上手难度很低,尤为是在 IDEA 的帮助下,只需在键盘上按下 so
两个字母就能够调出 System.out.println()
。
在本地环境下,使用 System.out.println()
打印日志是没问题的,能够在控制台看到信息。但若是是在生产环境下的话,System.out.println()
就变得毫无用处了。
控制台打印出的信息并无保存到日志文件中,只能即时查看,在一屏日志的状况下还能够接受。若是日志量很是大,控制台根本就装不下。因此就须要更高级的日志记录 API(好比 Log4j 和 java.util.logging)。
它们能够把大量的日志信息保存到文件中,而且控制每一个文件的大小,若是满了,就存储到下一个,方便查找。
使用 Java 日志的时候,必定要注意日志的级别,好比常见的 DEBUG、INFO、WARN 和 ERROR。
DEBUG 的级别最低,当须要打印调试信息的话,就用这个级别,不建议在生产环境下使用。
INFO 的级别高一些,当一些重要的信息须要打印的时候,就用这个。
WARN,用来记录一些警告类的信息,好比说客户端和服务端的链接断开了,数据库链接丢失了。
ERROR 比 WARN 的级别更高,用来记录错误或者异常的信息。
FATAL,当程序出现致命错误的时候使用,这意味着程序可能非正常停止了。
OFF,最高级别,意味着全部消息都不会输出了。
这个级别是基于 Log4j 的,和 java.util.logging 有所不一样,后者提供了更多的日志级别,好比说 SEVERE、FINER、FINEST。
为何说错误的日志记录方式会影响程序的性能呢?由于日志记录的次数越多,意味着执行文件 IO 操做的次数就越多,这也就意味着会影响到程序的性能,能 get 吧?
虽说普通硬盘升级到固态硬盘后,读写速度快了不少,但磁盘相对于内存和 CPU 来讲,仍是太慢了!就像马车和奔驰之间的速度差距。
这也就是为何要选择日志级别的重要性。对于程序来讲,记录日志是必选项,因此能控制的就是日志的级别,以及在这个级别上打印的日志。
对于 DEBUG 级别的日志来讲,必定要使用下面的方式来记录:
if(logger.isDebugEnabled()){ logger.debug("DEBUG 是开启的"); }
当 DEBUG 级别是开启的时候再打印日志,这种方式在你看不少源码的时候就能够发现,很常见。
切记,在生产环境下,必定不要开启 DEBUG 级别的日志,不然程序在大量记录日志的时候会变很慢,还有可能在你不注意的状况下,悄悄地把磁盘空间撑爆。
java.util.logging 属于原生的日志 API,Log4j 属于第三方类库,但我建议使用 Log4j,由于 Log4j 更好用。java.util.logging 的日志级别比 Log4j 更多,但用不着,就变成了多余。
Log4j 的另一个好处就是,不须要从新启动 Java 程序就能够调整日志的记录级别,很是灵活。能够经过 log4j.properties 文件来配置 Log4j 的日志级别、输出环境、日志文件的记录方式。
Log4j 仍是线程安全的,能够在多线程的环境下放心使用。
先来看一下 java.util.logging 的使用方式:
package com.itwanger; import java.io.IOException; import java.util.logging.FileHandler; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; /** * @author 微信搜「沉默王二」,回复关键字 PDF */ public class JavaUtilLoggingDemo { public static void main(String[] args) throws IOException { Logger logger = Logger.getLogger("test"); FileHandler fileHandler = new FileHandler("javautillog.txt"); fileHandler.setFormatter(new SimpleFormatter()); logger.addHandler(fileHandler); logger.info("细小的信息"); } }
程序运行后会在 target 目录下生成一个名叫 javautillog.txt 的文件,内容以下所示:
再来看一下 Log4j 的使用方式。
第一步,在 pom.xml 文件中引入 Log4j 包:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
第二步,在 resources 目录下建立 log4j.properties 文件,内容以下所示:
### 设置### log4j.rootLogger = debug,stdout,D,E ### 输出信息到控制台 ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n ### 输出DEBUG 级别以上的日志到=debug.log ### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = debug.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ### 输出ERROR 级别以上的日志到=error.log ### log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
1)配置根 Logger,语法以下所示:
log4j.rootLogger = [ level ] , appenderName, appenderName, …
level 就是日志的优先级,从高到低依次是 ERROR、WARN、INFO、DEBUG。若是这里定义的是 INFO,那么低级别的 DEBUG 日志信息将不会打印出来。
appenderName 就是指把日志信息输出到什么地方,能够指定多个地方,当前的配置文件中有 3 个地方,分别是 stdout、D、E。
2)配置日志输出的目的地,语法以下所示:
log4j.appender.appenderName = fully.qualified.name.of.appender.class log4j.appender.appenderName.option1 = value1 … log4j.appender.appenderName.option = valueN
Log4j 提供的目的地有下面 5 种:
3)配置日志信息的格式,语法以下所示:
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class log4j.appender.appenderName.layout.option1 = value1 … log4j.appender.appenderName.layout.option = valueN
Log4j 提供的格式有下面 4 种:
自定义格式的参数以下所示:
method:com.itwanger.Log4jDemo.main(Log4jDemo.java:14)
第三步,写个使用 Demo:
package com.itwanger; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; /** * @author 微信搜「沉默王二」,回复关键字 PDF */ public class Log4jDemo { private static final Logger logger = LogManager.getLogger(Log4jDemo.class); public static void main(String[] args) { // 记录debug级别的信息 logger.debug("debug."); // 记录info级别的信息 logger.info("info."); // 记录error级别的信息 logger.error("error."); } }
1)获取 Logger 对象
要使用 Log4j 的话,须要先获取到 Logger 对象,它用来负责日志信息的打印。一般的格式以下所示:
private static final Logger logger = LogManager.getLogger(Log4jDemo.class);
2)打印日志
有了 Logger 对象后,就能够按照不一样的优先级打印日志了。常见的有如下 4 种:
Logger.debug() ; Logger.info() ; Logger.warn() ; Logger.error() ;
程序运行后会在 target 目录下生成两个文件,一个名叫 debug.log,内容以下所示:
2020-10-20 20:53:27 [ main:0 ] - [ DEBUG ] debug. 2020-10-20 20:53:27 [ main:3 ] - [ INFO ] info. 2020-10-20 20:53:27 [ main:3 ] - [ ERROR ] error.
另一个名叫 error.log,内容以下所示:
2020-10-20 20:53:27 [ main:3 ] - [ ERROR ] error.
1)在打印 DEBUG 级别的日志时,切记要使用 isDebugEnabled()
!那小伙伴们确定很是好奇,为何要这样作呢?
先来看一下 isDebugEnabled()
方法的源码:
public boolean isDebugEnabled() { if(repository.isDisabled( Level.DEBUG_INT)) return false; return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel()); }
内部使用了 isDisabled()
方法进行了日志级别的判断,若是 DEBUG 是禁用的话,就 return false 了。
再来看一下 debug()
方法的源码:
public void debug(Object message) { if(repository.isDisabled(Level.DEBUG_INT)) return; if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) { forcedLog(FQCN, Level.DEBUG, message, null); } }
咦,不是也用 isDisabled()
方法判断吗?难道使用 isDebugEnabled()
不是多此一举吗?直接用 logger.debug()
不香吗?我来给小伙伴们解释下。
若是咱们在打印日志信息的时候须要附带一个方法去获取参数值,就像下面这样:
logger.debug("用户名是:" + getName());
假如 getName()
方法须要耗费的时间长达 6 秒,那完了!尽管配置文件里的日志级别定义的是 INFO,getName()
方法仍然会倔强地执行 6 秒,完过后再 debug()
,这就很崩了!
明明 INFO 的时候 debug()
是不执行的,意味着 getName()
也不须要执行的,恰恰就执行了 6 秒,是否是很傻?
if(logger.isDebugEnabled()) { logger.debug("用户名是:" + getName()); }
换成上面这种方式,那肯定此时 getName()
是不执行的,对吧?
为了程序性能上的考量,isDebugEnabled()
就变得颇有必要了!假如说 debug()
的时候没有传参,确实是不须要判断 DEBUG 是否启用的。
2)慎重选择日志信息的打印级别,由于这过重要了!若是只能经过日志查看程序发生了什么问题,那必要的信息是必需要打印的,但打印得太多,又会影响到程序的性能。
因此,该 INFO 的 info()
,该 DEBUG 的 debug()
,不要随便用。
3)使用 Log4j 而不是 System.out
、System.err
或者 e.printStackTrace()
来打印日志,缘由以前讲过了,就再也不赘述了。
4)使用 log4j.properties 文件来配置日志,尽管它不是必须项,使用该文件会让程序变得更灵活,有一种个人地盘我作主的味道。
5)不要忘记在打印日志的时候带上类的全名和线程名,在多线程环境下,这点尤其重要,不然定位问题的时候就太难了。
6)打印日志信息的时候尽可能要完整,不要太过于缺省,尤为是在遇到异常或者错误的时候(信息要保留两类:案发现场信息和异常堆栈信息,若是不作处理,经过 throws 关键字往上抛),省得在找问题的时候都是一些无用的日志信息。
7)要对日志信息加以区分,把某一类的日志信息在输出的时候加上前缀,好比说全部数据库级别的日志里添加 DB_LOG
,这样的日志很是大的时候能够经过 grep
这样的 Linux 命令快速定位。
8)不要在日志文件中打印密码、银行帐号等敏感信息。
打印日志真的是一种艺术活,搞很差会严重影响服务器的性能。最可怕的是,记录了日志,但最后发现屁用没有,那简直是苍了个天啊!尤为是在生产环境下,问题没有记录下来,但重现有必定的随机性,到那时候,真的是叫每天不该,叫地地不灵啊!
嗯哼,其实我已经写完了整个日志系统,包括 Log4j、 SLF4J、Logback、Log4j 它弟 Log4j 2,但我以为分开来发的话,更利于 SEO(瞧我这为了流量的心机,手动狗头)。若是你确实须要看完整版的话,我也贴心地为你准备了,点击下面的连接就能够下载 PDF:
https://pan.baidu.com/s/1dPwsQhT5OMVapE7hGi7vww 提取码:fxxy
码字不易,平常求个赞吧,动一动金手指,bug 少一个(逃。