天啦撸!打印日志居然只晓得 Log4j?

空了的时候,我都会在群里偷偷摸摸地潜水,对小伙伴们的一举一动、一言一行筛查诊断。一副班主任的即时感,让我感到很是的快乐,略微夹带一丝丝的枯燥java

这不,我在战国时代读者群里发现了这么一串聊天记录:程序员

居然有小伙伴不知道“打日志”是什么意思,不知道该怎么学习,还有小伙伴回答说,只知道 Log4j!数据库

有那么一刻,我遭受到了一万点暴击,心里莫名的伤感,犹如一匹垂头丧气的狗。由于网络上总有一些不怀好意的人不停地攻击我,说我写的文章入门,毫无深度——他们就是我命中注定的黑子,不信你到脉脉上搜“沉默王二”,就能看到他们毫无新意的抨击。apache

我就想问一下,怎么了,入门的文章有入门的群体须要,而我刚好帮助了这么一大批初学者,我应该受到褒奖好很差?安全

(说好的不在意,怎么在意起来了呢?手动狗头)服务器

管他呢,我行我素吧,保持初心不改就对了!这篇文章就来讲说 Log4j,这个打印日志的鼻祖。Java 中的日志打印实际上是个艺术活,我保证,这句话毫不是忽悠。微信

事实证实,打印日志绝逼会影响到程序的性能,这是不能否认的,毕竟多作了一项工做。尤为是在交易很是频繁的程序里,涌现大量的日志确实会比较低效。网络

基于性能上的考量,小伙伴们颇有必要认认真真地学习一下如何优雅地打印 Java 日志。毕竟,性能是一个程序员优不优秀的重要考量。多线程

0一、为何须要在 Java 中打印日志

System.out.println() 恐怕是咱们在学习 Java 的时候,最经常使用的一种打印日志的方式了,几乎每一个 Java 初学者都这样干过,甚至一些老鸟。app

之因此这样打印日志,是由于很方便,上手难度很低,尤为是在 IDEA 的帮助下,只需在键盘上按下 so 两个字母就能够调出 System.out.println()

在本地环境下,使用 System.out.println() 打印日志是没问题的,能够在控制台看到信息。但若是是在生产环境下的话,System.out.println() 就变得毫无用处了。

控制台打印出的信息并无保存到日志文件中,只能即时查看,在一屏日志的状况下还能够接受。若是日志量很是大,控制台根本就装不下。因此就须要更高级的日志记录 API(好比 Log4j 和 java.util.logging)。

它们能够把大量的日志信息保存到文件中,而且控制每一个文件的大小,若是满了,就存储到下一个,方便查找。

0二、选择不一样日志级别的重要性

使用 Java 日志的时候,必定要注意日志的级别,好比常见的 DEBUG、INFO、WARN 和 ERROR。

DEBUG 的级别最低,当须要打印调试信息的话,就用这个级别,不建议在生产环境下使用。

INFO 的级别高一些,当一些重要的信息须要打印的时候,就用这个。

WARN,用来记录一些警告类的信息,好比说客户端和服务端的链接断开了,数据库链接丢失了。

ERROR 比 WARN 的级别更高,用来记录错误或者异常的信息。

FATAL,当程序出现致命错误的时候使用,这意味着程序可能非正常停止了。

OFF,最高级别,意味着全部消息都不会输出了。

这个级别是基于 Log4j 的,和 java.util.logging 有所不一样,后者提供了更多的日志级别,好比说 SEVERE、FINER、FINEST。

0三、错误的日志记录方式是如何影响性能的

为何说错误的日志记录方式会影响程序的性能呢?由于日志记录的次数越多,意味着执行文件 IO 操做的次数就越多,这也就意味着会影响到程序的性能,能 get 吧?

虽说普通硬盘升级到固态硬盘后,读写速度快了不少,但磁盘相对于内存和 CPU 来讲,仍是太慢了!就像马车和奔驰之间的速度差距。

