在开发中,我们经常使用 System.out.println()
来打印一些信息,但是这样不好,因为大量的使用 System.out
会增加资源的消耗,而且部署在Linux上时只能通过日志来查看输出。我们实际项目中使用的是 slf4j
的 logback
来输出日志,slf4j+logback
也是springboot的默认日志框架,当然logback
也可以换成性能更好的log4j2
框架。
下面是几个日志概念介绍
SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。
slf4j要求我们只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。
首先需要添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
日志的使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
//logger.info("test");
}
还有一种方法通过注解方式,需要安装好Lombok插件
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test {
// log.info("test");
}
logback官方手册
logback中文文档
目前logback默认与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能。
application.yml
文件是 Spring Boot 中唯一一个需要配置的文件,一开始创建工程的时候是 application.properties
文件(注:同一优先级位置同时有 application.properties
和 application.yml
,那么 application.yml
里的属性会覆盖 application.properties
里的属性)
这里我们对 application.yml
文件中对日志的配置:
logging:
config: classpath:logback.xml
level:
com.test: trace
logging.config
是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml
文件,关于日志的相关配置信息,都放在 logback.xml
文件中了。logging.level
是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.test
包下的所有日志输出级别为 trace,会打印出所以debug以上信息。在生产环境上,将这个日志级别再设置成 error 级别即可。当然在 application.yml 中还可以显示定义其他例如logging.pattern.console
等配置信息。
常用的日志级别按照从高到低依次为:ERROR、WARN、INFO、DEBUG、TRACE 、 ALL。
对于classpath路径,src 路径下的文件 在编译后都会放到 WEB-INF/classes 路径下。默认classpath 就是指这里。用maven构建 项目时,resources 目录就是默认的classpath
scan
当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
scanPeriod
设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟
debug
当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false
<configuration scan="true" scanPeriod="60 seconds" debug="false">
configuration>
logger用来设置某一个包或者具体的某一个类的日志打印级别、以及指定 。 仅有一个name属性,一个可选的level和一个可选的addtivity属性
name
用来指定受此 loger 约束的某一个包或者具体的某一个类
level
用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特殊值 INHERITED 或者同义词 NULL ,代表强制执行上级的级别。如果未设置此属性,那么当前 logger 将会继承上级的日志级别
addtivity
是否向上级 logger 传递打印信息。默认是true
<logger name="org.mybatis" level="INFO"/>
appender是
的子节点,是负责写日志的组件。该标签负责以适当的格式将日志记录事件输出到适当的输出设备
name
指定appender名称
class
指定appender的全限定名
<appender name="ERROR-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
appender>
root为根元素,只有一个level属性。
可以包含零个或多个
元素
level
设置日志级别。
<root level="INFO">
<appender-ref ref="ASYNC-INFO"/>
root>
RollingFileAppender,滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件,下面是其子标签
encoder
对日志进行格式化target
rollingPolicy
当发生滚动时,决定RollingFileAppender 的行为,涉及文件移动和重命名(设置滚动策略)
class
为 rollingPolicy 的属性,设置 日志的滚动策略,最常用的滚动策略为TimeBasedRollingPolicy ,它根据时间来制定滚动策略,既负责滚动也负责出发滚动fileNamePattern
为一个必要的子节点,设置日志文件的名称 。一般包含文件名及“%d”转换符,“%d”可以包含一个java.text.SimpleDateFormat
指定的时间格式,如:%d{yyyy-MM}。如果直接使用 %d,默认格式是 yyyy-MM-dd。maxHistory
可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。\
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.home}/%d{yyyy-MM,aux}/soi-%d{yyyy-MM-dd}.error.logfileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
filter为日志过滤器。执行一个过滤器会有返回一个枚举值,即 DENY,NEUTRAL,ACCEPT 其中之一,这也是appender下的子节点,其中class
为 filter
设置指定的过滤器 ,下面列举几个常见的 过滤器
LevelFilter
级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUGlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
ThresholdFilter
临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARNlevel>
filter>
在上面 application.yml
文件中,我们指定了日志配置文件 logback.xml
,logback.xml
文件中主要用来做日志的相关配置。在 logback.xml
中,我们可以定义日志输出的格式、路径、控制台输出格式、文件大小、保存时长等等。下面来分析一下:
<configuration>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<property name="FILE_PATH" value="D:/logs/demo.%d{yyyy-MM-dd}.%i.log" />
configuration>
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%-5level :级别从左显示5个字符宽度
%logger{50}表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
对于“FILE_PATH” 文件路径,日志都会存储在该路径下。%i
表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。
如果想在控制台换成彩色打印输出,可以在
里添加如下配置
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){green} %clr(${LOG_LEVEL_PATTERN:-%5p}){red} %clr(${PID:-}){magenta} %clr(---){blue} %clr([%30.30t]){yellow} %clr(%-40.40logger{39}){cyan} %clr(:){blue} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
configuration>
使用
节点设置个控制台输出(class="ch.qos.logback.core.ConsoleAppender"
)的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${}
引用进来即可。
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${FILE_PATH}fileNamePattern>
<maxHistory>30maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>30MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
configuration>
使用
定义一个名为 “FILE” 的文件配置,主要是配置日志文件保存的时间、单个日志文件存储的大小、以及文件保存的路径和日志的输出格式。
<configuration>
<logger name="com.test" level="INFO" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
root>
configuration>
有了上面那些定义后,最后我们使用
来定义一下项目中默认的日志输出级别,这里定义级别为 INFO,然后针对 INFO 级别的日志,使用
引用上面定义好的控制台日志输出和日志文件的参数。这样 logback.xml 文件中的配置就设置完了。
这里演示另一种日志文件创建方法,更加抽象。首先需要在resources下创建logback文件夹,并存放以下三个文件,也可以都写在logback.xml中
logback.properties
用于定义变量appender.xml
用于定义Appender对象logger.xml
用于定义loggerlogback.xml
用于引入上面三个
<configuration debug="true">
<contextName>logback_studycontextName>
<property resource="logback/logback.properties" />
<include resource="logback/appender.xml" />
<include resource="logback/logger.xml" />
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file" />
root>
configuration>
# 定义日志存放的目录
# 项目名称
app_name=logbackStudy
# 日志存放的根路径
file_root_dir=D:\\
# 当前项目日志存放的根路径
file_root_app_dir=${file_root_dir}\\${app_name}
# 历史日志文件存放的根路径
file_root_app_history_dir=${file_root_dir}\\${app_name}\\history
# 日志格式
# 默认的日志格式
default_pattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} %n
# console的日志格式
console_pattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %logger %n[%thread] %7level: %message %n
# fileAppender的日志格式
file_pattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %logger %n[%thread] %7level: %message %n
<included>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console_pattern}pattern>
encoder>
<target>System.outtarget>
<withJansi>falsewithJansi>
appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${file_root_app_dir}/${app_name}_log.txtfile>
<append>trueappend>
<immediateFlush>trueimmediateFlush>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${file_root_app_history_dir}/%d{yyyy/MM,aux}/%d{yyyy-MM-dd}_log%i.zipfileNamePattern>
<maxFileSize>5MBmaxFileSize>
<maxHistory>365maxHistory>
<totalSizeCap>20GBtotalSizeCap>
rollingPolicy>
<encoder>
<pattern>${file_pattern}pattern>
encoder>
appender>
included>
<included>
<logger name="com.rain.test" level="debug" additivity="false">
<appender-ref ref="console" />
<appender-ref ref="file" />
logger>
included>
log4j2官方文档:https://logging.apache.org/log4j/2.x/index.html
Apache Log4j 2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了显着改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些固有问题。
Log4j2中日志有六个级别(level),当日志级别设置为某个值的时候,低于它的日志信息将不会被记录,只有高于设置的级别的信息会被记录。
Configuration
1、Configuration
Configuration为根节点,有status和monitorInterval等多个属性。
status
的值有trace、debug、info、warn、error和 fatal,用于控制log4j2日志框架本身的日志级别,如果将status设置为较低的级别(如trace)就会看到很多关于log4j2本身的日志,如加载log4j2配置文件的路径等信息,一般不用设置。monitorInterval
,含义是每隔多少秒重新读取配置文件,可以不重启应用的情况下修改配置。name
:配置名称strict
: 是否使用严格的XML格式,推荐使用,规范开发者的配置编写。2、properties
properties
:配置文件全局的参数变量,用于减少自定义配置信息的重复编码,该配置是可选的,例如定义日志的存放位置D:/logs3、Appenders
Appenders是输出源,用于定义日志输出的地方,log4j2支持的输出源有很多,有控制台Console、文件File、RollingRandomAccessFile、MongoDB、Flume等。
Console
:控制台输出源是将日志打印到控制台上,开发的时候一般都会配置,以便调试
File
:文件输出源,用于将日志写入到指定的文件,需要配置输入到哪个位置(例如:D:/logs/mylog.log)
RollingRandomAccessFile
: 该输出源也是写入到文件,不同的是比File更加强大,可以指定当文件达到一定大小(如20MB)时,另起一个文件继续写入日志,另起一个文件就涉及到新文件的名字命名规则,因此需要配置文件命名规则 这种方式更加实用,因为你不可能一直往一个文件中写,如果一直写,文件过大,打开就会卡死,也不便于查找日志。
fileName
:指定当前日志文件的位置和文件名称filePattern
:指定当发生Rolling时,文件的转移和重命名规则SizeBasedTriggeringPolicy
:指定当文件体积大于size指定的值时,触发RollingDefaultRolloverStrategy
:指定最多保存的文件个数TimeBasedTriggeringPolicy
:这个配置需要和filePattern结合使用。假如filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i,最小的时间粒度是mm,即分钟,若TimeBasedTriggeringPolicy指定的interval是1,结合起来就是每1分钟生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件。RollingFile
:同上,与RollingRandomAccessFile不同的是,RollingRandomAccessFile默认日志文件写入策略为异步刷盘,RollingRandomAccessFile会将日志信息先写入到缓冲区,然后缓冲区满后刷到磁盘,并清空缓冲区,默认缓冲区的大小在8-256kb,具体大小需要自己设置。
NoSql
:MongoDb, 输出到MongDb数据库中
Flume
:输出到Apache Flume(Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。)
Async
:异步,需要通过AppenderRef来指定要对哪种输出源进行异步(一般用于配置RollingRandomAccessFile)
PatternLayout
:控制台或文件输出源(Console、File、RollingRandomAccessFile)都必须包含一个PatternLayout节点,用于指定输出文件的格式(如日志输出的时间、文件、方法、行数等格式),例如pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n",各标记符详细含义如下:
%d 输出时间
%d{HH:mm:ss.SSS} 表示输出到毫秒的时间
%t 输出当前线程名称
%p 输出日志级别
%-5p -5表示左对齐并且固定输出5个字符,如果不足在右边补0
%c 日志消息所在类名
%m 消息内容
%n 换行
%F 输出所在的类文件名,如Log4j2Test.java
%L 输出行号
%M 输出所在方法名
%l 输出语句所在的行数, 包括类名、方法名、文件名、行数
%logger 输出logger名称,如果没有名称,就不输出
4、Loggers
日志器分根日志器Root和自定义日志器,当根据日志名字获取不到指定的日志器时就使用Root作为默认的日志器,自定义时需要指定每个Logger的名称name(对于命名可以以包名作为日志的名字,不同的包配置不同的级别等),日志级别level,相加性additivity(是否继承下面配置的日志器), 对于一般的日志器(如Console、File、RollingRandomAccessFile)一般需要配置一个或多个输出源AppenderRef。
每个logger可以指定一个level(TRACE, DEBUG, INFO, WARN, ERROR, ALL or OFF),不指定时level默认为ERROR。additivity指定是否同时输出log到父类的appender,缺省为true。
首先 ,引入log4j2的starter,将logback.xml改为log4j2的配置文件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
然后需要排除spring自带的日志框架logback依赖,如果项目中只引入了web的starter,则可以在web starter中排除掉logging
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
如果项目中引入的不止web starter,此时我们需要在spring-boot-starter中排除logging的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
若自定义文件名,则需要在application.yml
进行修改,配置文件也logback也有区别
logging:
config: classpath:log4j2_dev.xml
<configuration monitorInterval="30">
<Properties>
<property name="LOG_CONSOLE" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} [%t] %highlight{%c{1.}.%M(%L)}: %msg%n" />
<Property name="LOG_HOME" value="logs"/>
<property name="LOG_FILE" value="%d{yyyy-MM-dd HH:mm:ss.SSS} {%-5level} [%t] {%c{1.}.%M(%L)}: %msg%n" />
<property name="FILE_PATH" value="lamp.%d{yyyy-MM-dd}.%i.log" />
Properties>
<Appenders>
<console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_CONSOLE}"/>
console>
<RollingFile name="FILE" fileName="${LOG_HOME}/log.log" filePattern="${LOG_HOME}/${FILE_PATH}">
<PatternLayout pattern="${LOG_FILE}"/>
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
<SizeBasedTriggeringPolicy size="20 MB" />
Policies>
<DefaultRolloverStrategy>
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="${LOG_HOME}/*.log">
<IfLastModified age="30d"/>
IfFileName>
Delete>
DefaultRolloverStrategy>
RollingFile>
Appenders>
<Loggers>
<logger name="org.springframework" level="INFO">logger>
<logger name="org.mybatis" level="INFO">logger>
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
root>
Loggers>
configuration>
这里可以和之前logback那样分文件,这里我全部放在一起了
<configuration status="warn" monitorInterval="30">
<properties>
<property name="MAIL_LEVEL">ERRORproperty>
<property name="PROJECT_NAME">prod-projectproperty>
<property name="LOG_BACK_HOME">./${PROJECT_NAME}-logproperty>
properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} [%t] %highlight{%l}: %msg%n"/>
console>
<RollingFile name="RollingFileInfo" fileName="${LOG_BACK_HOME}/info.log"
filePattern="${LOG_BACK_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="WARN" onMatch="NEUTRAL" onMismatch="DENY"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
Filters>
<PatternLayout pattern="[%d{yyyy/MM/dd HH:mm:ss,SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10 MB"/>
Policies>
RollingFile>
<RollingFile name="RollingFileWarn" fileName="${LOG_BACK_HOME}/warn.log"
filePattern="${LOG_BACK_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="20"/>
RollingFile>
<RollingFile name="RollingFileError" fileName="${LOG_BACK_HOME}/error.log"
filePattern="${LOG_BACK_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="ERROR"/>
<PatternLayout pattern="[%d{yyyy/MM/dd HH:mm:ss,SSS}] [%t] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
Policies>
<DefaultRolloverStrategy max="20"/>
RollingFile>
appenders>
<loggers>
<logger name="org.springframework" level="INFO">logger>
<logger name="org.mybatis" level="INFO">logger>
<logger name="org.springframework.core" level="info" />
<logger name="org.springframework.beans" level="info" />
<logger name="org.springframework.context" level="info" />
<logger name="org.springframework.web" level="info" />
<logger name="org.jboss.netty" level="warn" />
<logger name="org.apache.http" level="warn" />
<logger name="org.hibernate" level="info" />
<logger name="com.alibaba.druid" level="info" />
<logger name="org.thymeleaf" level="info" />
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
root>
loggers>
configuration>
在Log4j 2.10以前的版本,pattern中配置%highlight属性是可以正常打印彩色日志的。但是是更新到2.10版本以后,控制台中就无法显示彩色日志了,各种级别的日志混杂在一起,难以阅读。Log4j2默认关闭了Jansi(一个支持输出ANSI颜色的类库)。
操作
IDEA中,点击右上角->Edit Configurations,在VM options中添加-Dlog4j.skipJansi=false
首先引入邮件依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
这里每出现一次定义的异常,都会发送一次邮件。
<configuration status="warn" monitorInterval="30">
<springProperty scope="context" name="smtpHost" source="spring.mail.host" />
<springProperty scope="context" name="username" source="spring.mail.username" />
<springProperty scope="context" name="password" source="spring.mail.password" />
<springProperty scope="context" name="mailSubject" source="spring.mail.error.subject" />
<springProperty scope="context" name="mailTo" source="spring.mail.error.to" />
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
<appender name="MAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<smtpHost>${smtpHost}smtpHost>
<smtpPort>465smtpPort>
<username>${username}username>
<password>${password}password>
<SSL>trueSSL>
<asynchronousSending>trueasynchronousSending>
<from>${username}from>
<to>${mailTo}to>
<subject>${mailSubject}: %logger{0} subject>
<charsetEncoding>UTF-8charsetEncoding>
<cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
<bufferSize>10bufferSize>
cyclicBufferTracker>
<layout class="ch.qos.logback.classic.html.HTMLLayout"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERRORlevel>
filter>
appender>
<root level="INFO">
<appender-ref ref="CONSOLE">appender-ref>
<appender-ref ref="MAIL"/>
root>
configuration>
自定义文件Appender
public class MyAppender extends AppenderBase<ILoggingEvent> {
@Override
public void start() {
//这里可以做些初始化判断 比如layout不能为null ,
//或者写入数据库、redis时的初始化连接等等
super.start();
}
@Override
public void stop() {
//释放相关资源,如数据库连接,redis线程池等等
if (!isStarted()) {
return;
}
super.stop();
}
@Override
protected void append(ILoggingEvent event) {
System.out.println("这是我自定义的Appender");
}
}
其次添加logback.xml
配置
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%t] %-5level %logger{36}.%M\(%file:%line\) - %msg%npattern>
<charset>UTF-8charset>
encoder>
appender>
<appender name="MyAppender" class="com.example.mx80.log.MyAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFOlevel>
filter>
appender>
<logger name="com.example.mx80." level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="MyAppender"/>
logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="MyAppender"/>
root>
configuration>
这里每出现一次定义的异常,都会发送一次邮件。如果要修改为策略形式,可以参考https://cxymm.net/article/qq_29693653/100564296
SMTP相关参数介绍
参数 | 说明 |
---|---|
name | appender的名称 |
subject | 主题 |
to | 邮件的接收方邮箱,多个账户使用“,”分割 |
from | 邮件发送所使用的账户 |
smtpProtocol | stmp协议 |
smtpHost | 邮件发送服务器域名 |
smtpPort | 邮件发送服务器端口 |
smtpPassword | 账户密码 |
smtpUsername | 账户名称 |
<configuration status="warn" monitorInterval="30">
<properties>
<property name="MAIL_LEVEL">ERRORproperty>
<property name="PROJECT_NAME">prod-projectproperty>
<property name="LOG_BACK_HOME">./${PROJECT_NAME}-logproperty>
properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} [%t] %highlight{%l}: %msg%n"/>
console>
<SMTP name="Mail" subject="${PROJECT_NAME} Error Log" to="要发送的邮箱@qq.com(可以多个,用逗号分隔)" from="你的邮箱名@qq.com"
smtpHost="smtp.qq.com" smtpPort="465" smtpProtocol="smtps" smtpPassword="你的密码"
smtpUsername="你的邮箱名@qq.com"
bufferSize="1024" smtpDebug="false">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
SMTP>
<Async name="AsyncMail" >
<appender-ref ref="Mail"/>
Async>
appenders>
<loggers>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="AsyncMail" level="${MAIL_LEVEL}"/>
root>
loggers>
configuration>
自定义文件Appender,这里需要注意几个点
@Plugin..
注解:这个注解,是为了在之后配置log4j2.xml
时,指定的Appender Tag。
构造函数:除了使用父类的以外,也可以增加一些自己的配置。
重写append()
方法:这里面需要实现具体的逻辑,日志的去向。
createAppender()
方法:主要是接收log4j2.xml中的配置项。
/**
* 自定义日志Appender
*/
@Plugin(name = "MyAppender", category = "Core", elementType = "appender", printObject = true)
public class MyAppender extends AbstractAppender {
private String fileName;
protected MyAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, Property[] properties, String fileName) {
super(name, filter, layout, ignoreExceptions, properties);
this.fileName=fileName;
}
@Override
public void start(){
//这里可以做些初始化判断,即系统启动时
System.out.println("日志开始");
super.start();
}
@Override
public void stop(){
System.out.println("日志结束");
if (!isStarted()) {
return;
}
super.stop();
}
@Override
public void append(LogEvent event) {
// 这里就可以通过log日志做很多事情,比如接入邮件等日志报警通知,或者自己的文件写入磁盘
System.out.println("这是我的自定义appender");
//final byte[] bytes = getLayout().toByteArray(event);
//writerFile(bytes);
}
/**
* 接收配置文件中的参数
*/
@PluginFactory
public static MyAppender createAppender(@PluginAttribute("name") String name,
@PluginAttribute("fileName") String fileName,
@PluginElement("Filter") final Filter filter,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
if (name == null) {
LOGGER.error("no name defined in conf.");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
// 创建文件
// if (!createFile(fileName)) {
// return null;
// }
return new MyAppender(name, filter, layout, ignoreExceptions, null,fileName);
}
// private static boolean createFile(String fileName) {
// Path filePath = Paths.get(fileName);
// try {
// // 每次都重新写文件,不追加
// if (Files.exists(filePath)) {
// Files.delete(filePath);
// }
// Files.createFile(filePath);
// } catch (IOException e) {
// LOGGER.error("create file exception", e);
// return false;
// }
// return true;
// }
//
// private void writerFile(byte[] log) {
// try {
// Files.write(Paths.get(fileName), log, StandardOpenOption.APPEND);
// } catch (IOException e) {
// LOGGER.error("write file exception", e);
// }
// }
}
其次添加log4j2.xml
配置,需要注意一下几点
下面的log配置,一共配了2个输出。一个是终端输出,一个是采用自定义的MyAppender 输出,即append()
方法输出
标签要与自定义Appender中的类注解保持一致。
<configuration status="INFO" monitorInterval="30">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/>
console>
<MyAppender name="MyAppender" fileName="log.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
MyAppender>
appenders>
<loggers>
<logger name="org.springframework" level="INFO">logger>
<logger name="org.mybatis" level="INFO">logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="MyAppender"/>
root>
loggers>
configuration>
https://howtodoinjava.com/log4j2/threadcontext-fish-tagging/
线程上下文堆栈会唯一地标记每个请求,用户可以在请求刚进入时将上下文信息压入堆栈(通过ThreadContext.put()
),当发送错误邮件时,日志框架会获取压入的上下文数据进行发送。此时再看邮件效果会多出来一行MDC
@Slf4j
@Component
public class RequestLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
ThreadContext.put("URL", request.getRequestURL().toString());
ThreadContext.put("HttpMethod", request.getMethod());
ThreadContext.put("IPAddr", request.getRemoteAddr());
ThreadContext.put("ContentType", request.getContentType());
// 获取所有参数名
Map<String, String> paramNames = new HashMap<>();
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
paramNames.put(name, request.getParameter(name));
}
ThreadContext.put("ParamNames", CollectionUtils.isEmpty(paramNames) ? "{}" : JSON.toJSONString(paramNames));
// TODO 需要时打开注释
// log.info("URL: " + request.getRequestURL().toString());
// log.info("Http Method: " + request.getMethod());
// log.info("IP Addr: " + request.getRemoteAddr());
// log.info("Content Type: " + request.getContentType());
// log.info("ParamNames: " + (CollectionUtils.isEmpty(paramNames) ? "{}" : JSON.toJSONString(paramNames)));
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
//请求结束后清除信息
ThreadContext.clearAll();
}
}
首先参考SpringBoot异步、邮件、定时任务搭建好基本邮件框架,并按照上面自定义appender配置好,在自定义的appender进行修改
private static final long INTERVAL = 1 * 1000 * 60;
private long lastAlarmTime = 0;
private static final String TO = "[email protected]";
private static final String SUBJECT = "测试邮件";
private static final String CONTENT = "test content";
private static final String SENDER = "[email protected]";
public static void sendMail(String context) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
//发件人
simpleMailMessage.setFrom(SENDER);
//邮件接收人,可以是多个
simpleMailMessage.setTo(TO);
//邮件主题
simpleMailMessage.setSubject(SUBJECT);
//邮件内容
simpleMailMessage.setText(context);
JavaMailSender javaMailSender = ContextUtil.getApplicationContext().getBean(JavaMailSender.class);
javaMailSender.send(simpleMailMessage);
}
//这里按照不同的日志框架自己修改
@Override
public void append(LogEvent event) {
// 到达级别才报警
if(event.getLevel().equals(Level.ERROR) || event.getLevel().equals(Level.FATAL)){
if(canAlarm()){
sendMail(String.valueOf(event.getMessage()));
}
}
}
private boolean canAlarm() {
// 做一个简略的频率过滤
long now = System.currentTimeMillis();
if (now - lastAlarmTime >= INTERVAL) {
lastAlarmTime = now;
return true;
} else {
return false;
}
}
上面的邮件发送中,需要使用JavaMailSender
,因为MailUtil这个不是Spring bean对象,而是普通的java对象,由日志框架实例化,不能直接注入获取JavaMailSender,所以借助Spring的ApplicationContext来获取对象。
@Component
public class ContextUtil implements ApplicationContextAware, EnvironmentAware {
private static ApplicationContext applicationContext;
private static Environment environment;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ContextUtil.applicationContext = applicationContext;
}
@Override
public void setEnvironment(Environment environment) {
ContextUtil.environment = environment;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Environment getEnvironment() {
return environment;
}
}
当然了,还可以在全局异常那块进行日志发送告警
参考:
https://blog.csdn.net/java821643/article/details/88753898
http://logback.qos.ch/manual/index.html
https://blog.csdn.net/weixin_39370859/article/details/105039787
log4j2自定义Appender(输出到文件/RPC服务中)
SpringBoot实战基于异常日志的邮件报警