文档

关联网络质量分析器与Trace服务

更新时间:
重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

在使用网络质量分析器排查问题时可能需要把业务数据串联起来。本文主要介绍在app中同时集成网络质量分析器SDK和Trace SDK来实现数据的交互串联。

前提条件

已开通Trace应用。具体操作,请参见开通Trace应用

重要

Trace应用开通后,记录projectproject关联的endpoint实例ID,用于后续配置SDK参数时使用。

SDK集成

  • Android

    在app或module级别的build.gradle文件中加入以下配置内容:

    // sls android SDK,如果已经集成,可以不更新版本号
    implementation 'io.github.aliyun-sls:aliyun-log-android-sdk:2.7.0@aar'
    
    // 网络探测SDK
    implementation 'io.github.aliyun-sls:sls-android-network-diagnosis:2.2.1@aar'
    implementation 'io.github.aliyun-sls:sls-android-core:1.0.8@aar'
    implementation 'io.github.aliyun-sls:sls-android-ot:1.0.8.1@aar'
    
    // opentelemetry extension
    implementation 'io.github.aliyun-sls:android-otel-common:1.1.0@aar'
    implementation 'io.github.aliyun-sls:android-exporter-otlp:1.1.1@aar'
      
    // opentelemetry sdk
    implementation(platform("io.opentelemetry:opentelemetry-bom:1.30.0"))
    implementation("io.opentelemetry:opentelemetry-api")
    implementation("io.opentelemetry:opentelemetry-context")
    implementation('io.opentelemetry:opentelemetry-sdk')
    implementation("io.opentelemetry:opentelemetry-semconv:1.30.0-alpha")
  • iOS

    iOS新版本的依赖包与之前版本有所不同,建议按照以下方式导入依赖。

    在项目Podfile加入以下内容:

    use_frameworks!
    source 'https://gitee.com/aliyun-sls/Specs.git'
    target '${your_target}' do |t|
      # SLS iOS SDK
      pod 'AliyunLogProducer', '4.1.0'
      # OpenTelemetry Extension
      pod 'AliyunLogOTelCommon', '4.1.0'
      pod 'AliyunLogOtlpExporter', '4.1.0'
      # 网络探测SDK
      pod 'AliyunLogNetworkDiagnosis', '4.1.0'
    end

