Skywalking 插件開(kāi)發(fā)

本文出處shenyifengtk.github.io 轉(zhuǎn)載請(qǐng)說(shuō)明

概念

Span

Span 是分布式跟蹤系統(tǒng)中一個(gè)重要且常用的概念. 可從 Google Dapper PaperOpenTracing 學(xué)習(xí)更多與 Span 相關(guān)的知識(shí).

SkyWalking 從 2017 年開(kāi)始支持 OpenTracing 和 OpenTracing-Java API, 我們的 Span 概念與論文和 OpenTracing 類似. 我們也擴(kuò)展了 Span.

Span 有三種類型

1.1 EntrySpan

EntrySpan 代表服務(wù)提供者, 也是服務(wù)器端的端點(diǎn). 作為一個(gè) APM 系統(tǒng), 我們的目標(biāo)是應(yīng)用服務(wù)器. 所以幾乎所有的服務(wù)和 MQ-消費(fèi)者 都是 EntrySpan听绳⌒┡颍可以理解一個(gè)進(jìn)程處理第一個(gè)span就是EntrySpan衣形,意思為entiry span 進(jìn)入服務(wù)span峭判。

1.2 LocalSpan

LocalSpan 表示普通的 Java 方法, 它與遠(yuǎn)程服務(wù)無(wú)關(guān), 也不是 MQ 生產(chǎn)者/消費(fèi)者, 也不是服務(wù)(例如 HTTP 服務(wù))提供者/消費(fèi)者族吻。所有本地方法調(diào)用都是localSpan,包括異步線程調(diào)用捏境,線程池提交任務(wù)都是峻村。

1.3 ExitSpan

ExitSpan 代表一個(gè)服務(wù)客戶端或MQ的生產(chǎn)者, 在 SkyWalking 的早期命名為 LeafSpan. 例如 通過(guò) JDBC 訪問(wèn)DB, 讀取 Redis/Memcached 被歸類為 ExitSpan.

image.png

上下文載體 (ContextCarrier)

為了實(shí)現(xiàn)分布式跟蹤, 需要綁定跨進(jìn)程的追蹤, 并且上下文應(yīng)該在整個(gè)過(guò)程中隨之傳播. 這就是 ContextCarrier 的職責(zé).

以下是有關(guān)如何在 A -> B 分布式調(diào)用中使用 ContextCarrier 的步驟.

  1. 在客戶端, 創(chuàng)建一個(gè)新的空的 ContextCarrier.
  2. 通過(guò) ContextManager#createExitSpan 創(chuàng)建一個(gè) ExitSpan 或者使用 ContextManager#inject 來(lái)初始化 ContextCarrier.
  3. ContextCarrier 所有信息放到請(qǐng)求頭 (如 HTTP HEAD), 附件(如 Dubbo RPC 框架), 或者消息 (如 Kafka) 中,詳情可以看官方給出跨進(jìn)程傳輸協(xié)議sw8
  4. 通過(guò)服務(wù)調(diào)用, 將 ContextCarrier 傳遞到服務(wù)端.
  5. 在服務(wù)端, 在對(duì)應(yīng)組件的頭部, 附件或消息中獲取 ContextCarrier 所有內(nèi)容.
  6. 通過(guò) ContestManager#createEntrySpan 創(chuàng)建 EntrySpan 或者使用 ContextManager#extract 將服務(wù)端和客戶端的綁定.

讓我們通過(guò) Apache HttpComponent client 插件和 Tomcat 7 服務(wù)器插件演示, 步驟如下:

  1. 客戶端 Apache HttpComponent client 插件
span = ContextManager.createExitSpan("/span/operation/name", contextCarrier, "ip:port");
CarrierItem next = contextCarrier.items();
while (next.hasNext()) {
    next = next.next();
    httpRequest.setHeader(next.getHeadKey(), next.getHeadValue());
}

  1. 服務(wù)端 Tomcat 7 服務(wù)器插件
