Spring日志

日志

在学习java的过程中,排查一个错误,可以使用sout输出一个字符串到控制台来打印日志,从而发现和定位问题。

当项目足够复杂的时候,向控制台打印就不可取了。

日志的用处

  1. 系统监控:监控现在几乎是一个成熟系统的标配, 我们可以通过日志记录这个系统的运行状态, 每一个方法的响应时间, 响应状态等, 对数据进行分析, 设置不同的规则, 超过阈值时进行报警. 比如统计日志中关键字的数量,并在关键字数量达到一定条件时报警,这也是日志的常见需求之一
  2. 数据采集:数据采集是一个比较大的范围, 采集的数据可以作用在很多方面, 比如数据统计, 推荐排序等.
    1. 数据统计: 统计页面的浏览量(PV), 访客量(UV), 点击量等, 根据这些数据进行数据分析, 优化公司运营策略
    2. 推荐排序: 目前推荐排序应用在各个领域, 我们经常接触的各行各业很多也都涉及推荐排序, 比如购物, 广告, 新闻等领域. 数据采集是推荐排序工作中必须做的一环, 系统通过日志记录用户的浏览历史, 停留时长等, 算法人员通过分析这些数据, 训练模型, 给用户做推荐.
  1. 日志审计:随着互联网的发展,众多企业的关键业务越来越多的运行于网络之上. 网络安全越来越受到大家的关注, 系统安全也成为了项目中的一个重要环节, 安全审计也是系统中非常重要的部分. 国家的政策法规、行业标准等都明确对日志审计提出了要求. 通过系统日志分析,可以判断一些非法攻击, 非法调用,以及系统处理过程中的安全隐患.

日志使用

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

SpringBoot内置了日志框架Slf4j,可以直接在程序中调用Slf4j来输出日志。