SDK初始化

  1. 网络质量探测SDK初始化

    具体操作,请参见SDK插件使用说明

    以下是初始化示例代码,供参考:

    Android

    Credentials credentials = new Credentials();
    NetworkDiagnosisCredentials networkDiagnosisCredentials = credentials.getNetworkDiagnosisCredentials();
    networkDiagnosisCredentials.endpoint = "${ipa_endpoint}";
    networkDiagnosisCredentials.project = "${ipa_project}";
    networkDiagnosisCredentials.secretKey = "${ipa_secretKey}"
    
    final OptionConfiguration optionConfiguration = configuration -> {
        configuration.enableNetworkDiagnosis = true;
    };
    
    // 预先初始化。在用户同意数据隐私合规之前调用
    SLSAndroid.preInit(context, credentials, optionConfiguration);
    
    // 用户同意数据隐私合规协议后,完整初始化
    SLSAndroid.initialize(context, credentials, optionConfiguration);
    
    // 注册SDK回调
    SLSAndroid.registerCredentialsCallback(new Callback() {
        @Override
        public void onCall(String feature, LogProducerResult result) {
            // 参数错误,或AK没有设置
            if (LogProducerResult.LOG_PRODUCER_SEND_UNAUTHORIZED == result ||
                LogProducerResult.LOG_PRODUCER_PARAMETERS_INVALID == result) {
                // 处理token过期,AK失效等鉴权类问题
                Credentials credentials = new Credentials();
                credentials.accessKeyId = "${ipa_accesskey_id}";
                credentials.accessKeySecret = "${ipa_accesskey_secret}";
                // credentials.securityToken = "${ipa_accesskey_token}";// 仅当通过STS方式获取的AK时才需要设置
    
                // 如果是仅更新 AK 的话,可以不对NetworkDiagnosisCredentials进行更新
                //NetworkDiagnosisCredentials networkDiagnosisCredentials = credentials
                // .getNetworkDiagnosisCredentials();
                // networkDiagnosisCredentials.endpoint = "${ipa_endpoint}";
                // networkDiagnosisCredentials.project = "${ipa_project}";
                // networkDiagnosisCredentials.secretKey = ""; // secretKey不支持动态更新
    
                SLSAndroid.setCredentials(credentials);
            }
        }
    });

    iOS

    // 导入依赖模块
    import AliyunLogNetworkDiagnosis
    
    let credentials = SLSCredentials()
    let networkDiagnosisCredentials = credentials.createNetworkDiagnosisCredentials()
    networkDiagnosisCredentials.endpoint = "${ipa_endpoint}"
    networkDiagnosisCredentials.project = "${ipa_project}"
    networkDiagnosisCredentials.secretKey = "${ipa_secretKey}"
    
    let configutaionHandler: ((SLSConfiguration) -> Void) = { configuration in
        configuration.enableNetworkDiagnosis = true
    }
    
    // 预先初始化。在用户同意数据隐私合规之前调用
    SLSCocoa.sharedInstance().preInit(credentials, configuration: configutaionHandler)
    // 用户同意数据隐私合规协议后,完整初始化
    SLSCocoa.sharedInstance().initialize(credentials, configuration: configutaionHandler)
    
    // 注册SDK回调
    SLSCocoa.sharedInstance().registerCredentialsCallback { feature, code in
        // 参数错误,或AK没有设置
        if ("LogProducerParametersInvalid" == code) {
            let credentials = SLSCredentials()
            let networkDiagnosisCredentials = credentials.createNetworkDiagnosisCredentials()
            networkDiagnosisCredentials.endpoint = "${ipa_endpoint}"
            networkDiagnosisCredentials.project = "${ipa_project}"
            // networkDiagnosisCredentials.secretKey 不支持动态更新
            
            credentials.accessKeyId = "${ipa_accesskey_id}"
            credentials.accessKeySecret = "${ipa_accesskey_secret}"
            // networkDiagnosisCredentials.securityToken = "${ipa_accesskey_token}" // 仅当通过STS方式获取的AK时才需要设置
            
            SLSCocoa.sharedInstance().setCredentials(credentials)
        }
        
        // AK过期或无效
        if ("LogProducerSendUnauthorized" == code) {
            let credentials = SLSCredentials()
            credentials.accessKeyId = "${ipa_accesskey_id}"
            credentials.accessKeySecret = "${ipa_accesskey_secret}"
            // credentials.securityToken = "${ipa_accesskey_token}" // 仅当通过STS方式获取的AK时才需要设置
            
            SLSCocoa.sharedInstance().setCredentials(credentials)
        }
    }
  2. Trace SDK初始化

    警告

    必须确保网络探测SDK已经初始化,才能进行这一步。

    示例代码如下:

    Android

    // 全局设置
    // 参数配置错误,或者AK访问失效时会回调以下方法
    ConfigurationManager.getInstance().setProvider(
        scope -> {
            if ("ipa".equalsIgnoreCase(scope)) {
                return AccessKey.accessKey(
                    "${ipa_accesskey_id}",
                    "${ipa_accesskey_secret}",
                    "${ipa_accesskey_token}" 
                );
            }
    
            if ("trace".equalsIgnoreCase(scope)) {
                AccessKey.accessKey(
                    "${trace_accesskey_id}",
                    "${trace_accesskey_secret}",
                    "${trace_accesskey_token}" 
                );
            }
    
            return null;
        },
        scope -> {
            if ("ipa".equalsIgnoreCase(scope)) {
                return Workspace.workspace(
                    "${ipa_endpoint}",
                    "${ipa_project}",
                    "ipa-${ipa_instanceId}-raw"
                );
            }
    
            if ("trace".equalsIgnoreCase(scope)) {
                return Workspace.workspace(
                    "${trace_endpoint}",
                    "${trace_project}",
                    "${trace_instanceId}-traces"
                );
            }
    
            return null;
        },
        scope -> {
            // 该配置用不到,直接返回null即可
            return null;
        }
    );
    
    // 初始化Trace Exporter,用于导出数据到Trace
    OtlpSLSSpanExporter exporter = OtlpSLSSpanExporter.builder()
        .setScope("trace")
        .setEndpoint("${trace_endpoint}")
        .setProject("${trace_project}")
        .setLogstore("${trace_instanceId}-traces")
        .setAccessKey(PreferenceUtils.getAccessKeyId(this), PreferenceUtils.getAccessKeySecret(this), null) // 初始化时可以先不设置AK
        .build();
    
    SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
        .addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
        .setResource(io.opentelemetry.sdk.resources.Resource.create(Attributes.builder()
            .put(ResourceAttributes.SERVICE_NAME, "network-Trace") // 建议根据实际业务情况填写
            .put(ResourceAttributes.SERVICE_NAMESPACE, "AndroidExamples") // 建议填写应用名称
            .put(ResourceAttributes.SERVICE_VERSION, BuildConfig.VERSION_NAME)
            .put(ResourceAttributes.HOST_NAME, Build.HOST)
            .put(ResourceAttributes.OS_NAME, "Android")
            .put(ResourceAttributes.OS_TYPE, "Android")
            .put(ResourceAttributes.DEVICE_ID, Utdid.getInstance().getUtdid(this)) // 设置设备Id
            .build()));
    
    // 初始化网络质量探测 Exporter,用于导出数据到网络质量探测
    NetworkDiagnosis.getInstance().setupTracer(builder);
    
    SdkTracerProvider tracerProvider = builder.build();
    
    OpenTelemetrySdk.builder()
        .setTracerProvider(tracerProvider)
        .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
        .buildAndRegisterGlobal();
    
    NetworkDiagnosis.getInstance().setOpenTelemetrySdk(GlobalOpenTelemetry.get());

    iOS

    // 导入依赖模块
    import AliyunLogOTelCommon
    import OpenTelemetryApi
    import OpenTelemetrySdk
    
    // 全局配置
    // 参数配置错误,或者AK访问失效时会回调以下方法
    ConfigurationManager.shared.setProvider (
        accessKeyProvider: { scope in
            // 根据scope的不同,返回对应的AccessKey,按照实际情况填写
            // 网络质量探测
            if ("ipa" == scope) {
                return AccessKey.`init`(
                    accessKeyId: "${ipa_accesskey_id}",
                    accessKeySecret: "${ipa_accesskey_secret}",
                    accessKeySecuritToken: "${ipa_accesskey_token}"
                )
            }
    
            // Trace
            if ("trace" == scope) {
                return AccessKey.`init`(
                    accessKeyId: "${trace_accesskey_id}",
                    accessKeySecret: "${trace_accesskey_secret}",
                    accessKeySecuritToken: "${trace_accesskey_token}"
                )
            }
            
            return nil
        },
        workspaceProvider: { scope in
            // 根据scope的不同,返回Workspace(包含endpoint、project、logstore)
            // 网络质量探测默认不用设置,如果需要动态更新endpoint、project等相关参数,则需要按照下述方式处理
            // if ("ipa" == scope) {
            //     return Workspace.`init`(
            //         endpoint: "${ipa_endpoint}",
            //         project: "${ipa_project}",
            //         instanceId: "ipa-\("${ipa_instanceId}")-raw"
            //     )
            // }
            
            // Trace 如果涉及到动态更新,按照以下方式设置
            if ("trace" == scope) {
                return Workspace.`init`(
                    endpoint: "${trace_endpoint}",
                    project: "${trace_project}",
                    instanceId: "\("${trace_instanceId}")-traces"
                )
            }
            
            return nil
        })
    
    // 初始化Trace Exporter,用于导出数据到Trace
    let exporter = OtlpSLSSpanExporter.builder("trace")
        .setEndpoint("${trace_endpoint}")
        .setProject("${trace_project}")
        .setLogstore("${trace_instanceId}-traces")
        .build()
    
    // 初始化网络质量探测 Exporter,用于导出数据到网络质量探测
    let ipaExporter = NetworkDiagnosisHelper.exporter()
    
    let spanExporters = MultiSpanExporter(spanExporters: [exporter, ipaExporter])
    let spanProcessor = BatchSpanProcessor(spanExporter: spanExporters)
    let tracerProviderBuilder = TracerProviderBuilder()
        .add(spanProcessor: spanProcessor)
        .with(resource: Resource(attributes: [
            ResourceAttributes.serviceName.rawValue: AttributeValue.string("iOS-Trace"), // 可根据业务情况填写
            ResourceAttributes.serviceNamespace.rawValue: AttributeValue.string("iOSExamples"), // 建议填写App名称
            ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("1.0.0"), // 建议填写App版本号
            ResourceAttributes.osName.rawValue: AttributeValue.string("iOS"), // 手机系统
            ResourceAttributes.deviceId.rawValue: AttributeValue.string(SLSUtdid.getUtdid()) // 设备Id
        ]))
    OpenTelemetry.registerTracerProvider(tracerProvider:tracerProviderBuilder.build())
  3. 变量说明

    上文SDK初始化过程使用到的变量说明如下:

    变量名

    描述

    示例

    ${ipa_endpoint}

    网络质量分析器Endpoint。

    https://cn-hagnzhou.log.aliyuncs.com

    ${ipa_project}

    选择目标Project。

    日志服务将自动在该Project下生成Logstore,用于存储网络探测数据。

    test-project

    ${ipa_secretKey}

    网络质量分析器SecretKey,接入端应用的密钥。更多信息,请参见使用网络质量分析器

    ey********************************************************************************************************************************************=

    ${ipa_accesskey_id}

    网络质量分析器AccessKey ID。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey(包括AccessKey ID和AccessKey Secret)。授予RAM用户向指定Project写入数据权限的具体操作,请参见RAM自定义授权场景。如何获取AccessKey的具体操作,请参见访问密钥

    LA*****************

    ${ipa_accesskey_secret}

    网络质量分析器AccessKey Secret。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey。

    fR*****************

    ${ipa_accesskey_token}

    网络质量分析器AccessKey SecurityToken。仅当使用STS进行临时访问时候才需要配置的。

    T****************v

    ${trace_endpoint}

    Trace实例对应的Endpoint。

    https://cn-hangzhou.log.aliyuncs.com

    ${trace_project}

    Trace实例对应的Project名称。

    test-project

    ${trace_instanceId}

    全栈可观测服务实例ID。更多信息,请参见创建Trace实例

    test-traces

    ${trace_accesskey_id}

    Trace AccessKey ID。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey(包括AccessKey ID和AccessKey Secret)。授予RAM用户向指定Project写入数据权限的具体操作,请参见RAM自定义授权场景。如何获取AccessKey的具体操作,请参见访问密钥

    LA*****************

    ${trace_accesskey_secret}

    Trace AccessKey Secret。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey。

    fR*****************

    ${trace_accesskey_token}

    Trace AccessKey SecurityToken。仅当使用STS进行临时访问时候才需要配置的。

    T****************v

    ${ipa_instanceId}

    网络质量分析器应用ID。在网络质量分析器应用中的接入端管理页面获取。

    Mq********************

