初始JavaEE篇 —— SpringBoot 日志

发布于:2025-03-11 ⋅ 阅读:(37) ⋅ 点赞:(0)

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

目录

日志简介

日志使用

日志框架

门面模式(外观模式)

日志格式的介绍

日志级别的介绍

日志级别的使用

配置日志级别

日志持久化

日志文件分割

更加简单的日志输出


日志简介

日志是一种记录事件、操作或信息的记录方式。我们在JavaSE阶段学习的异常也算是一种日志的记录,如果程序某个地方出现了啥问题,就可以通过try-catch结构抛出异常信息,这样就能快速定位问题,进一步分析如何解决该问题。又或者执行到某个具体操作时,使用 System.out.println 来打印信息到控制台,这样就能明确知道程序是在执行啥操作,或者该用户是在执行啥操作。但仅限于上述方式是很不现实的,不能很好的满足我们的需求,因此就出现了日志框架。

日志使用

SpringBoot项目在启动时,默认就会输出日志信息到控制台,如下图所示:

上述图中的日志,使用 System.out.println 并不能实现上述效果,而是需要特定的日志框架去实现,这里我们采用的 slf4j,下面就使用该框架来打印类似的日志:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping("/print")
    public void printLog() {
        // 通过slf4j包内的Logger类的工厂方法获取logger对象
        Logger logger = LoggerFactory.getLogger(HelloController.class);
        // 通过logger对象将日志输出到控制台上
        logger.info("使用slf4j日志框架,将日志输出到控制台上");
    }
}

注意:

1、我们在导包的时候,一定要注意别导错了,得导入 slf4j 包:

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

当传入Class 对象时,内部自动调用Class.getName方法获取类全限定名作为name,便于在日志中直接显示类;当直接传入字符串时,name 将作为自定义日志标识。待会学习日志格式的时候,再来具体观察效果。

日志框架

Java 日志系统通常由日志门面(日志接口)和日志框架(具体实现)组成。日志门面可以理解为一个接口,日志框架可以理解为是对接口的一个具体实现。上述两者之间的关系,就可以理解为Java中的接口类和具体实现类之间的关系。

slf4j 是一个日志门面,并不是一个具体的日志实现。

门面模式(外观模式)

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

举个简单的例子,以前计算机最开始问世的时候,如果想要实现某个功能的话,还得在纸带上书写机器语言,然后给计算机读取,计算机再根据读取到的指令才知道接下来要干啥。但是现在不一样了,我们只需要通过图形化界面来操作具体的步骤,计算机就能知道接下来要干嘛。

在上述过程中,图形化界面就相当于是一个门面模式的具体实现,所有的计算机操作,都通过图形化界面操作来实现。

在门面模式中,主要包含两种角色:外观角色(门面角色),是系统对外的统一接口;子系统角色,可以同时有一个或者多个子系统角色的存在。对于子系统角色而言,它本身是不知道有外观角色的存在的,就像上述计算操作,无论是通过机器语言来实现该操作,还是通过图形化界面来操作,本身对于计算这个操作来说,两者都是使用方,即客户端。

接下来,我们就来编写一个具体的门面模式的代码:

interface Graphic {
    // 计算
    void calc();

    // 刷视频
    void watchVideo();

    // 打游戏
    void playGame();
}


class Option implements Graphic {