ContextCarrier contextCarrier = new ContextCarrier();
CarrierItem next = contextCarrier.items();
while (next.hasNext()) {
    next = next.next();
    next.setHeadValue(request.getHeader(next.getHeadKey()));
}

span = ContextManager.createEntrySpan("/span/operation/name", contextCarrier);

上下文快照 (ContextSnapshot)

除了跨進(jìn)程, 跨線程也是需要支持的, 例如異步線程(內(nèi)存中的消息隊(duì)列)和批處理在 Java 中很常見(jiàn), 跨進(jìn)程和跨線程十分相似, 因?yàn)槎际切枰獋鞑ド舷挛? 唯一的區(qū)別是, 不需要跨線程序列化.

以下是有關(guān)跨線程傳播的三個(gè)步驟:

  1. 使用 ContextManager#capture 方法獲取 ContextSnapshot 對(duì)象.
  2. 讓子線程以任何方式, 通過(guò)方法參數(shù)或由現(xiàn)有參數(shù)攜帶來(lái)訪問(wèn) ContextSnapshot
  3. 在子線程中使用 ContextManager#continued田巴。

跨進(jìn)程Span傳輸原理

public class CarrierItem implements Iterator<CarrierItem> {
    private String headKey;
    private String headValue;
    private CarrierItem next;

    public CarrierItem(String headKey, String headValue) {
        this(headKey, headValue, null);
    }

    public CarrierItem(String headKey, String headValue, CarrierItem next) {
        this.headKey = headKey;
        this.headValue = headValue;
        this.next = next;
    }

    public String getHeadKey() {
        return headKey;
    }

    public String getHeadValue() {
        return headValue;
    }

    public void setHeadValue(String headValue) {
        this.headValue = headValue;
    }

    @Override
    public boolean hasNext() {
        return next != null;
    }

    @Override
    public CarrierItem next() {
        return next;
    }

    @Override
    public void remove() {

    }
}

CarrierItem 類似Map key value的數(shù)據(jù)接口钠糊,通過(guò)一個(gè)單向連接將K/V連接起來(lái)。
看下 ContextCarrier.items()方法如何創(chuàng)建CarrierItem

    public CarrierItem items() {
       //內(nèi)置一個(gè) sw8-x key
        SW8ExtensionCarrierItem sw8ExtensionCarrierItem = new SW8ExtensionCarrierItem(extensionContext, null); 
       //內(nèi)置  sw8-correlation key
        SW8CorrelationCarrierItem sw8CorrelationCarrierItem = new SW8CorrelationCarrierItem(
            correlationContext, sw8ExtensionCarrierItem);
       //內(nèi)置 sw8 key 
        SW8CarrierItem sw8CarrierItem = new SW8CarrierItem(this, sw8CorrelationCarrierItem);
        return new CarrierItemHead(sw8CarrierItem);
    }

創(chuàng)建一個(gè)鏈接CarrierItemHead->SW8CarrierItem ->SW8CorrelationCarrierItem->SW8ExtensionCarrierItem
在看下上面tomcat7 遍歷CarrierItem壹哺,調(diào)用key從http header獲取值設(shè)置到對(duì)象內(nèi)置值抄伍,這樣就可以做到將上一個(gè)進(jìn)程header 值設(shè)置到下一個(gè)進(jìn)程里,在調(diào)用

    ContextCarrier deserialize(String text, HeaderVersion version) {
        if (text == null) {
            return this;
        }
        if (HeaderVersion.v3.equals(version)) {
            String[] parts = text.split("-", 8);
            if (parts.length == 8) {
                try {
                    // parts[0] is sample flag, always trace if header exists.
                    this.traceId = Base64.decode2UTFString(parts[1]);
                    this.traceSegmentId = Base64.decode2UTFString(parts[2]);
                    this.spanId = Integer.parseInt(parts[3]);
                    this.parentService = Base64.decode2UTFString(parts[4]);
                    this.parentServiceInstance = Base64.decode2UTFString(parts[5]);
                    this.parentEndpoint = Base64.decode2UTFString(parts[6]);
                    this.addressUsedAtClient = Base64.decode2UTFString(parts[7]);
                } catch (IllegalArgumentException ignored) {

                }
            }
        }
        return this;
    }

