1. 日志概述

1.1 为什么要学习日志

        在学习JavaSe部分,我们通过使用System.out.println打印日志,观察、发现问题所在,追踪问题源头;在学习Spring的阶段,也经常需要根据控制台的日志来分析和定位问题。

        随着项目的复杂度提升,我们对日志的打印也有了更高的需求,不仅仅是定位排查问题。

        比如:需要记录一些用户操作记录,也可能有使用日志来记录用户的一些喜好,把日志持久化,后续进行数据分析等。由于System.out.print不能很好的满足我们的需求,我们就需要使用一些专门的日志框架。

1.2 日志的用途

        日志主要是为了发现问题、分析问题、定位问题的,但除此之外,日志还有很多用途。

1、系统监控 

        监控现在几乎是一个成熟系统的标配,我们可以通过日志记录这个系统的运行状态,每一个方法的响应时间、响应状态等,对数据进行分析,设置不同的规则,超过阈值时进行报警。比如统计日志中关键字的数量,并在关键字数量达到一定条件时报警,这也是日志的常见需求之一。

2、数据采集

        数据采集是一个比较大的范围,采集的数据可以作用在很多方面,比如数据统计,推荐排序等。

        数据统计:统计页面的浏览量(PV),访客量(UV),点击量等等,根据这些数据进行数据分析,优化公司运营策略。

        推荐排序:目前推荐排序应用在各个领域,我们经常接触的各行各业,很多也都涉及推荐排序,比如购物、广告、新闻等领域。数据采集是推荐排序工作中必须做的一环,系统通过日志记录用户的浏览历史,停留时长等,算法人员通过分析这些数据,训练模型,给用户做推荐。

        下图的数据源,其中一部分就来自于日志记录的数据。

3、日志审计

        随着互联网的发展,众多企业的关键业务越来越多的运行于网络之上。网络安全越来越受到大家的关注,系统安全也成为项目中的一个重要环节,安全审计也是系统中非常重要的部分。国家的政策法规、行业标准等都明确对日志审计提出了要求。通过系统日志分析,可以判断一些非法攻击,非法调用,以及系统处理过程中的安全隐患。

2. 日志使用

        Spring Boot项目在启动时,默认就有日志输出,如下图:

         我们通过以前学习的时候打印System.out.println类型的日志,相比于Spring Boot打印的日志,少了很多信息。

        Spring Boot内置了日志框架:Slf4j ,我们可以直接在程序中调用 Slf4j 来输出日志。

2.1 打印日志

        步骤如下:

1、在程序中得到日志对象。

        在程序中获取日志对象需要使用日志工程 LoggerFactory,如下代码所示:

private static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);

          LoggerFactory.getLogger需要传递一个参数,标识这个日志的名称。这样就可以更清晰的知道是哪个类输出的日志。当有问题时,可以更方便直观的定位到问题类。 

        Logger对象是属于 org.slf4j 包下的,不要导入错包 

2、使用日志对象输出要打印的内容。

         日志对象的打印方法有很多种,我们可以先使用 info() 方法来输出日志,代码如下:

package com.example.zxslzw2014_8_11;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/logger")
@RestController
public class LoggerController {
    private static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
    @RequestMapping("/print")
    public String print() {
        logger.info("使用日志对象打印日志");
        System.out.println("使用sout打印日志");
        return "仲夏是荔枝味";
    }
}

        如此,浏览器访问http://127.0.0.1:8101/logger/print

         控制台输出的日志内容如下:

 

2.2 日志框架介绍

         SLF4J 不同于其他日志框架,它不是一个真正的日志实现,而是一个抽象层,对日志框架制定的一种规范、标准、接口。所以SLF4J并不能独立使用,需要和具体的日志框架配合使用。 

2.2.1 门面模式(外观模式)

        SLF4J是门面模式的典型应用(但不仅仅使用了门面模式)。

        门面模式(Facade Pattern)又称为外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用。

        门面模式主要包含2种角色: 

        外观角色(Facade):也称门面角色,系统对外的统一接口。

        子系统角色(SubSystem):可以同时有一个或多个 SubSystem。每个 SubSystem 都不是一个单独的类,而是一个类的集合。SubSystem并不知道Facade的存在,对于SubSystem而言,Facade只是另一个客户端而已(即Facade对SubSystem透明)

        ⽐如去医院看病,可能要去挂号, ⻔诊, 化验, 取药, 让患者或患者家属觉得很复杂, 如果有提供接待⼈ 员, 只让接待⼈员来处理, 就很⽅便.

2.2.2 门面模式的实现

        场景:回家,我们会开各个屋的灯。离开家时,会关闭各个屋的灯。如果家里设置一个总开关,来控制整个屋的灯就会很方便。

        使用门面模式实现:

1、灯接口的构造

public interface Light {
    void on();
    void off();
}