    @Override
    public void calc() {
        try {
            System.out.println("开始计算");
            System.out.println("正在计算...");
            Thread.sleep(5000); // 休眠五秒钟
            System.out.println("计算完毕");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void watchVideo() {
        try {
            System.out.println("开始刷视频");
            System.out.println("正在刷视频...");
            Thread.sleep(5000);
            System.out.println("视频刷完了");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void playGame() {
        try {
            System.out.println("开始玩游戏");
            System.out.println("正在玩游戏...");
            Thread.sleep(5000);
            System.out.println("游戏结束");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}


class User {

    private Graphic graphic = new Option();

    public User() {
        System.out.println("用户张三买了一台电脑,并已经登录开始使用...");
    }

    public void runComputer() {
        graphic.calc();
        graphic.playGame();
        graphic.watchVideo();
    }

    public static void main(String[] args) {
        User user = new User();
        user.runComputer();
    }
}

门面模式的优点:

 1、统一接口封装:通过为子系统提供统一的高层次入口,将复杂的内部调用逻辑隐藏,客户端只需要与门面对象交互;

2、减少客户端依赖对象数量:客户端只需关注门面对象,而非子系统;

3、实现了解耦:门面模式隔离了客户端与子系统的直接依赖,子系统的内部修改不会影响客户端代码,增强系统的可维护性和扩展;

4、访问控制与安全性增强:门面对象可限制客户端对子系统的访问权限,仅暴露必要的接口,防止非法的操作。

日志格式的介绍

接下来,我们详细学习日志级别。

日志级别的介绍

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

如果没有日志级别会怎么样呢?假设今天是放假,正常情况下没有Error级别以上的日志信息,就不需要我们来处理或者说可以在工作日内处理,但如果不存在日志级别,老板发现这里打印了许多日志,误以为是代码哪里出现了问题,赶紧把你从休假的状态叫回来,结果你发现这只是一个小小的警告信息,想一想这时你的心里状态是咋样的,肯定是双眼冒火的看着老板。又或者你是老板在处理事情时,下面的员工不管是重要的事情,还是屁大点事都来通知你,让你去做决定,你肯定忙不停。因此日志级别对于我们程序员来说是非常重要的。

日志级别从高到低依次是:FATAL、ERROR、WARN、INFO、DEBUG、TRACE。

FATAL:致命信息,表示需要立即被处理的系统级错误。
ERROR:错误信息,级别较高的错误日志信息,但仍然不影响系统的继续运行。
WARN:警告信息,不影响使用,但需要注意的问题
INFO:普通信息,用于记录应用程序正常运行时的一些信息,例如系统启动完成、请求处理完成等。
DEBUG:调试信息,需要调试时候的关键信息打印.
TRACE:追踪信息,比DEBUG更细粒度的信息事件(除非有特殊用意,否则请使用DEBUG级别替代)

日志级别的使用

针对不同的日志级别,logger对象都提供了打印方法:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping("/printByRank")
    public void printLogByRank() {
        // 传入的logger对象的名字
//        Logger logger = LoggerFactory.getLogger("logger");
        Logger logger = LoggerFactory.getLogger(HelloController.class);
        logger.error("这是ERROR级别的日志");
        logger.warn("这是WARN级别的日志");
        logger.info("这是INFO级别的日志");
        logger.debug("这是DEBUG级别的日志");
        logger.trace("这是TRACE级别的日志");
    }
}

但从上述图中控制台的输出结果来看,只打印了ERROR、WARN、INFO级别的日志,而对于DEBUG 和 TRACE 级别的日志并未打印,这是因为SpringBoot项目的默认的日志级别配置是输出INFO级别以上的日志,对于级别更低的日志并不会输出。

注意:SpringBoot项目默认的日志框架是logback,而logback中并没有FATAL级别。

配置日志级别

# 配置日志级别
logging:
  level:
    root: trace

在配置文件中,加上上述配置之后,再来启动程序,观察控制台输出的日志:

日志持久化

上述的所有日志都是输出到控制台上的,但是在实际生产环境中,我们需要将日志保存到本地,以便于后续排查与追溯问题。日志持久化的方式有两种:1、配置日志文件名;2、配置日志的存储目录。同样,日志持久化也是需要修改配置文件的。

第一种方式就是指定日志文件存放的目录,以及对应的文件名

# 配置日志持久化
logging:
  file:
    name: logger.log # 注意文件的后缀名

当我们再次运行程序时,就会在当前项目的路径生成一个文本文件 

除了自动生成到当前项目路径下之外,还可以手动指定创建到哪个磁盘路径下。

我们再来看第二种方式,其是指定日志文件存储的路径,不包括具体的文件名。

# 配置日志持久化
logging:
  file:
    path: D:\logger\ # 只需要写上路径即可

# 生成的日志文件名,默认是 spring.log

注意:

1、虽然在路径的最后加不加 "\" 的效果都是一样的,但是加上之后,可读性更好。

2、当name和path同时存在时,以name为主。

3、使用name配置日志持久化时,如果在最后不加上文件名的话,配置就不会生效。

日志文件分割

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

logging:
# 配置日志持久化
  file:
    path: logger/111/
    name: logger/222/logger.log
  logback:
    rollingpolicy:
      max-file-size: 10KB # 按照10KB大小进行分割
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i # 子文件的格式

我们可以从上述图中文件的大小来看,其并不是按照文件大小进行严格划分的,当该行的文件已经超过10KB的话,会将该行的所有日志全部存储完之后,再去新开一个文件来存储,而不是说一满10KB的话,就新开一个文件来存储。 

注意:默认当日志文件的大小超过10M时,就会自动进行分割。

更加简单的日志输出

上述所有的日志输出都需要通过日志工厂来获取对应的方法,如果有多个类需要输出日志的话,那么就需要将上述代码重复写,那有没有注解的方式呢?肯定是有的,通过Lombok依赖即可添加对应的注解来输出日志。

@Slf4j // 提供了一个logger对象,直接使用即可
@RestController
@RequestMapping("/hello3")
public class HelloController3 {
    @RequestMapping("/print")
    public void printLog() {
        log.info("1111");
    }
}

好啦!本期 初始JavaEE篇 —— SpringBoot 日志 的学习之旅 就到此结束啦!我们下一期再一起学习吧!