在程序中得到日志对象

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

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

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoggerController {
    private static Logger logger = LoggerFactory.getLogger(LoggerController.clas
    @RequestMapping("/logger")
    public String logger(){
        logger.info("--------------要输出日志的内容----------------");
        return "打印日志";
    }	
}

日志框架介绍

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

门面模式(外观模式)

SLF4J是门面模式的典型应用

门面模式:提供了一个统一的接口,用来采访子系统中的一群接口,主要特征是定义了一个高层接口,让子系统更容易使用。

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

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

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

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


门面模式的实现

场景:回家, 我们会开各个屋的灯. 离开家时, 会关闭各个屋的灯如果家里设置一个总开关, 来控制整个屋的灯就会很方便.使用门面模式的实现

package com.example.booksmanagementsystem.test.facade;

public class Main {
    public static void main(String[] args) {
        HallLight hallLight = new HallLight();
        hallLight.on();

        LivingLight livingLight = new LivingLight();
        livingLight.on();

        DiningLight diningLight = new DiningLight();
        diningLight.on();
        
    }
}

package com.example.booksmanagementsystem.test.facade;

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


public class DiningLight implements Light{
    @Override
    public void on() {
        System.out.println("dining light on");
    }

    @Override
    public void off() {
        System.out.println("dining light off");
    }
}

package com.example.booksmanagementsystem.test.facade;

public class HallLight implements Light{
    @Override
    public void on() {
        System.out.println("HallLight on");
    }

    @Override
    public void off() {
        System.out.println("HallLight off");
    }
}

package com.example.booksmanagementsystem.test.facade;

public class LivingLight implements Light{
    @Override
    public void on() {
        System.out.println("LivingLight on");
    }

    @Override
    public void off() {
        System.out.println("LivingLight off");
    }
}

每次开灯都需要一个一个打开,那么能不能设计一个开关,可以打开全部的灯,

package com.example.booksmanagementsystem.test.facade;

public class FacadePattern {
    public void lightOn() {
        HallLight hallLight = new HallLight();
        hallLight.on();

        LivingLight livingLight = new LivingLight();
        livingLight.on();

        DiningLight diningLight = new DiningLight();
        diningLight.on();
    }

    public void lightOff() {
        HallLight hallLight = new HallLight();
        hallLight.off();

        LivingLight livingLight = new LivingLight();
        livingLight.off();

        DiningLight diningLight = new DiningLight();
        diningLight.off();
    }
}



package com.example.booksmanagementsystem.test.facade;

public class Main {
    public static void main(String[] args) {

        FacadePattern facadePattern = new FacadePattern();
        facadePattern.lightOn();
        facadePattern.lightOff();
    }
}

这就是一个简单的门面模式实现。

门面模式的优点

• 减少了系统的相互依赖. 实现了客户端与子系统的耦合关系, 这使得子系统的变化不会影响到调用它的客户端;

• 提高了灵活性, 简化了客户端对子系统的使用难度, 客户端无需关心子系统的具体实现方式, 而只需要和门面对象 交互即可.

• 提高了安全性. 可以灵活设定访问权限, 不在门面对象中开通方法, 就无法访问

SLF4J框架介绍

了解了门面模式,再来看SLF4J,SLF4J就是其他日志框架的门面,它提供了日志服务的统一API接口,并不涉及到具体的日志逻辑实现。


不引入日志门面

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

存在问题:

  1. 不同日志框架的API接口和配置文件不同, 如果多个日志框架共存, 那么不得不维护多套配置文件(这个配置文件是指用户自定义的配置文件).
  2. 如果要更换日志框架, 应用程序将不得不修改代码, 并且修改过程中可能会存在一些代码冲突.
  3. 如果引入的第三方框架, 使用了多套, 那就不得不维护多套配置.

引入日志门面

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

SLF4J 就是这个日志门面.

日志格式的说明

package com.example.booksmanagementsystem.controller;


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

import javax.annotation.PostConstruct;

@RestController
public class LoggerController {
    // 表示该方法在类实例化之后(通过构造函数创建对象之后)立即执行。
    private static Logger logger = LoggerFactory.getLogger(LoggerController.class);


    @PostConstruct
    public void print() {
        System.out.println("打印日志");
        logger.info("打印日志");
    }
}
  1. 时间,精确到毫秒
  2. 日志级别
  3. 进程ID
  4. 线程名
  5. Logger名(通常使用源代码的类名)
  6. 日志内容

日志级别

日志级别代表着日志信息对应问题的严重性, 为了更快的筛选符合目标的日志信息.从高到低依次为: FATAL、ERROR、WARN、INFO、DEBUG、TRACE

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

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

• WARN: 警告信息, 不影响使用, 但需要注意的问题

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

• DEBUG: 调试信息, 需要调试时候的关键信息打印.

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

日志级别通常和测试人员的Bug级别没有关系.日志级别是开发人员设置的, 用来给开发人员看的. 日志级别的正确设置, 也与开发人员的工作经验有关. 如果开发人员把error级别的日志设置成了info, 就很有可能会影响开发人员对项目运行情况的判断. 出现error级别的日志信息较多时, 可能也没有任何问题. 测试的bug级别更多是依据现象和影响范围来判断


package com.example.booksmanagementsystem.controller;


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

import javax.annotation.PostConstruct;

@RestController
public class LoggerController {

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


    @PostConstruct
    public void print() {
        logger.info("打印日志");
        logger.error("打印日志");
        logger.warn("打印日志");
        logger.debug("打印日志");
        logger.trace("打印日志");
    }
}

只打印了三个级别的日志。这与日志级别的配置有关, 日志的输出级别默认是 info级别, 所以只会打印大于等于此级别的日志, 也就是info, warn和error.

日志配置

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

yml配置

logging:
  level:
    root: debug

日志持久化

上面所描述的日志都是输出在控制台上的。把日志保存下来,叫做持久化,以便出现问题之后追溯问题。

日志持久化的两种方式

  1. 配置日志文件名
  2. 配置日志的存储目录

配置日志文件和路径的文件名

yml配置

# 设置日志文件的文件名
logging:
 file:
  name: logger/springboot.log

后面可以跟绝对路径或者相对路径


配置日志文件的保存路径

yml配置

logging:
  level:
    root: debug
  file:
    path: ./logs

logging.file.name 和 logging.file.path 两个都配置的情况下, 只生效其一, 以logging.file.name 为准.

配置日志文件分割

如果我们的日志都放在一个文件中, 随着项目的运行, 日志文件会越来越大, 需要对日志文件进行分割.

当然, 日志框架也帮我们考虑到了这一点, 所以如果不进行配置, 就走自动配置默认日志文件超过10M就进行分割

|--------------------------------------------------|-----------------|----------------------------------|
| 配置项 | 说明 | 默认值 |
| logging.logback.rollingpolicy.fil e-name-pattern | 日志分割后的文件名格式 | ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
| logging.logback.rollingpolicy.m ax-file-size | 日志文件超过这个大小就自动分割 | 10MB |

配置日志文件分割:

yml配置

logging:
  level:
    root: debug
  file:
    path: ./logs
  logback:
    rollingpolicy:
      max-file-size: 1KB
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
  1. 日志文件超过1KB就分割(设置1KB是为了更好展示. 企业开发通常设置为200M, 500M等, 此处没有明确标准)
  2. 分割后的日志文件名为: 日志名.日期.索引

在项目运行之后,就可以看到分割的日志了

配置日志格式

目前日志打印的格式是默认的

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

|-------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 配置项 | 说明 | 默认值 |
| logging.pattern.console | 控制台日志格式 | %clr(%d{{LOG_DATEFORMAT_PATTERN:-yyyy-MMdd'T'HH: mm:ss.SSSXXX}}){faint} %clr({LOG_LEVEL_PATTERN:- %5p}) %clr({PID:- }){magenta} %clr(---){faint} %clr(\[%15.15t\]) {faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n{LOG_EXCEPTION_CONVERSION_WORD:-%wEx} |
| logging.pattern.file | 日志文件的日志格式 | %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MMdd'T'HH: mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} {PID:- } --- \[%t\] %-40.40logger{39} : %m%n{LOG_EXCEPTION_CONVERSION_WORD:-%wEx} 配 |

配置项说明:

  1. %clr(表达式){颜色}设置输入日志的颜色

支持颜色有以下几种:

• blue

• cyan

• faint

• green

• magenta

• red

• yellow

  1. %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") 获取

  1. %5p 显示日志级别ERROR,MARN,INFO,DEBUG,TRACE.
  2. %t 线程名. %c 类的全限定名. %M method. %L 为行号. %thread 线程名称. %m 或者%msg 显示输出消息. %n 换行符
  3. %5 若字符长度小于5,则右边用空格填充. %-5 若字符长度小于5,则左边用空格填充. %.15 若字符长度超过15,截去多余字符. %15.15 若字符长度小于15,则右边用空格填充. 若字符长度超过15,截去多余字符

更多说明,参考这个链接

更简单的日志输出

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

  1. 添加 lombok 框架支持
  2. 使用 @slf4j 注解输出日志。

依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

package com.example.booksmanagementsystem.controller;


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

import javax.annotation.PostConstruct;


@Slf4j
@RestController
public class LoggerController {



    @PostConstruct
    public void print() {
        log.info("打印日志");
        log.error("打印日志");
        log.warn("打印日志");
        log.debug("打印日志");
    }
}
相关推荐
robin_suli26 分钟前
Java多线程八股(三)一>多线程环境使用哈希表和ArrayList
java·开发语言·多线程·哈希表
NiNg_1_23430 分钟前
Java中的多线程
java·开发语言
丁总学Java37 分钟前
nohup java -jar supporterSys.jar --spring.profiles.active=prod &
java·spring·jar
呆呆小雅39 分钟前
C# 结构体
android·java·c#
谢尔登39 分钟前
使用 Maven 创建 jar / war 项目
java·maven·jar
理想不理想v1 小时前
前端开发工程师需要学什么?
java·前端·vue.js·webpack·node.js
赶路人儿1 小时前
IntelliJ IDEA配置(mac版本)
java·macos·intellij-idea
jjw_zyfx1 小时前
docker 的各种操作
java·docker·eureka
生财1 小时前
获取字 short WORD 上指定的位是否有效
java·服务器·c#
hummhumm1 小时前
第 36 章 - Go语言 服务网格
java·运维·前端·后端·python·golang·java-ee