2、 卧室灯接口

public class BedRoomLight implements Light{
    @Override
    public void on() {
        System.out.println("打开卧室灯");
    }
    @Override
    public void off() {
        System.out.println("关闭卧室灯");
    }
}

3、走廊灯接口

public class HallLight implements Light{
    @Override
    public void on() {
        System.out.println("打开走廊灯");
    }
    @Override
    public void off() {
        System.out.println("关闭走廊灯");
    }
}

4、灯门面模式

public class LightFacade {
    private Light bedRoomLight = new BedRoomLight();
    private Light hallLight = new HallLight();
    public void lightOn() {
        bedRoomLight.on();
        hallLight.on();
    }
    public void lightOff() {
        bedRoomLight.off();
        hallLight.off();
    }
}

5、main方法

public class Main {
    public static void main(String[] args) {
        LightFacade lightFacade = new LightFacade();
        lightFacade.lightOn();
        lightFacade.lightOff();
    }
}

6、运行结果如下

2.2.3  门面模式的优点

        1、减少了系统的相互依赖. 实现了客⼾端与⼦系统的耦合关系, 这使得⼦系统的变化不会影响到调⽤它 的客⼾端;

        2、提⾼了灵活性, 简化了客⼾端对⼦系统的使⽤难度, 客⼾端⽆需关⼼⼦系统的具体实现⽅式, ⽽只需 要和⻔⾯对象交互即可.

        3、提⾼了安全性. 可以灵活设定访问权限, 不在⻔⾯对象中开通⽅法, 就⽆法访问

2.2.4 SLF4J 框架介绍

        SLF4J 就是其他日志框架的门面。SLF4J可以理解为是提供日志服务的统一API接口,并不涉及到具体的日志逻辑实现

1、不引入日志门面:

        常见的日志框架有log4J、logback等。如有一个项目已经使用了log4J,而你依赖的另一个类库,假设是Apache Active MQ,它依赖于另外一个日志框架logback,那么你就需要把logback也加载进去。如图:

不引入日志门面所存在的问题:

1、不同日志框架的API接口陪配置文件不同,如果多个日志框架共存,那么就不得不维护多套配置文件了(这里的配置文件是指用户自定义的配置文件)。

2、如果更换日志框架,应用程序将不得不修改代码,并且修改过程中可能会存在一些代码冲突。

3、如果引入的第三方框架,需要使用了多套配置文件,那就不得不维护多套配置文件了。

2、引入日志门面:

        引入门面日志框架之后,应用程序和日志框架(框架的具体实现)之间就有了统一的API接口(门面日志框架实现),此时应用程序只需要维护一套日志文件配置,且当底层实现框架改变时,也不需要更改应用程序的代码。如图:

           SLF4J就是这个日志门面。总的来说,SLF4J使你的代码独立于任意一个特定的日志API,这是一个对开发API的开发者很好的思想。

2.3 日志格式的说明

        打印的日志分别代表什么呢?如图:

 从上图可以看到,⽇志输出内容元素具体如下:

        1. 时间⽇期:精确到毫秒

        2. ⽇志级别:ERROR, WARN, INFO, DEBUG 或TRACE

        3. 进程ID

        4. 线程名

        5. Logger名(通常使⽤源代码的类名)

        6. ⽇志内容

2.4 日志级别

        日志级别代表日志信息对应问题的严重性,为了更快的筛选符合目标的日志信息。

        日志级别大概类似于公司内各个从下到上的级别领导,关于每一个业务都要从下往上层层上报签字,同样的道理,有了日志级别之后,就可以过滤想看到的信息了,比如只关注ERROR级别的,就可以根据级别过滤出来ERROR级别的日志信息,节约开发者的信息筛选时间。

 2.4.1 日志级别的分类

        日志的级别从高到低依次分为:FATAL、ERROE、WARN、INFO、DEBUG、TRACE。

FATAL致命信息,表示需要立即被处理的系统级错误。

ERROR错误信息,级别较高的错误日志信息,但仍然不影响系统的继续运行。

WRAN警告信息,不影响使用,但需要注意的问题。

INFO普通信息,用于记录应用程序正常运行时的一些信息,例如系统启动完成、请求处理完成等。

DEBUG调试信息,需要调试时候的管家信息打印。

TRACE追踪信息,比DEBUG更细粒度的信息事件(除非有特殊用意,否则请使用DEBUG级别替代)

        日志级别的顺序 如下所示,同时级别越高,收到的信息越少;

2.4.2 日志级别的使用

        日志级别是开发人员自己设置的。开发人员根据自己的理解来判断该信息的重要程度。

        Spring Boot默认的日志框架是Logback,Logback没有FATAL级别的日志,它被映射到ERROR。我们也可以想想,如果出现fatal日志,表示服务已经出现了某种程度的不可用,需要系统管理员紧急介入处理。通常情况下,一个进程生命周期中应该最多只有一次FATAL记录。

        针对这些级别,Logger 对象分别提供了对应的方法,来输出日志。代码如下:

package com.example.zxslzw2014_8_11;


import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/logger")
@RestController
public class LoggerController {
    private static Logger logger = LoggerFactory.getLogger(LoggerController.class);

    @RequestMapping("/level")
    public String levelPrint() {
        logger.trace("=======trace级别的日志==========");
        logger.debug("=======debug级别的日志==========");
        logger.info("=======info级别的日志===========");
        logger.warn("=======warn级别的日志==========");
        logger.error("=======error级别的日志==========");
        return "success";
    }
}

        访问浏览器http://127.0.0.1:8101/logger/level,返回success,控制台页面如下:

         观察打印日志的结果,只打印了info、warn、error,并没有输出trace、debug。这与日志级别的配置有关,日志的输出级别默认最低级是到info级别,所以只会打印大于等于次级别的日志,也就只会打印info、warn、error。

        上面是日志的使用,日志框架还支持我们更灵活的输出日志,包括内容、格式等。

2.5日志配置

2.5.1 配置日志级别

        日志级别配置只需要在配置文件中设置 “logging.level” 配置项即可,如下所示:

properties配置:

logging.level.root=debug

yml配置:

logging:
  level:
    root: debug

重新运行上面的代码,访问浏览器,日志页面如下:

        我们会发现日志页面密密麻麻的,多了很多debug级别的日志;

 2.5.2 日志持久化

        以上的日志都是输出在控制台上的,然而在线上环境中,我们需要把日志保存下来,以便出现问题之后追溯问题。而把日志保存下来,就叫做 持久化。

        日志持久化有两种方式:1、配置日志文件名(logging.file.name); 2、配置日志的存储目录(logging.file.path)

       1、 配置日志文件的路径和文件名:
        properties配置:

logging.file.name=logger/springboot.log

        yml配置:

logging:
  file:
    name: logger/springboot.log

         name后面可以跟绝对路径或者相对路径。运行项目后,日志内容保存在了对应的目录下,如图:

        2、配置日志文件的保存路径:

        properties配置:

logging.file.path=D:/temp

         yml配置:

logging:
  file:
    path: D:/temp

        运行项目,该路径下会多出一个日志文件,其中,logging.file.name和logging.file.path同时存在的话只会,只会生效一个,一logging.file.name为准

2.5.3 配置日志文件分割

        如果我们的日志都放在一个文件中,随着项目的运行,日志文件会越来越大,需要对日志文件进行分割日志框架也考虑到了这一点,所以如果不进行配置,就走自动配置,默认日志文件超过10M就进行分割;

         properties配置:

logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB

         yml配置:

logging:
  logback:
    rollingpolicy:
      max-file-size: 1KB
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i

2.5.4 配置日志格式

        打印日志的格式,也是支持配置的。支持控制台和日志文件分别设置。

配置项说明:
1、%clr(表达式){颜色}设置输入日志的颜色,支持也是有:blue、cyan、faint、green、magenta、red、yellow。

2、%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} 日期和时间--精确到毫秒

%d{}⽇期
${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX} 非空表达式,获取
系统属性 LOG_DATEFORMAT_PATTERN ,若属性 LOG_DATEFORMAT_PATTERN 不存在,则使用 -yyyy-MM-dd HH:mm:ss.SSSXXX 格式,系统属性可以从System.getProperty("LOG_DATEFORMAT_PATTERN") 获取

3、%5p 显示日志级别ERROR、WARN、INFO、DEBUG、TRACE。

4、%t 线程名。%c 类的全限定名。%M method。 %L 为行号。%thread线程名称。%m或者%msg显示输出消息。%n换行符

5、%5若字符长度小于5,则右边用空格填充。%-5若字符长度小于5,则左边空格填充。%.15若字符长度超过15,截去多余字符。%15.15若字符长度小于15,则右边用空格填充。若字符长度超过15,截去多余字符。

3. 更简单的日志输出

        每次都使用LoggerFactory.getLogger(xxx.class)很繁琐,且每个类都要添加一遍,lombok给我们提供了一种更简单的方式。

第一步:添加lombok框架支持。

第二步:使用@slf4j注解输出日志。

        代码如下所示:

package com.example.zxslzw2014_8_11;


import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/logger")
@RestController
public class LoggerController {

    @RequestMapping("/level")
    public String log() {
        log.info("打印日志");
        return "success";
    }
}

        浏览器访问之后会出现如下日志信息:

           lombok提供的@Slf4j会帮我们提供一个日志对象log,我们直接使用就可以。如图:

ps:本次的内容就到这里了,如果对你有所帮助的话,就请一键三连哦!!!
————————————————

电子签名:上嘉路

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