依赖配置
<!--排除默认日志配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入log4j2依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
基本组件
Logger 日志记录器
如何输出日志,如指定输出方式、设置日志级别、设置日志过滤器等
Appender 日志输出方式
日志输出到什么地方,常用的包括控制台、滚动文件等
- ConsoleAppender
- RollingFileAppender
Layout 日志格式化
根据表达式或JSON格式化日志,如PatternLayout和JsonLayout
日志参数表达式
Pattern表达式 | 对应类 | 功能说明 |
---|---|---|
%date | DatePatternConverter | 日志时间 |
%level | LevelPatternConverter | 日志等级 |
%thread | ThreadNamePatternConverter | 线程名称 |
%msg | MessagePatternConverter | 日志内容 |
%replace{}{}{} | RegexReplacementConverter | 替换文本,如:%replace{%message}{a}{b} 替换日志内容中的a为b |
%X{} | MdcPatternConverter | Mdc中的值,如:%X{abc} |
%n | LineSeparatorPatternConverter | 换行符 |
%-10.10 | 无 | 正数右对齐,负数左对齐,不足位数补充空格;点加数字表示末尾截取长度; |
环境配置参数表达式
Lookup表达式 | 对应类 | 功能说明 |
---|---|---|
${java:} | JavaLookup | 获取Java环境参数,如:${java:version} 获取Java版本 |
Policy 日志滚动规则
- TimeBasedTriggeringPolicy
Strategy 日志删除策略
- DefaultRolloverStrategy
Filter 日志过滤器
过滤器在Configuration、Logger、Appender三种组件中均可以配置
- MarkerFilter 标记过滤
- ThresholdFilter 日志等级过滤
- TimeFilter 时间过滤
- RegexFilter 正则过滤
Appenders:
Console: #输出到控制台
name: Console # Appender 的名字
target: SYSTEM_OUT
PatternLayout: #输出格式
pattern: "%date %level %class %method : %msg%n"
filters:
markerFilter: #marker过滤器
marker: ACCESS_LOGGER
onMatch: DENY #丢弃
onMismatch: NEUTRAL #中立
thresholdFilter: #日志等级过滤器
level: debug
onMatch: ACCEPT
onMismatch: DENY
编程式日志组件
import cn.orange.common.utils.JsonUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.action.*;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.filter.MarkerFilter;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 初始化默认日志配置
*/
public class Log4jConfig {
public static final String ACCESS_LOGGER = "ACCESS_LOGGER";
@Value("${spring.application.name}")
private String SERVER_NAME;
@PostConstruct
public void init(){
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
//日志统一添加字段
Map<String,String> patternTemplate = new HashMap<>();
patternTemplate.put("date","%date{yyyy-MM-dd HH:mm:ss.SSS}");
patternTemplate.put("level","%level");
patternTemplate.put("traceId","%X{traceId}");
patternTemplate.put("serverName",SERVER_NAME);
patternTemplate.put("thread","%thread");
patternTemplate.put("class","%class");
patternTemplate.put("method","%method");
//双引号、换行符、TAB符转义
patternTemplate.put("message","%replace{%replace{%replace{%message}{\"}{\\\"}}{\r?\n}{\\n}}{\t}{\\t}");
patternTemplate.put("exception","%replace{%replace{%replace{%xwException}{\"}{\\\"}}{\r?\n}{\\n}}{\t}{\\t}");
//------------------------------- Appender ----------------------------------
RollingFileAppender ERROR_ROLLING_FILE = createRollingFileAppender(JsonUtils.beanToJson(patternTemplate) + "%n", "ERROR_ROLLING_FILE", "error",config);
RollingFileAppender INFO_ROLLING_FILE = createRollingFileAppender(JsonUtils.beanToJson(patternTemplate) + "%n", "INFO_ROLLING_FILE", "info",config);
RollingFileAppender ACCESS_ROLLING_FILE = createRollingFileAppender("%message%n", "ACCESS_ROLLING_FILE", "access",config);
ERROR_ROLLING_FILE.start();
INFO_ROLLING_FILE.start();
ACCESS_ROLLING_FILE.start();
//------------------------------- Logger设置 ----------------------------------
config.getRootLogger().addAppender(ERROR_ROLLING_FILE, Level.ERROR, null);
//info不需要access日志
MarkerFilter infoMarkerFilter = MarkerFilter.createFilter(ACCESS_LOGGER, Filter.Result.DENY, Filter.Result.ACCEPT);
config.getRootLogger().addAppender(INFO_ROLLING_FILE, Level.DEBUG, infoMarkerFilter);
//只保留access日志
MarkerFilter accessMarkerFilter = MarkerFilter.createFilter(ACCESS_LOGGER, Filter.Result.ACCEPT, Filter.Result.DENY);
config.getRootLogger().addAppender(ACCESS_ROLLING_FILE, Level.INFO, accessMarkerFilter);
ctx.updateLoggers(config);
}
/**
* 创建appender
* @param pattern 日志表达式
* @param name appender名称
* @param fileName 日志文件名(不包括后缀名,如:info)
* @param config
* @return
*/
private RollingFileAppender createRollingFileAppender(String pattern,String name,String fileName,Configuration config){
//Layout
PatternLayout patternLayout = PatternLayout.newBuilder()
.withAlwaysWriteExceptions(false)
.withCharset(StandardCharsets.UTF_8)
.withPattern(pattern)
.build();
//日志滚动规则
TimeBasedTriggeringPolicy timeBasedTriggeringPolicy = TimeBasedTriggeringPolicy.newBuilder().withModulate(true).withInterval(1).build();
//日志删除策略:7天
IfFileName ifFileName = IfFileName.createNameCondition(fileName+"-*.log",null,null);
IfLastModified ifLastModified = IfLastModified.createAgeCondition(Duration.parse("168H"),null);
PathCondition[] pathConditions = {ifFileName,ifLastModified};
DeleteAction deleteAction = DeleteAction.createDeleteAction("logs/",false, 1,false,null,pathConditions,null,config);
Action[] actions = {deleteAction};
DefaultRolloverStrategy defaultRolloverStrategy = DefaultRolloverStrategy.newBuilder().withCustomActions(actions).build();
//Appender
return RollingFileAppender.newBuilder()
.setName(name)
.withFileName("logs/"+fileName+".log")
.withFilePattern("logs/"+fileName+"-%date{yyyy-MM-dd}.log")
.setLayout(patternLayout)
.withPolicy(timeBasedTriggeringPolicy)
.withStrategy(defaultRolloverStrategy)
.build();
}
}
分布式追踪日志
- 通过 servlet 的 Filter 设置 traceId。参考
- 通过阿里开源的 TransmittableThreadLocal 跨线程存储 traceId。