JAVA日志框架,这一篇就够了

目录

一、JUL日志框架(日常使用)

(一)、入门案例

(二)、日志的级别

(三)、日志配置

(四)、日志格式化

 (五)、配置文件

 自定义配置

在代码中加载自定义配置文件

二、日志门面

减少依赖冲突

日志桥接

三、log4j2的使用

四、项目中怎么使用日志

 不同级别的使用

ERROR

WARN

INFO

DEBUG

TRACE


主流日志框架

  • 日志实现(具体干活的):JUL(java util logging)、logback、log4j、log4j2

  • 日志门面(指定规则的):JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)

一、JUL日志框架

Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,使用方便,学习简单,能在小型应用中灵活使用。

在JUL中有以下组件:

  • Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger 通常时应用程序访问日志系统的入口程序。
  • Handler :负责将日志记录发送到适当的目的地,如控制台、文件或者网络目的地。下面是一些常见的 Handler 类型:

    ConsoleHandler - 将日志记录输出到标准错误流(通常是控制台)。
    FileHandler - 将日志记录输出到文件中。可以通过配置指定文件名模式,允许创建滚动文件。
    SocketHandler - 将日志记录发送到套接字服务器。
    StreamHandler - 将日志记录输出到任意的输出流。
    MemoryHandler - 将日志记录存储在内存缓冲区中,当满足某些条件时,将缓冲区中的日志记录推送到目标 Handler。

  • Formatters:Formatter 接口用于定义日志记录的格式。通过自定义 Formatter,可以控制日志输出的具体样式,比如日期格式、消息前缀等。也有一些内置的 Formatter 类。
  • Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将Level和Loggers,Handler 做关联以便于我们过滤消息。
  • Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。

(一)、入门案例

import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class LoggingExample {
    public static void main(String[] args) {
        // 获取 Logger 实例
        Logger logger = Logger.getLogger(LoggingExample.class.getName());

        try {
            // 1.创建 FileHandler 实例,指定日志文件的位置和是否追加日志
            FileHandler fileHandler = new FileHandler("app.log", true);

            // 2.设置日志格式化器
            fileHandler.setFormatter(new SimpleFormatter());

            // 3.创建 ConsoleHandler 实例
            ConsoleHandler consoleHandler = new ConsoleHandler();

            // 添加 Handler 到 Logger
            logger.addHandler(fileHandler);
            logger.addHandler(consoleHandler);

            // 4.设置 Logger 的日志级别
            logger.setLevel(java.util.logging.Level.ALL);

            // 记录一条日志消息
            logger.info("This is an info message.");
        } catch (IOException e) {
            logger.severe("Exception occurred while setting up logging handlers: " + e.getMessage());
        }
    }
}

(二)、日志的级别

java.util.logging.Level中定义日志的级别:

  1. SEVERE(最高值)
  2. WARNING
  3. INFO (默认级别)
  4. CONFIG
  5. FINE
  6. FINER
  7. FINEST(最低值)

再例如:我们查看tomcat的日志,能明显的看到不同级别的日志,其实tomcat默认使用的就是JUL:

还有两个特殊的级别:

  • OFF,可用来关闭日志记录。
  • ALL,启用所有消息的日志记录。

虽然我们测试了7个日志级别,

@Test
public void testLogger() {
    Logger logger = Logger.getLogger(LoggerTest.class.getName());
    logger.severe("severe");
    logger.warning("warning");
    logger.info("info");
    logger.config("config");
    logger.fine("fine");
    logger.finer("finer");
    logger.finest("finest");
}

我们发现能够打印的只有三行,这是为什么呢?

我们找一下这个文件,下图是jdk11的日志配置文件:

或者在jdk1.8中:

就可以看到系统默认在控制台打印的日志级别了,我们可以单独创建配置文件修改配置文件。

(三)、日志配置

import java.io.IOException;
import java.util.logging.*;

public class LoggingExample {
    public static void main(String[] args) throws IOException {
        // a.创建日志记录器对象
        Logger logger = Logger.getLogger(LoggingExample.class.getName());
        // 关闭系统默认配置
        logger.setUseParentHandlers(false);

        // b.创建handler对象
        FileHandler fileHandler = new FileHandler("jul.log");
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // c.创建formatter对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        // d.进行关联
        consoleHandler.setFormatter(simpleFormatter);
        fileHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);
        logger.addHandler(fileHandler);
        // e.设置日志级别
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);
        // 日志记录输出
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
}

文件、控制台全都输出全部级别 

(四)、日志格式化

上面我们用的Formatters是框架自带的,我们可以继承Formatter自己实现一个模版:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

public class CustomFormatter extends Formatter {

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Override
    public String format(LogRecord record) {
        StringBuilder builder = new StringBuilder(1000);
        String timeString = dateFormat.format(record.getMillis());
        builder.append(timeString)
               .append(" [")
               .append(record.getLevel())
               .append("] ")
               .append(record.getLoggerName())
               .append("方法:")
               .append(record.getSourceMethodName())
               .append(System.lineSeparator())
               .append(formatMessage(record))
               .append(System.lineSeparator());
        return builder.toString();
    }
}

把之前的

SimpleFormatter simpleFormatter = new SimpleFormatter();

修改为

CustomFormatter simpleFormatter = new CustomFormatter();

运行:

对比系统自带,去除了日期:

 (五)、配置文件

看一看jdk文件夹下的logging.properties文件

#处理器这里设置的是 ConsoleHandler,意味着所有的日志消息将会被发送到标准错误流
#(通常是控制台)
handlers= java.util.logging.ConsoleHandler

