鹰眼系统日志信息能够将分布式调用链上产生的日志信息串联起来,最主要归功于打印出了traceId
。
一个普通的请求,在分布式微服务环境下,可能又调用了很多微服务来完成该请求。不同的服务散落在不同的机器上,日志数据也可能保持在各自的机器上,就算将它们汇总在一个文件上,由于日志数据庞大,一条日志的前后可能都是毫不相干的日志信息。根本无法做到跟着请求的调用日志来排查问题。
但是如果有一个唯一标识来标记同一个请求的调用链上产生的日志,那么我们就可以根据该标识查询出该调用链上产生的所有日志信息。
那么此时的问题就为,如何产生traceId以及如何在打印日志时带上traceID
鹰眼系统日志是利用skywalking+logback自带的功能实现打印日志带上traceId的。因为利用了skywalking,所以traceId自然就是使用skywalking产生的traceId。
打印日志带上TraceId
<configuration scan="true" scanPeriod="30 seconds">
<include optional="true" resource="logback-include.xml"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %X{fddTraceId} %msg%n</Pattern>
</encoder>
</appender>
//自定义"tid"格式转换器
<conversionRule conversionWord="tid" converterClass="org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter"/>
<appender name="SERVER_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOGS_DIR:-logs}/server-ELK.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOGS_DIR:-logs}/server.%d{yyyy-MM-dd}-ELK.log</FileNamePattern>
<MaxHistory>5</MaxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"ts": "%date{yyyy-MM-dd HH:mm:ss.SSS}",
"tid": "%tid",
"level": "%level",
"thread": "%thread",
"class": "%logger{60}:%L",
"method": "%method",
"message": "%message",
"exception": "%exception{10}",
"appId":"${DomainID}"
}
</pattern>
</pattern>
<version/>
<logLevelValue/>
</providers>
</encoder>
</appender>
<root>
<level value="${LOGS_LEVEL:-INFO}" />
<appender-ref ref="STDOUT" />
<appender-ref ref="SERVER_LOG"/>
</root>
</configuration>
先从logback.xml
配置文件入手,可以观察到这里定义了tid这个字段来输出traceId,而tid的输出由org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter
这个类来转换。
这里每个%后面带着的信息,都会有一个对应的converter来生成数据。例如level
由LevelConverter
来生成,还有date等等。
可以在package ch.qos.logback.classic
下的PatternLayout中看到。
public class PatternLayout extends PatternLayoutBase<ILoggingEvent> {
public static final Map<String, String> defaultConverterMap = new HashMap<String, String>();
public static final String HEADER_PREFIX = "#logback.classic pattern: ";
static {
defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
defaultConverterMap.put("d", DateConverter.class.getName());
defaultConverterMap.put("date", DateConverter.class.getName());
defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());
defaultConverterMap.put("level", LevelConverter.class.getName());
defaultConverterMap.put("le", LevelConverter.class.getName());
defaultConverterMap.put("p", LevelConverter.class.getName());
....
}
public class LevelConverter extends ClassicConverter {
public String convert(ILoggingEvent le) {
return le.getLevel().toString();
}
}
这里主要的就是convert这个方法,接受一个event,返回一个字符串。
public abstract String convert(E event);
这里我们看一下tid指定的这个LogbackPatternConverter是如何返回traceId的。
public class LogbackPatternConverter extends ClassicConverter {
public LogbackPatternConverter() {
}
public String convert(ILoggingEvent iLoggingEvent) {
return "TID: N/A";
}
}
发现这里根本就没有输出traceId,而是输出一个默认的TID:N/A
。真正输出traceId的其实是在skywalking-agent的apm-tooklt-logback-1.x-activation
插件上实现的。由该插件对该conver方法进行增强。
可以查看skywalking中关于apm-sniffer/apm-tookit-activation/apm-tookit-logback-1.x-activation
的源码。
public class LogbackPatternConverterActivation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.toolkit.activation.log.logback.v1.x.PrintTraceIdInterceptor";
public static final String ENHANCE_CLASS = "org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter";
public static final String ENHANCE_METHOD = "convert";
/**
* @return the target class, which needs active.
*/
@Override
protected ClassMatch enhanceClass() {
//这里指定增强`LogbackPatternConverter`这个类
return byName(ENHANCE_CLASS);
}
/**
* @return null, no need to intercept constructor of enhance class.
*/
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return null;
}
/**
* @return the collection of {@link StaticMethodsInterceptPoint}, represent the intercepted methods and their
* interceptors.
*/
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
//进一步指定了`LogbackPatternConverter`这个类的`convert`方法
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_METHOD).and(takesArgumentWithType(0, "ch.qos.logback.classic.spi.ILoggingEvent"));
}
@Override
public String getMethodsInterceptor() {
//交由`PrintTraceIdInterceptor`这个类进行增强的逻辑
return INTERCEPT_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}
看看org.apache.skywalking.apm.toolkit.activation.log.logback.v1.x.PrintTraceIdInterceptor
这个类
public class PrintTraceIdInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (!ContextManager.isActive()) {
if (allArguments[0] instanceof EnhancedInstance) {
SkyWalkingContext skyWalkingContext = (SkyWalkingContext) ((EnhancedInstance) allArguments[0]).getSkyWalkingDynamicField();
if (skyWalkingContext != null) {
return "TID:" + skyWalkingContext.getTraceId();
}
}
}
return "TID:" + ContextManager.getGlobalTraceId();
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
}
}
在这里就能明白TID是如何被打印出来的了
在logback中配置tid这个字段的conver为skywalking的log-tookit中的LogbackPatternConverter
。使其在输出tid字段数据的时候,调用LogbackPatternConverter
的cover方法,但是这个方法并不是真正输出traceId的地方,只是起到一个触发的作用,毕竟traceId的传播都在skywalking-agent的相关增强逻辑中,所以输出tarceId也由插件进行实现,直接增强LogbackPatternConverter
的cover方法,在调用该方法后返回真正的traceId。