這樣剛剛new 出來(lái)ContextCarrier就可以從上一個(gè)調(diào)用者上繼承所有的屬性管宵,新創(chuàng)建span就可以跟上一個(gè)span 關(guān)聯(lián)起來(lái)了了截珍。

開(kāi)發(fā)插件

知識(shí)點(diǎn)

追蹤的基本方法是攔截 Java 方法, 使用字節(jié)碼操作技術(shù)(byte-buddy)和 AOP 概念. SkyWalking 包裝了字節(jié)碼操作技術(shù)并追蹤上下文的傳播, 所以你只需要定義攔截點(diǎn)(換句話說(shuō)就是 Spring 的切面)。

ClassInstanceMethodsEnhancePluginDefine定義了構(gòu)造方法 Contructor 攔截點(diǎn)和 instance method 實(shí)例方法攔截點(diǎn)啄糙,主要有三個(gè)方法需要被重寫

     /**
     * 需要被攔截Class
     * @return
     */
    @Override
    protected ClassMatch enhanceClass() {
        return null;
    }

    /**
     * 構(gòu)造器切點(diǎn)
     * @return
     */
    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[0];
    }

    /**
     * 方法切點(diǎn)
     * @return InstanceMethodsInterceptPoint 里面會(huì)聲明攔截按個(gè)方法
     */
    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[0];
    }

ClassMatch 以下有四種方法表示如何去匹配目標(biāo)類:

  • NameMatch.byName, 通過(guò)類的全限定名(Fully Qualified Class Name, 即 包名 + . + 類名).
  • ClassAnnotationMatch.byClassAnnotationMatch, 根據(jù)目標(biāo)類是否存在某些注解.
  • MethodAnnotationMatchbyMethodAnnotationMatch, 根據(jù)目標(biāo)類的方法是否存在某些注解.
  • HierarchyMatch.byHierarchyMatch, 根據(jù)目標(biāo)類的父類或接口

ClassStaticMethodsEnhancePluginDefine 定義了類方法 class 靜態(tài)method 攔截點(diǎn)笛臣。

public abstract class ClassStaticMethodsEnhancePluginDefine extends ClassEnhancePluginDefine {

    /**
     * 構(gòu)造器切點(diǎn)
     * @return null, means enhance no constructors.
     */
    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return null;
    }

    /**
     * 方法切點(diǎn)
     * @return null, means enhance no instance methods.
     */
    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return null;
    }
}

InstanceMethodsInterceptPoint 普通方法接口切點(diǎn)有哪些方法

public interface InstanceMethodsInterceptPoint {
    /**
     * class instance methods matcher.
     *  可以理解成功對(duì)Class 那些方法進(jìn)行增強(qiáng)
     *  ElementMatcher 是bytebuddy 類庫(kù)一個(gè)方法匹配器,里面封裝了各種方法匹配
     * @return methods matcher
     */
    ElementMatcher<MethodDescription> getMethodsMatcher();

    /**
     * @return represents a class name, the class instance must instanceof InstanceMethodsAroundInterceptor.
     *  返回一個(gè)攔截器全類名隧饼,所有攔截器必須實(shí)現(xiàn)    InstanceMethodsAroundInterceptor 接口
     */
    String getMethodsInterceptor();

    /**
     *  是否要覆蓋原方法入?yún)?     * @return
     */
    boolean isOverrideArgs();
}

在看下攔截器有那些方法

/**
 * A interceptor, which intercept method's invocation. The target methods will be defined in {@link
 * ClassEnhancePluginDefine}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine}
 */