.level= INFO

#定义了 FileHandler 使用的文件名模式。%h 表示用户的主目录,%u 表示一个唯一的编号,
#用于防止文件名冲突。因此,这行配置的意思是在用户的主目录下创建名为 java0.log
#(如果存在冲突则可能是 java1.log 等)的日志文件。
java.util.logging.FileHandler.pattern = %h/java%u.log

#设置每个日志文件的最大字节数。
java.util.logging.FileHandler.limit = 50000

#指定要保留的日志文件的数量。当文件达到其大小限制时,它将被覆盖。如果设置为更大的数值,
#例如 3,则会保留最近的三个日志文件。
java.util.logging.FileHandler.count = 1

#日志格式化器
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

#为 ConsoleHandler 单独设置了日志记录级别。通过控制台输出的日志消息级别至少需要是 INFO。
java.util.logging.ConsoleHandler.level = INFO

#为特定的命名空间(这里是 com.xyz.foo)设置了日志记录级别。
#意味着只有级别为 SEVERE 的日志消息才会被记录下来。这可以用来控制特定组件的日志输出,
#比如只记录错误信息。
com.xyz.foo.level = SEVERE

 自定义配置

自定义的配置文件。假设我们将其命名为 logging.properties,用默认配置做更改

在代码中加载自定义配置文件

创建好配置文件后,你需要告诉 java.util.logging 使用这个自定义的配置文件。这通常在程序启动时完成,例如在主类的 main 方法中:

LogManager.getLogManager().readConfiguration(
                Main.class.getResourceAsStream("/YouLogging.properties")
            );

//之后正常使用就行
logger.info("Application started.");

到这里,你应该了解怎么在代码中使用和配置日志框架了,其实其他日志框架都差别不大。

通过简单的JUL框架,我们后面将学习主流框架搭配:log4j2(实现) + slf4j(门面)

二、日志门面

当我们的系统变的复杂的之后,难免会集成其他的系统,不同的系统之间可能会使用不同的日志系统。那么在一个系统中,我们的日志框架可能会出现多个,会出现混乱,而且随着时间的发展,可能会出现新的效率更高的日志系统,如果我们想切换代价会非常的大。如果我们的日志系统能和jdbc一样,有一套自己的规范,其他实现均按照规范去实现,就能很灵活的使用日志框架了。

​ 日志门面就是为了解决这个问题而出现的一种技术,日志门面是规范,其他的实现按照规范实现各自的日志框架即可,我们程序员基于日志门面编程即可。

减少依赖冲突

在一个复杂的项目中,可能会使用多个第三方库,而这些库可能依赖于不同的日志框架。如果不使用日志门面,就可能导致版本冲突或者冗余依赖的问题。通过引入日志门面,可以解决这类问题,因为所有组件都通过门面来记录日志,而不是直接依赖具体的日志实现。

日志桥接

......日志门面搭配日志实现可有多种搭配,主流为slf4+ log4j2,我就不去做分析了。只做slf4+ log4j2的教程。

三、log4j2的使用

篇幅过长,请移步:log4j2的使用-优快云博客

一、Log4j2入门

1.使用log4j-api做门面

2.使用slf4j做门面

二、自定义配置

三、异步日志(重头戏)

全局异步AsyncLogger

混合异步AsyncLogger

四、项目中怎么使用日志

使用参数化信息的方式而不是字符串拼接:

int id = 12345;
String symbol = "AAPL";
LOG.error("id:[{}],symbol:[{}]",id,symbol);

 不同级别的使用

  • ERROR

        影响到程序正常运行、当前请求正常运行的异常情况:

  1. 打开配置文件失败
  2. 所有第三方对接的异常(包括第三方返回错误码)

  3. 所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)

  4. 不应该出现的情况,比如要使用阿里云传图片,但是未响应

  5. 如果有Throwable信息,需要记录完成的堆栈信息:

log.error("获取用户[{}]的用户信息时出错",userName,e);

说明,如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理:

反例(不要这么做):

try{
    ....
}catch(Exception ex){
    String errorMessage=String.format("Error while reading information of user [%s]",userName);
    logger.error(errorMessage,ex);
    throw new UserServiceException(errorMessage,ex);
}

这样做会重复打印错误异常

  • WARN

不应该出现但是不影响程序、当前请求正常运行的异常情况:

  1. 有容错机制的时候出现的错误情况

  2. 找不到配置文件,但是系统能自动创建配置文件

  3. 即将接近临界值的时候,例如:缓存池占用达到警告线,业务异常的记录,比如:用户锁定异常

  • INFO

  1. Service方法中对于系统/业务状态的变更

  2. 主要逻辑中的分步骤:1,初始化什么 2、加载什么

  3. 外部接口部分

  4. 客户端请求参数(REST/WS)

  5. 调用第三方时的调用参数和调用结果

  6. 对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)。

  7. 调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)

说明 并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。反例(不要这么做):

public List listByBaseType(Integer baseTypeId) {
    log.info("开始查询基地");
    BaseExample ex=new BaseExample();
    BaseExample.Criteria ctr = ex.createCriteria();
    ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
    Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
    log.info("查询基地结束");
    return baseRepository.selectByExample(ex);
}
  • DEBUG

可以填写所有的想知道的相关信息(但不代表可以随便写,debug信息要有意义,最好有相关参数)

生产环境需要关闭DEBUG信息

如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启。

  • TRACE

特别详细的系统运行完成信息,业务代码中,不要使用(除非有特殊用意,否则请使用DEBUG级别替代)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值