Java日志学习

不要再滥用System.out.println()


日志概念

日志是什么

生活中的日志是记录你生活的点点滴滴,让它把你内心的世界表露出来,更好的诠释自己的内心世界,而电脑里的日志可以是有价值的信息宝库,也可以是毫无价值的数据泥潭。

网络设备、系统及服务程序等,在运作时都会产生一个叫log的事件记录;每一行日志都记载着日期、时间、使用者及动作等相关操作的描述。

日志有什么优点

输出日志,而不是用System.out.println(),有以下几个好处:

  1. 可以设置输出样式,避免自己每次都写"ERROR: " + var
  2. 可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
  3. 可以被重定向到文件,这样可以在程序运行结束后查看日志;
  4. 可以按包名控制日志级别,只输出某些包打的日志;
  5. ……

总之就是十分友好。


Java中的日志

常见的日志工具

  • [日志实现]Java标准库内置的日志包java.util.logging
  • [日志接口][不活跃]Apache软件基金会的Commons Logging
  • [日志接口][活跃]QOS.ch的日志抽象SLF4J
  • [日志实现][不活跃]Apache软件基金会的log4j
  • [日志实现][活跃]QOS.chLogback

我的选择

截止当前(2021年7月1日)以上只有SLF4JLogback近期(一个月内)还有commit
所以笔者做以下选择

  • SLF4J + Logback

准备工作

新建示例工程

笔者选择使用更为简单的Maven工程
目录结构如下(已删去无关文件)

│   pom.xml
│
├───src
    └───main
        └───java
            └───com
                └───example
                        App.java

唯一源代码文件App.java

package com.example;

/**
 * Hello world!
 *
 */
public class App
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

配置工程依赖

  <dependencies>
    <!-- slf4j-api -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.31</version>
    </dependency>
    <!-- logback -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.2.3</version>
    </dependency>
    <!-- logback-classic(已含有对slf4j的集成包) -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
  </dependencies>

上手

最基本的用法

    public static void main(String[] args) {
        System.out.println("Hello World!");
        helloSLF4J();
    }

    public static void helloSLF4J() {
        Logger logger = LoggerFactory.getLogger(App.class);
        logger.info("Hello SLF4J!");
    }

控制台输出

Hello World!
16:38:54.863 [main] INFO com.example.App - Hello SLF4J!

看到没,时间 [线程] 日志等级 所在类 - 信息的友善格式完胜直接System.out.println()

SLF4J日志等级

优先级由低到高:

  1. trace - 一般用来追踪详细的程序运行流
  2. debug - 这类日志往往用在调试场景
  3. info - 用来记录程序运行的一些关键信息
  4. warn - 用来记录一些警告信息
  5. error - 用来记录运行时的错误信息

代码实例

        Logger logger = LoggerFactory.getLogger(App.class);
        logger.trace("Hello trace!");
        logger.debug("Hello debug!");
        logger.info("Hello info!");
        logger.warn("Hello warn!");
        logger.error("Hello error!");

控制台输出

16:56:14.662 [main] DEBUG com.example.App - Hello debug!
16:56:14.669 [main] INFO com.example.App - Hello info!
16:56:14.669 [main] WARN com.example.App - Hello warn!
16:56:14.669 [main] ERROR com.example.App - Hello error!

可以看到除了trace其余等级的日志都打印了出来,为什么trace那么可怜?答案下节揭晓。

配置文件(Logback)

SLF4J不过是日志接口而已,要想对日志进行配置,还需要在resources目录下新建一个配置文件。

│   pom.xml
│
├───src
    └───main
        ├───java
        │   └───com
        │       └───example
        │               App.java
        │
        └───resources
                logback.xml

配置文件格式可以是groovyxml,笔者不熟悉groovy,所以选择xml格式。

Groovy

Groovy是一种基于JVM 的敏捷开发语言,它结合了PythonRubySmalltalk 的许多强大的特性。既可以作为脚本语言直接解释执行,也可以编译成Java字节码执行,十分强大。
这里不做过多介绍。

XML

XML 指可扩展标记语言(eXtensible Markup Language)。
XML 被设计用来传输和存储数据。

logback.xml内容如下

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>xml %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="trace">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
名词解释
名词 意义
configuration 配置
appender 输出源
encoder 编码器
pattern 模式
layout 布局
root

笔者从logback官方手册中复制了一个最简单的配置,但又有所修改。
文件定义了一个名为STDOUTappender,又它的class属性可以看出,它是一个向控制台输出的appender
这个appenderencoderpatternxml %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

Conversion Word

像%d{HH:mm:ss.SSS}中的{HH:mm:ss.SSS}是代表时间的格式,{}中的内容是转义词的一些属性。
更详细的解释可以看这里

转义词(Conversion Word)表[部分]

名词 意义
%d 当前时间
%thread 当前线程
%level 当前消息等级
%logger 当前logger
%msg 消息内容
%n 换行

搞懂了%{}的意义,但这个%-5level中间夹的-5又是怎么回事?

Format modifiers

用格式修饰符可以美观地对齐内容。
点击这里看更多用法与详解。

格式修饰符(Format modifiers)表[部分]

格式修饰符 左对齐 最小宽度[字符] 最大宽度[字符]
%20logger 20
%-20loggerr 20
%.30logger - 30

所以,%-5level就是将等级以最小5个字符左对齐。

root是配置的根,它的输出等级是traceroot中引用了名为STDOUTappender
让我们再运行一次工程。

xml 17:30:34.232 [main] TRACE com.example.App - Hello trace!
xml 17:30:34.234 [main] DEBUG com.example.App - Hello debug!
xml 17:30:34.235 [main] INFO  com.example.App - Hello info!
xml 17:30:34.235 [main] WARN  com.example.App - Hello warn!
xml 17:30:34.235 [main] ERROR com.example.App - Hello error!

look!!!
每行日志开头都由我们写进patternxml开头,我们的xml配置文件生效了。而且这一次,trace级别的日志打印出来了!再也不是没人爱了!

输出日志到文件

新增appender

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>myApp.log</file>
        <encoder>
            <pattern>file %date %level [%thread] %logger{36} [%file:%line] %msg%n</pattern>
        </encoder>
    </appender>

别忘了在root中引用

<appender-ref ref="FILE" />

运行代码
发现项目目录下出现了myApp.log文件
查看内容

file 2021-07-09 12:53:33,940 TRACE [main] com.example.App [App.java:22] Hello trace!
file 2021-07-09 12:53:33,942 DEBUG [main] com.example.App [App.java:23] Hello debug!
file 2021-07-09 12:53:33,943 INFO [main] com.example.App [App.java:24] Hello info!
file 2021-07-09 12:53:33,943 WARN [main] com.example.App [App.java:25] Hello warn!
file 2021-07-09 12:53:33,948 ERROR [main] com.example.App [App.java:26] Hello error!

正如我们定义appender一样。

参考资料

[1] 百度百科 日志
[2] 廖雪峰的官方网站 Java教程
[3] SLF4J官网
[4] Logback官网
[5] Java日志框架:SLF4J详解
[6] SLF4J日志级别以及使用场景
[7] XML 教程