Trace新增跳转配置

为了能够从Trace分析页面跳转到网络探测详情页面,需要在Trace分析中增加事件配置。

  1. 在Trace详情页签中,单击事件配置

    image.png

  1. Drilldown配置面板中,完成如下配置:

    1. 单击添加字段,然后选择attribute.detection.type

      image.png

    2. 然后单击添加事件,并选择自定义HTTP链接

      image.png

      • 自定义名称:建议输入“网络诊断”

      • 协议:选择HTTPS

      • 链接地址:输入以下固定链接

      • cms.console.aliyun.com/ipa?hide_sldebar=true&type=sls&page=detectionDetail&detectionType=${{attribute.detection.type}}&deviceId=${{attribute.detection.deviceId }}&traceId=${{attribute.detection.traceId}}&spanId=${{attribute.detection.spanId}}
      • 是否转码:默认配置即可

      • 打开新窗口:建议开启

    3. 最后单击确定按钮保存配置

  2. 验证配置

    按照以上方式接入数据后,您可以运行一下代码以便产生数据。数据上报成功后,在Trace详情页面看到以下页面即接入成功。

    image.png

    其中:玩家连接、玩家登录账号、GET /mall/api/productcategory/list 是业务Trace数据,http是网络探测数据。

    在右侧面板,鼠标放在“ping”字段上,会自动弹出一个浮层,单击“网络诊断”即可。

    image.png