public interface InstanceMethodsAroundInterceptor {
    /**
     * called before target method invocation.
     * 前置通知
     * @param result change this result, if you want to truncate the method.
     */
    void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable;

    /**
     * called after target method invocation. Even method's invocation triggers an exception.
     * 后置通知
     * @param ret the method's original return value. May be null if the method triggers an exception.
     * @return the method's actual return value.
     */
    Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable;

    /**
     * called when occur exception.
     * 異常通知
     * @param t the exception occur.
     */
    void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
        Class<?>[] argumentsTypes, Throwable t);
}

開(kāi)發(fā)Skywalking實(shí)戰(zhàn)

項(xiàng)目maven環(huán)境配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>tk.shenyifeng</groupId>
    <artifactId>skywalking-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <skywalking.version>8.10.0</skywalking.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-agent-core</artifactId>
            <version>${skywalking.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>java-agent-util</artifactId>
            <version>${skywalking.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>false</shadedArtifactAttached>
                            <createDependencyReducedPom>true</createDependencyReducedPom>
                            <createSourcesJar>true</createSourcesJar>
                            <shadeSourcesContent>true</shadeSourcesContent>
                            <relocations>
                                <relocation>
                                    <pattern>net.bytebuddy</pattern>
                                    <shadedPattern>org.apache.skywalking.apm.dependencies.net.bytebuddy</shadedPattern>
                                </relocation>
                            </relocations>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

為了更有代表性一些沈堡,使用Skywalking官方開(kāi)發(fā)的ES插件來(lái)做一個(gè)例子。為了兼容不同版本框架燕雁,Skywalking 官方使用witnessClasses诞丽,當(dāng)前框架Jar存在這個(gè)Class就會(huì)任務(wù)是某個(gè)版本、同樣witnessMethods當(dāng)Class存在某個(gè)Method拐格。

public class AdapterActionFutureInstrumentation extends ClassEnhancePluginDefine {

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[0];
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new InstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return named("actionGet"); //攔截方法
                }

                @Override
                public String getMethodsInterceptor() {  //攔截器全類名
                    return "org.apache.skywalking.apm.plugin.elasticsearch.v7.interceptor.AdapterActionFutureActionGetMethodsInterceptor";
                }

                @Override
                public boolean isOverrideArgs() {
                    return false;
                }
            }
        };
    }

    @Override
    public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
        return new StaticMethodsInterceptPoint[0];
    }

    @Override
    protected ClassMatch enhanceClass() { //增強(qiáng)Class
        return byName("org.elasticsearch.action.support.AdapterActionFuture");
    }

    @Override
    protected String[] witnessClasses() {//ES7 存在Class
        return new String[] {"org.elasticsearch.transport.TaskTransportChannel"};
    }

    @Override
    protected List<WitnessMethod> witnessMethods() { //ES7 SearchHits 存在方法
        return Collections.singletonList(new WitnessMethod(
            "org.elasticsearch.search.SearchHits",
          named("getTotalHits").and(takesArguments(0)).and(returns(named("org.apache.lucene.search.TotalHits")))
        ));
    }
}

創(chuàng)建一個(gè)給定類名的攔截器僧免,實(shí)現(xiàn)InstanceMethodsAroundInterceptor接口。創(chuàng)建一個(gè)EntrySpan

