最简单版本的logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="LOG_DIR" value="./logs"/>
<!--带高亮显示-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%highlight(%-5level)] [%thread] %logger{30}.%M\(%F:%L\) - %msg%n</pattern>
</encoder>
</appender>
<!--这个会被异步打印里面调用-->
<appender name="ROLLFILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/game.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/game.%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>[%-5level][%d{HH:mm:ss.SSS}][%thread][%logger][%L] - %msg%n</pattern>
</encoder>
</appender>
<!--异步打印-->
<appender name="ASYNC_ROLLFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="ROLLFILE"/>
<queueSize>1024</queueSize>
<maxFlushTime>3000</maxFlushTime>
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC_ROLLFILE"/>
</root>
</configuration>
=====================
SpringBoot 5.2关服钩子日志打印不出来。 这是因为关服时,有自己的钩子导致日志先于我们的钩子关闭,我们禁用掉,使用自己的钩子。修改: application.properties
logging.register-shutdown-hook=false
==============1.基础知识================
1)基于门面模式的实现。也就是slf4j这种提供接口,logback提供实现。 而且自动查找logback.xml。
2)先配置好输出什么:如 线程、行数、时间、日志级别等。。
再配置好以什么方式输出:如 控制台、文件。 基本上logback的设计都是基于反射,配置的xml,其实就是调用set方法进行设置。而且设置的有实现类。
3)可以自定义输出格式:如哪个包下,以什么样的日志级别输出。
4)支持文件的切割,比如:以日期来作为文件命名,超过多大后,进行按照0、1、2、3这样子切割。
5)支持压缩,文件过大,可以压缩为:xxx.gz
6)支持异步输出,不阻塞打印线程。 原理是基于BlockingQueue。
===============2.logback的使用================
pom.xml
<!--logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
配置例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
配置集中管理属性
我们可以直接改属性的 value 值
格式:${name}
-->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>
<!--
日志输出格式:
%-5level
%d{yyyy-MM-dd HH:mm:ss.SSS}日期
%c类的完整名称
%M为method
%L为行号
%thread线程名称
%m或者%msg为信息
%n换行
-->
<!--定义日志文件保存路径属性-->
<property name="log_dir" value="/logs"></property>
<!--控制台日志输出的 appender-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--控制输出流对象 默认 System.out 改为 System.err-->
<target>System.err</target>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--日志文件输出的 appender-->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/logback.log</file>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--html 格式日志文件输出 appender-->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/logback.html</file>
<!--html 消息格式配置-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern>
</layout>
</encoder>
</appender>
<!--日志拆分和归档压缩的 appender 对象-->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件保存路径-->
<file>${log_dir}/roll_logback.log</file>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--指定拆分规则-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--按照时间和压缩格式声明拆分的文件名-->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--按照文件大小拆分-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!--日志级别过滤器-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--日志过滤规则-->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--异步日志-->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<!--指定某个具体的 appender-->
<appender-ref ref="rollFile"/>
</appender>
<!--root logger 配置-->
<root level="ALL">
<appender-ref ref="console"/>
<appender-ref ref="async"/>
</root>
<!--自定义 looger 对象
additivity="false" 自定义 logger 对象是否继承 rootLogger
-->
<logger name="com.itheima" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
===============3.zfoo下的logback================
zfoo项目中的调试环境下的logback.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<property name="LOG_HOME" value="log/"/>
<property name="PATTERN_FILE"
value="%d{yyyy-MM-dd HH:mm:ss} [%-5level] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<property name="PATTERN_CONSOLE"
value="%d{yyyy-MM-dd HH:mm:ss} [%highlight(%-5level)] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<!-- 负责写日志,控制台日志,会打印所有的包的所有级别日志 -->
<appender name="zfoo_console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_CONSOLE}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- debug日志,只有一个文件,只收集debug级别日志,每次启动会覆盖以前的debug日志 -->
<appender name="zfoo_debug" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/debug.log</file>
<!-- append: true,日志被追加到文件结尾; false,清空现存文件;默认是true -->
<append>false</append>
<encoder>
<pattern>${PATTERN_FILE}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- info,warn,error级别的日志都会添加在info.log日志中 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="zfoo_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 30 days worth of history, but at most 40GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>40GB</totalSizeCap>
</rollingPolicy>
<encoder>
<Pattern>${PATTERN_FILE}</Pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="zfoo_async_info" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="zfoo_info"/>
</appender>
<!-- 只收集error级别的日志 -->
<appender name="zfoo_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<Pattern>${PATTERN_FILE}</Pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--step1:指定想要输出的类型-->
<!-- 根logger -->
<root level="info">
<!--控制台-->
<appender-ref ref="zfoo_console"/>
<!-- <appender-ref ref="zfoo_debug"/>-->
<appender-ref ref="zfoo_async_info"/>
<!-- <appender-ref ref="zfoo_error"/>-->
</root>
<logger name="ch.qos.logback" level="info"/>
<logger name="org.springframework" level="info"/>
<logger name="io.netty" level="info"/>
</configuration>
思考:
必然线上使用的话,是使用基于文件大小 和 文件拆分的方式。 比如:知道大概啥时出的问题,这样子快速定位这一天的日志。 不可能1天几个G的输出,肯定要缩小问题的范围。
===============4.自己对于logback的思考和各个标签================
====20220725(实战:真正理解logger、root、CONSOLE中的level配置)=====
LogServer.java
package com.example.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogServer {
public static Logger GAME = LoggerFactory.getLogger("GAME");
public static Logger CONF = LoggerFactory.getLogger("CONF");
}
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<property name="LOG_HOME" value="log/"/>
<property name="PATTERN_CONSOLE"
value="%d{yyyy-MM-dd HH:mm:ss} [%highlight(%-5level)] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<pattern>${PATTERN_CONSOLE}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="GAME" level="error"/>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
Application.java
package com.example;
import com.example.log.LogServer;
public class Application {
public static void main(String[] args) {
LogServer.GAME.info("game");
LogServer.CONF.info("conf");
}
}
/*
2022-07-25 14:53:33 [INFO ] [main] CONF.main(Application.java:8) - conf
*/
原因:
由于CONF没有再logback中指定,因此是:采用root的,是info级别,info >= CONSOLE的,因此输出conf。
而 GAME,指定的是error级别,输出时采用的是 info,info < error,因此轮不到CONSOLE了,不会输出。
总结:在使用的地方如写成:LogServer.GAME.xx("...") 这里的xx:
1.先要突破 logback中的级别限制(先看指定的这个模块配置没有【如:<logger name="GAME" level="..."/>,先看这里的level是啥】,没配置,则采用root的,因为我们默认是继承的root的日志级别)。
2.其次,再突破CONSOLE中的级别限制,才会输出出来。
===============5.打印代码当前堆栈================
-----------------------------使用new Throwable() 打印出当前堆栈
package org.example.testPredicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Main {
private static Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// Predicate<Long> isLess = age -> age < 18;
//
//
// System.out.println(isLess.test(3L));
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
int f = iterator.next();
if (f < 3) {
iterator.remove();
}
call(f);
System.out.println();
}
System.out.println(map);
}
public static void call(int f){
LOG.error("f={}", f, new Throwable());
}
}
/*
2023-04-23 18:09:47 [ERROR] [main] org.example.testPredicate.Main.call(Main.java:43) - f=1
java.lang.Throwable: null
at org.example.testPredicate.Main.call(Main.java:43)
at org.example.testPredicate.Main.main(Main.java:34)
2023-04-23 18:09:47 [ERROR] [main] org.example.testPredicate.Main.call(Main.java:43) - f=2
java.lang.Throwable: null
at org.example.testPredicate.Main.call(Main.java:43)
at org.example.testPredicate.Main.main(Main.java:34)
2023-04-23 18:09:47 [ERROR] [main] org.example.testPredicate.Main.call(Main.java:43) - f=3
java.lang.Throwable: null
at org.example.testPredicate.Main.call(Main.java:43)
at org.example.testPredicate.Main.main(Main.java:34)
2023-04-23 18:09:47 [ERROR] [main] org.example.testPredicate.Main.call(Main.java:43) - f=4
java.lang.Throwable: null
at org.example.testPredicate.Main.call(Main.java:43)
at org.example.testPredicate.Main.main(Main.java:34)
*/
哪个方法调用的,在哪一行出现的这个问题都轻松看出来。
==============6.Exception的打印===================
Exception的打印方式
有些未知的异常我们需要打印,则这样子就可以了