JAVA && Spring && SpringBoot2.x — 学习目录
如何根据日志文件快速定位到应用的行为。
1. layout
Layout组件:
([累奥特] 布局)
,将日志事件进行格式化,返回一个字符串。
1.1 常用的转换词
转换词 | 备注 |
---|---|
C{length},class{length} | 输出发出日志请求调用者的完整类名。注:类名最右边部分不会省略,即使长度超过了设定了length长度。 |
contextName | 日志记录器上下文的名字 |
d{pattern},date{pattern},d{pattern,timezone},date{pattern,timezone} | 日志事件的日期,日期转换词需要一个格式化字符串作为参数,格式化字符串语义与java.text.SimpleDateFormat相同。 |
F/file | 日志请求的java源文件名,一般生成文件信息比较慢,因此一般不使用这个转换词 |
caller{depth} | 输出日志调用者位置信息 |
L/line | 日志请求的行号,由于生成行号信息比较慢,一般不使用这个转换词 |
m/msg/message | 日志事件相关的应用提供信息 |
M/method | 输出与日志事件相关的应用提供的信息 |
n | 输出与平台独立的行分割符,等价于"\n"或者"\r\n" |
p/le/level | 输出日志级别 |
r/relative(相对的) | 输出日志事件的相对时间 |
t/thread[斯乱德] |
输出产生日志事件的线程名 |
X{key:-defaultVal} | 输出与产生日志事件线程相关的MDC(mapped diagnosis context)。如果mdc转换词后面花括号中有key,那么value就会输出,如果值为空,那么输出默认值;若没有默认值,则输出空字符串。 |
ex{depth}[带婆斯] |
若发生异常时,指定输出异常的行数。 |
replace(p){r,t} | 用't'替换p中正则表达式r的内容,例如:%replace('%m'){'\s',''}会去替换事件消息中包含的所有空格 |
详细请见:https://blog.csdn.net/lingbomanbu_lyl/article/details/89852037
1.2 自定义layout
- 继承自LayoutBase接口即可:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
/**
* @ClassName MySampleLayout
* @Description 日志布局器
* @Date 2019/7/1
* @Version 1.0
**/
public class MySampleLayout extends LayoutBase {
@Override
public String doLayout(ILoggingEvent event) {
StringBuffer sb=new StringBuffer();
sb.append(event.getTimeStamp()-event.getLoggerContextVO().getBirthTime());
sb.append(" [").append(event.getThreadName()).append("] ");
sb.append(event.getLoggerName()).append("-");
sb.append(event.getFormattedMessage()).append(CoreConstants.LINE_SEPARATOR);
return sb.toString();
}
}
- 配置文件
- 输出格式:
1230 [restartedMain] com.MmWebApplication-The following profiles are active: test
若包含自定义参数的layout组件
- java实现类
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import org.apache.commons.lang3.StringUtils;
/**
* @ClassName CustomLayout
* @Description 自定义Layout
* @Date 2019/7/1
* @Version 1.0
**/
public class CustomLayout extends LayoutBase {
private String prefix = null;
boolean printThreadName = true;
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setPrintThreadName(boolean printThreadName) {
this.printThreadName = printThreadName;
}
@Override
public String doLayout(ILoggingEvent event) {
StringBuffer sbuf = new StringBuffer(128);
if (StringUtils.isNotBlank(prefix)) {
sbuf.append(prefix).append(" ");
}
sbuf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());
sbuf.append(" ");
sbuf.append(event.getLevel());
if (printThreadName) {
sbuf.append(" [");
sbuf.append(event.getThreadName());
sbuf.append("] ");
} else {
sbuf.append(" ");
}
sbuf.append(event.getLoggerName());
sbuf.append(" - ");
sbuf.append(event.getFormattedMessage());
sbuf.append(CoreConstants.LINE_SEPARATOR);
return sbuf.toString();
}
}
- 配置文件
MyPrefix
false
- 输出格式
MyPrefix 1405 INFO com.MmWebApplication - The following profiles are active: test
2. MDC
logback内置的日志字段还是比较少的,如果我们需要打印有关业务的更多内容,包括自定义的一些数据,需要借助logback的MDC机制,(映射诊断上下文),即将一些运行时的上下文数据通过logback打印出来;此时我们需要借助org.sl4j.MDC
类。
MDC类的原理:其内部持有一个InheritableThreadLocal [因海瑞特包 遗传]
实例,用于保存context数据,MDC提供了put/get/clear等几个接口,用来操作ThreadLocal中的数据;ThreadLocal中的K-V,可以在logback.xml中声明,最终会打印在日志中。
MDC.put("userId",1000);
那么在logback.xml中,即可在layout中通过"%X{userId}"来打印此信息。
注意:因为使用的是ThreadLocal,在需要线程退出之前,需要清除(clear)MDC里面的数据;在线程池中使用MDC时,在子线程退出之前调用MDC.clear()方法。
2.1 MDC实践
public class LogMDCUtil {
//接口名
private final static String SVR_ID = "SVR_ID";
private final static Logger logger = LoggerFactory.getLogger(LogMDCUtil.class);
/**
* 为日志设置服务名
*
* @param serviceId
*/
public static void setServiceNameToLog(String serviceId) {
try {
MDC.put(SVR_ID, serviceId);
} catch (Exception e) {
logger.error("【Logback设置MDC】-设置服务名失败", e);
}
}
/**
* 删除ThreadLocal中关于ServiceName的数据,防止内存溢出
*/
public static void removeServiceNameToLog() {
//线程执行完毕,需要删除ServiceName
try {
MDC.remove(SVR_ID);
} catch (Exception e) {
logger.error("【Logback设置MDC】-删除服务名失败", e);
}
}
}
配置文件:
%d %p [%t %X{SVR_ID}] (%C{0}:%line\): %m%n
UTF-8
2.2 MDC实战—为请求生成唯一编号
如何为每一请求生成唯一编号,并在日志中打印出来呢?
- 借助Redis自增命令,获取唯一编号
@Service
public class GainIdService {
private static Logger logger = LoggerFactory.getLogger(GainIdService.class);
@Resource
private StringRedisTemplate template;
//前缀 不同系统的前缀可以不相同
public String gainId(String prefix) {
Long seq = template.opsForValue().increment("system:idseq:" + pre.getPrefix());
DecimalFormat df = new DecimalFormat("0000");
String str = df.format(seq);
//substring(int beginIndex),[beginIndex,endIndex]的子串,即获取后四位编号
str = str.substring(str.length() - 4);
SimpleDateFormat sf=new SimpleDateFormat("yyyyMMddHHmmss");
String timeStr = sf.format(new Date());
return pre.getPrefix() + timeStr + str;
}
}
- 在SpringBoot拦截器中放入MDC中
springBoot—拦截器详解
@Component
public class LogInterceptor implements HandlerInterceptor {
private final static String REQ_ID = "REQ_ID";
@Autowired
GainIdService gainIdService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String traceId = gainIdService.gainId(IdPrefixEnum.REQ_ID);
MDC.put(REQ_ID, traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
MDC.remove(REQ_ID);
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
- 将拦截器配置到系统中
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
@Autowired
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
}
}
参考文档
(译)(五)Logback中的Layout