业务链路串联示例

Trace SDK通过Span来跟踪调用链路。在Trace SDK的设计理念中,每个Span表示应用程序执行路径的一段操作。Span与Span之间通过TraceId、SpanId、ParentSpanId表示次序和依赖关系。如:

  • 不同的Span通过同样的TraceId可以关联为一条Trace,即调用链路;

  • 不同的Span通过ParentSpanId可以表示依赖关系,如:B的parentSpanId是A的spanId,则B是A的子操作。

这里举一个调用链路的例子,供使用Trace SDK时作为参考。有以下玩家进入游戏的过程:

image.png

以上过程有以下特性:

  • “开始”表示用户发起游戏连接起始,“结束”表示已经进入游戏,或游戏进入失败

  • 进入游戏成功的条件:网络连接、资源下载、进入游戏环节都成功

  • 每个环节都有主备链路,主链路调用失败后会发起一次网络探测

  • 每个环节的调用顺序是串联,只有前一个环节成功后,才会调用下一个环节

  • 每次“开始”,都是一次独立的调用链路

1. 开始

“开始”表示用户发起游戏连接,在这个业务流程中,“开始”表示一次完整业务调用的开始,因此需要针对“开始”这个操作进行埋点。

  • Android埋点如下:

    Tracer tracer = GlobalOpenTelemetry.get().getTracer("connect");
    final Span startSpan = tracer.spanBuilder("开始连接游戏").startSpan();
    // 由于后续流程的操作都需要和“开始”span关联,我们需要把它设为“活跃”状态,后续流程产生的Span就会和“开始”自动关联,建议通过以下方式:
    try (Scope ignored = span.makeCurrent()) {
        // 后续流程的调用放在这里
    } finally {
        startSpan.end();
    }
    
  • iOS埋点如下:

    // 构建一个tracer,该tracer为当前流程共用
    let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "connect", instrumentationVersion: "1.0.0")
    
    // 产生一个“开始”span
    let startSpan = tracer.spanBuilder(spanName: "开始连接游戏").startSpan()
    // 由于后续流程的操作都需要和“开始”span关联,我们需要把它设为“活跃”状态,后续流程产生的Span就会和“开始”自动关联
    OpenTelemetry.instance.contextProvider.setActiveSpan(span)