这也就是为何要选择日志级别的重要性。对于程序来讲,记录日志是必选项,因此能控制的就是日志的级别,以及在这个级别上打印的日志。

对于 DEBUG 级别的日志来讲,必定要使用下面的方式来记录:

if(logger.isDebugEnabled()){ 
    logger.debug("DEBUG 是开启的"); 
}

当 DEBUG 级别是开启的时候再打印日志,这种方式在你看不少源码的时候就能够发现,很常见。

切记,在生产环境下,必定不要开启 DEBUG 级别的日志,不然程序在大量记录日志的时候会变很慢,还有可能在你不注意的状况下,悄悄地把磁盘空间撑爆。

0四、为何选择 Log4j 而不是 java.util.logging

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 种:

  • org.apache.log4j.ConsoleAppender:控制台
  • org.apache.log4j.FileAppender:文件
  • org.apache.log4j.DailyRollingFileAppender:天天产生一个文件
  • org.apache.log4j.RollingFileAppender:文件大小超过阈值时产生一个新文件
  • org.apache.log4j.WriterAppender:将日志信息以流格式发送到任意指定的地方

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 种:

  • org.apache.log4j.HTMLLayout:HTML 表格
  • org.apache.log4j.PatternLayout:自定义
  • org.apache.log4j.SimpleLayout:包含日志信息的级别和信息字符串
  • org.apache.log4j.TTCCLayout:包含日志产生的时间、线程、类别等等信息

自定义格式的参数以下所示:

  • %m:输出代码中指定的消息
  • %p:输出优先级
  • %r:输出应用启动到输出该日志信息时花费的毫秒数
  • %c:输出所在类的全名
  • %t:输出该日志所在的线程名
  • %n:输出一个回车换行符
  • %d:输出日志的时间点
  • %l:输出日志的发生位置,包括类名、线程名、方法名、代码行数,好比: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.

0五、打印日志的 8 个小技巧

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.outSystem.err 或者 e.printStackTrace() 来打印日志,缘由以前讲过了,就再也不赘述了。

4)使用 log4j.properties 文件来配置日志,尽管它不是必须项,使用该文件会让程序变得更灵活,有一种个人地盘我作主的味道。

5)不要忘记在打印日志的时候带上类的全名和线程名,在多线程环境下,这点尤其重要,不然定位问题的时候就太难了。

6)打印日志信息的时候尽可能要完整,不要太过于缺省,尤为是在遇到异常或者错误的时候(信息要保留两类:案发现场信息和异常堆栈信息,若是不作处理,经过 throws 关键字往上抛),省得在找问题的时候都是一些无用的日志信息。

7)要对日志信息加以区分,把某一类的日志信息在输出的时候加上前缀,好比说全部数据库级别的日志里添加 DB_LOG,这样的日志很是大的时候能够经过 grep 这样的 Linux 命令快速定位。

8)不要在日志文件中打印密码、银行帐号等敏感信息。

0六、 总结

打印日志真的是一种艺术活,搞很差会严重影响服务器的性能。最可怕的是,记录了日志,但最后发现屁用没有,那简直是苍了个天啊!尤为是在生产环境下,问题没有记录下来,但重现有必定的随机性,到那时候,真的是叫每天不该,叫地地不灵啊!

嗯哼,其实我已经写完了整个日志系统,包括 Log4j、 SLF4J、Logback、Log4j 它弟 Log4j 2,但我以为分开来发的话,更利于 SEO(瞧我这为了流量的心机,手动狗头)。若是你确实须要看完整版的话,我也贴心地为你准备了,点击下面的连接就能够下载 PDF:

https://pan.baidu.com/s/1dPwsQhT5OMVapE7hGi7vww 提取码:fxxy

码字不易,平常求个赞吧,动一动金手指,bug 少一个(逃。

相关文章
相关标签/搜索