public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor {

    private static boolean IS_SERVLET_GET_STATUS_METHOD_EXIST;
    private static final String SERVLET_RESPONSE_CLASS = "javax.servlet.http.HttpServletResponse";
    private static final String GET_STATUS_METHOD = "getStatus";

    static {
        IS_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist(
            TomcatInvokeInterceptor.class.getClassLoader(), SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD);
    }

    /**
     * * The {@link TraceSegment#ref} of current trace segment will reference to the trace segment id of the previous
     * level if the serialized context is not null.
     *
     * @param result change this result, if you want to truncate the method.
     */
    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                             MethodInterceptResult result) throws Throwable {
        Request request = (Request) allArguments[0];
        ContextCarrier contextCarrier = new ContextCarrier();

        CarrierItem next = contextCarrier.items();
       //如果 HTTP 請(qǐng)求頭中有符合sw8 傳輸協(xié)議的請(qǐng)求頭則 取出來(lái)設(shè)置到上下文ContextCarrier
        while (next.hasNext()) {
            next = next.next();
            next.setHeadValue(request.getHeader(next.getHeadKey()));
        }
        String operationName =  String.join(":", request.getMethod(), request.getRequestURI());
        AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier);//關(guān)聯(lián)起來(lái)
        Tags.URL.set(span, request.getRequestURL().toString()); //添加 span 參數(shù)
        Tags.HTTP.METHOD.set(span, request.getMethod());
        span.setComponent(ComponentsDefine.TOMCAT);
        SpanLayer.asHttp(span);

        if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) {
            collectHttpParam(request, span);
        }
    }

    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                              Object ret) throws Throwable {
        Request request = (Request) allArguments[0];
        HttpServletResponse response = (HttpServletResponse) allArguments[1];

        AbstractSpan span = ContextManager.activeSpan();
        if (IS_SERVLET_GET_STATUS_METHOD_EXIST && response.getStatus() >= 400) {
            span.errorOccurred();
            Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus());
        }
        // Active HTTP parameter collection automatically in the profiling context.
        if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) {
            collectHttpParam(request, span);
        }
        ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
        ContextManager.stopSpan();
        return ret;
    }

    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
                                      Class<?>[] argumentsTypes, Throwable t) {
        AbstractSpan span = ContextManager.activeSpan();
        span.log(t);
    }

    private void collectHttpParam(Request request, AbstractSpan span) {
        final Map<String, String[]> parameterMap = new HashMap<>();
        final org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
        final Parameters parameters = coyoteRequest.getParameters();
        for (final Enumeration<String> names = parameters.getParameterNames(); names.hasMoreElements(); ) {
            final String name = names.nextElement();
            parameterMap.put(name, parameters.getParameterValues(name));
        }

        if (!parameterMap.isEmpty()) {
            String tagValue = CollectionUtil.toString(parameterMap);
            tagValue = TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ?
                StringUtil.cut(tagValue, TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) :
                tagValue;
            Tags.HTTP.PARAMS.set(span, tagValue);
        }
    }
}

開(kāi)發(fā)完成攔截器后捏浊,一定要在類路徑上添加skywalking-plugin.def文件懂衩,將開(kāi)發(fā)后的全類名添加到配置。

xxxName = tk.shenyifeng.skywalking.plugin.RepladInstrumentation

如果jar 里面沒(méi)有這個(gè)文件,插件不會(huì)被Skywalking加載的浊洞。
最后將打包的jar 放到Skywalking的plugin或者activations目錄就可以了牵敷。

xml配置插件

<?xml version="1.0" encoding="UTF-8"?>
<enhanced>
    <class class_name="test.apache.skywalking.apm.testcase.customize.service.TestService1">
        <method method="staticMethod()" operation_name="/is_static_method" static="true"></method>
        <method method="staticMethod(java.lang.String,int.class,java.util.Map,java.util.List,[Ljava.lang.Object;)"
                operation_name="/is_static_method_args" static="true">
            <operation_name_suffix>arg[0]</operation_name_suffix>
            <operation_name_suffix>arg[1]</operation_name_suffix>
            <operation_name_suffix>arg[3].[0]</operation_name_suffix>
            <tag key="tag_1">arg[2].['k1']</tag>
            <tag key="tag_2">arg[4].[1]</tag>
            <log key="log_1">arg[4].[2]</log>
        </method>
        <method method="method()" static="false"></method>
        <method method="method(java.lang.String,int.class)" operation_name="/method_2" static="false">
            <operation_name_suffix>arg[0]</operation_name_suffix>
            <tag key="tag_1">arg[0]</tag>
            <log key="log_1">arg[1]</log>
        </method>
        <method
            method="method(test.apache.skywalking.apm.testcase.customize.model.Model0,java.lang.String,int.class)"
            operation_name="/method_3" static="false">
            <operation_name_suffix>arg[0].id</operation_name_suffix>
            <operation_name_suffix>arg[0].model1.name</operation_name_suffix>
            <operation_name_suffix>arg[0].model1.getId()</operation_name_suffix>
            <tag key="tag_os">arg[0].os.[1]</tag>
            <log key="log_map">arg[0].getM().['k1']</log>
        </method>
        <method method="retString(java.lang.String)" operation_name="/retString" static="false">
            <tag key="tag_ret">returnedObj</tag>
            <log key="log_map">returnedObj</log>
        </method>
        <method method="retModel0(test.apache.skywalking.apm.testcase.customize.model.Model0)"
          operation_name="/retModel0" static="false">
            <tag key="tag_ret">returnedObj.model1.id</tag>
            <log key="log_map">returnedObj.model1.getId()</log>
        </method>
    </class>
    