2. 网关链接

网关存在主备链路,当连接失败时,需要对当前链路发起一次网络探测。

  • Android示例代码:

    public void connectProxy() {
        final Span connectSpan = tracer.spanBuilder("连接网关")
                            .startSpan()
        // 后续主备线路的连接产生的span都作为connectSpan的子节点存在
        OpenTelemetry.instance.contextProvider.setActiveSpan(span)
        var success = connectProxy("主线路")
        // 如果连接失败,则链接备用线路
        success = connectProxy("备用线路1")
    
        // 不管连接成功或失败,都需要结束connectSpan
        connectSpan.end()
    }
    
    private boolean connectProxy(String line) {
      final Span proxySpan = tracer.spanBuilder("连接网关")
                            .setAttribute("proxy_line", line)
                            .startSpan();
      // 连接的具体过程省略
      // ...
      
      // 如果连接失败,需要发起tcp类型的网络探测
      try (Scope ignored = proxySpan.makeCurrent()) {
          TcpPingRequest request = new TcpPingRequest();
          request.domain = "www.aliyun.com";
          request.port = 8888;
          NetworkDiagnosis.getInstance().tcpPing(request);
      } finally {
          // 连接过程结束后,结束proxySpan
          proxySpan.end()
      }
      return true
    }
  • iOS:

    func connectProxy() {
        let connectSpan = tracer.spanBuilder(spanName: "连接网关").startSpan()
        // 后续主备线路的连接产生的span都作为connectSpan的子节点存在
        OpenTelemetry.instance.contextProvider.setActiveSpan(span)
        var success = connectProxy("主线路")
        // 如果连接失败,则链接备用线路
        success = connectProxy("备用线路1")
    
        // 不管连接成功或失败,都需要结束connectSpan
        connectSpan.end()
    }
    
    func connectProxy(line: String) -> Bool {
      let proxySpan = tracer.spanBuilder(spanName: "连接网关")
                            .setAttribute(key: "proxy_name", value: line)
                            .startSpan()
      // 连接的具体过程省略
      // ...
      
      // 如果连接失败,需要发起tcp类型的网络探测
      OpenTelemetry.instance.contextProvider.setActiveSpan(proxySpan) // 可选,如果增加这一行,网络探测的Span会作为proxySpan的子span存在
      let request = SLSTcpPingRequest()
      request.domain = "www.aliyun.com"
      request.port = 8888
      SLSNetworkDiagnosis.sharedInstance().tcpPing2(request)
      
      // 连接过程结束后,结束proxySpan
      proxySpan.end()
    
      return true
    }

3. 资源下载、进入游戏

与网关连接的操作相似,这里省略。

4. 结束

不管整个流程成功或是失败,结束流程都需要处理。

  • Android已经在 finally 代码块中结束了startSpan,不需要额外处理。

  • iOS:

    // 结束startSpan即可
    startSpan.end()

  • 本页导读 (1)
文档反馈