Closed songlonqi-java closed 1 year ago
目前是通过手动修改的方式插入 traceId
,这种方式不利于大规模应用部署。
主要是需要对 logback
或者 log4j
配置文件进行动态 pattern
调整,可以利用 java 字节码技术进行调整。
otel 通过 log-exporter
调整输出的方式来插入 traceId
,同时要维护一套日志输出机制,投入及维护成本较高,不推荐采用类似的方式实现。
以logback为基础,通过分析 logback 的执行流程和源码,使用Instrumentation
尝试了四种方案,均无效果。
ch.qos.logback.core.pattern.PatternLayoutBase
是一个abstract 类,其中有一个属性为 pattern
,通过对 public void setPattern(String pattern)
对 pattern
进行动态调整。
package ch.qos.logback.core.pattern;
...
public abstract class PatternLayoutBase<E> extends LayoutBase<E> {
static final int INTIAL_STRING_BUILDER_SIZE = 256;
Converter<E> head;
String pattern;
public void setPattern(String pattern) {
this.pattern = pattern;
}
......
探针实现:
......
@Override
public String hierarchyMarkerType() {
return "ch.qos.logback.core.pattern.PatternLayoutBase";
}
@Override
public void adviceTransformations(AdviceTransformation transformation) {
transformation.applyAdvice(
isMethod()
.and(isPublic())
.and(named("setPattern")),
PatternLayoutBaseInstrumentation.class.getName() + "$StartAdvice");
}
public static class StartAdvice{
@Advice.OnMethodEnter
public static void onEnter() {
System.out.println("do StartAdvice,pattern:");
}
}
......
ch.qos.logback.core.pattern.PatternLayoutEncoderBase
也可以初始化pattern
的值,主要通过public void setProperty(String name, String value)
函数。
PatternLayoutEncoderBase
部分源码
package ch.qos.logback.core.pattern;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
public class PatternLayoutEncoderBase<E> extends LayoutWrappingEncoder<E> {
String pattern;
protected boolean outputPatternAsHeader = false;
public PatternLayoutEncoderBase() {
}
public String getPattern() {
return this.pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
......
探针实现;
@Override
public String hierarchyMarkerType() {
return "ch.qos.logback.core.pattern.PatternLayoutEncoderBase";
}
@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return extendsClass(named(hierarchyMarkerType()));
}
@Override
public void adviceTransformations(AdviceTransformation transformation) {
transformation.applyAdvice(
isMethod()
.and(isPublic())
.and(named("setPattern")),
PatternLayoutEncoderBaseInstrumentation.class.getName() + "$StartAdvice");
}
public static class StartAdvice{
@Advice.OnMethodEnter
public static void onEnter() {
System.out.println("do PatternLayoutEncoderBase,pattern:");
}
}
PropertySetter
(ch.qos.logback.core.joran.util.PropertySetter) 是用来初始化xml 对象属性(值),通过public void setProperty(String name, String value)
来修改对象pattern
值。
探针实现;
......
@Override
public String instrumentedType() {
return "ch.qos.logback.core.joran.util.PropertySetter";
}
@Override
public void adviceTransformations(AdviceTransformation transformation) {
System.out.println(" PropertySetterInstrumentation transformation");
transformation.applyAdvice(
isMethod()
.and(isPublic())
.and(named("setProperty"))
.and(takesArguments(2)),
PropertySetterInstrumentation.class.getName() + "$PropertySetterAdvice");
}
public static class PropertySetterAdvice{
@Advice.OnMethodEnter
public static void onEnter() {
System.out.println("do PropertySetterAdvice,pattern:");
}
}
......
ch.qos.logback.classic.encoder.PatternLayoutEncoder
为logback 默认的编码器,通过start
函数可以对PatternLayoutEncoder
进行对象初始化,初始化时,同时赋值 pattern
。
package ch.qos.logback.classic.encoder;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.pattern.PatternLayoutEncoderBase;
public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
public PatternLayoutEncoder() {
}
public void start() {
PatternLayout patternLayout = new PatternLayout();
patternLayout.setContext(this.context);
patternLayout.setPattern(this.getPattern());
patternLayout.setOutputPatternAsHeader(this.outputPatternAsHeader);
patternLayout.start();
this.layout = patternLayout;
super.start();
}
}
探针实现
...
@Override
public String hierarchyMarkerType() {
return "ch.qos.logback.classic.encoder.PatternLayoutEncoder";
}
@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return named(hierarchyMarkerType());
}
@Override
public void adviceTransformations(AdviceTransformation transformation) {
transformation.applyAdvice(isConstructor(), getClass().getName() + "$PatternLayoutEncoderConstructor");
transformation.applyAdvice(
isMethod()
.and(isPublic())
.and(named("start"))
.and(takesArguments(0)),
LogbackPatternLayoutEncoderInstrumentation.class.getName() + "$StartAdvice");
}
public static class PatternLayoutEncoderConstructor{
@Advice.OnMethodExit
public static void exit() {
System.out.println("do $PatternLayoutEncoderConstructor,OnMethodExit:");
}
}
public static class StartAdvice{
@Advice.OnMethodEnter
public static void onEnter() {
System.out.println("do StartAdvice,pattern:");
}
}
...
以上四种探针测试无效(没有进入对应的探针方法)。
仍需要对logback
进行探究,以找到可以对 pattern
进行调整的最佳方式。
log4j2 已实现 pattern 调整 https://github.com/GuanceCloud/dd-trace-java/commit/89adbac547e745fa7bce5ef12c5dfce3333ea38c
默认 pattern = (%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - [%method,%line] %X{dd.service} %X{dd.trace_id} %X{dd.span_id} - %msg%n)
尝试几种方式 没有进入探针位置
@AutoService(Instrumenter.class) public final class LoggerContextInstrumentation extends Instrumenter.Tracing implements Instrumenter.ForSingleType{
public LoggerContextInstrumentation() { super("logback-1"); }
@Override public String instrumentedType() { return "ch.qos.logback.classic.LoggerContext"; }
@Override public void adviceTransformations(AdviceTransformation transformation) { transformation.applyAdvice( isMethod() .and(named("getLogger")) .and(takesArguments(1)), getClass().getName()+"$EncodeAdvice1" ); }
public static final class EncodeAdvice1 { @Advice.OnMethodExit(onThrowable = Throwable.class,suppress = Throwable.class) public static void getLogger( @Advice.Return final Logger logger ){ System.out.println("------------------into -------------- EncodeAdvice"); logger.info("------------------"); System.out.println("----------------out ------------------------"); } } }
2. ch.qos.logback.classic.encoder.PatternLayoutEncoder
```java
@AutoService(Instrumenter.class)
public class EncodeInstrumentation extends Instrumenter.Tracing
implements Instrumenter.ForTypeHierarchy{
public EncodeInstrumentation() {super("logback");}
@Override
public String hierarchyMarkerType() {
return "ch.qos.logback.classic.encoder.PatternLayoutEncoder";
}
@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return named(hierarchyMarkerType());
}
@Override
public void adviceTransformations(AdviceTransformation transformation) {
transformation.applyAdvice(
isMethod().
and(named("start")).and(takesArguments(0)),
EncodeInstrumentation.class.getName()+"$EncodeAdvice"
);
}
public static class EncodeAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.This PatternLayoutEncoder encoder
){
System.out.println("------------------into -------------- EncodeAdvice");
System.out.println(encoder.getPattern());
System.out.println(encoder.getLayout().getClass().getName());
System.out.println("----------------out ------------------------");
}
}
}
logback 框架采用多种方式暂时无法提供支持,后续持续跟进,先关掉issue
在日志中增加 dd 或者其他 agent 链路信息
在 agent 加载时候,修改用户的日志输出,增加 “traceid” 信息
可以在每条日志中修改patten 也可以直接修改log 对象的patten。
这只是大纲,做法可以在楼下贴出。