</enhanced>

通過(guò)xml配置可以省去編寫Java代碼,打包jar步驟法希。
xml規(guī)則

配置 說(shuō)明
class_name 需要被增強(qiáng)Class
method 需要被增強(qiáng)Method,支持參數(shù)定義
operation_name 操作名稱
operation_name_suffix 操作后綴枷餐,用于生成動(dòng)態(tài)operation_name
tag 將在local span中添加一個(gè)tag。key的值需要在XML節(jié)點(diǎn)上表示
log 將在local span中添加一個(gè)log苫亦。key的值需要在XML節(jié)點(diǎn)上表示
arg[n] 表示輸入的參數(shù)值毛肋。比如args[0]表示第一個(gè)參數(shù)
.[n] 當(dāng)正在被解析的對(duì)象是Array或List,你可以用這個(gè)表達(dá)式得到對(duì)應(yīng)index上的對(duì)象
.['key'] 當(dāng)正在被解析的對(duì)象是Map, 你可以用這個(gè)表達(dá)式得到map的key

在配置文件agent.config中添加配置:

plugin.customize.enhance_file=customize_enhance.xml的絕對(duì)路徑


引用資料
https://www.itmuch.com/skywalking/apm-customize-enhance-plugin/
https://skyapm.github.io/document-cn-translation-of-skywalking/zh/6.1.0/guides/Java-Plugin-Development-Guide.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屋剑,一起剝皮案震驚了整個(gè)濱河市润匙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唉匾,老刑警劉巖趁桃,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肄鸽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)油啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門典徘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人益咬,你說(shuō)我怎么就攤上這事逮诲。” “怎么了幽告?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵梅鹦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冗锁,道長(zhǎng)齐唆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任冻河,我火速辦了婚禮箍邮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叨叙。我一直安慰自己锭弊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布擂错。 她就那樣靜靜地躺著味滞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剑鞍,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天昨凡,我揣著相機(jī)與錄音,去河邊找鬼攒暇。 笑死土匀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的形用。 我是一名探鬼主播就轧,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼田度!你這毒婦竟也來(lái)了妒御?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镇饺,失蹤者是張志新(化名)和其女友劉穎乎莉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奸笤,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惋啃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了监右。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片边灭。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖健盒,靈堂內(nèi)的尸體忽然破棺而出绒瘦,到底是詐尸還是另有隱情,我是刑警寧澤扣癣,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布惰帽,位于F島的核電站,受9級(jí)特大地震影響父虑,放射性物質(zhì)發(fā)生泄漏该酗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一频轿、第九天 我趴在偏房一處隱蔽的房頂上張望垂涯。 院中可真熱鬧,春花似錦航邢、人聲如沸耕赘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)操骡。三九已至九火,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間册招,已是汗流浹背岔激。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留是掰,地道東北人虑鼎。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像键痛,于是被迫代替她去往敵國(guó)和親炫彩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容