SLS:使用 OTel 官方 SDK 采集 Android、iOS Trace 数据实践

简介: 本文介绍了使用 OTel 官方 SDK 采集 Android、iOS Trace 数据实践。

1. OTel 简介

OTel 是 OpenTelemetry 的简称,OpenTelemetry 官方提供了 Android、iOS SDK 安装包。

OTel 主要做了什么?

  • 制定标准规范

  • 接收、处理、输出观测数据

  • 数据采集 SDK

  • 各种语言的 SDK

  • Instrumentation,开箱即用的数据采集器

OTel SDK 的特征:

  • 数据产生

只依赖API库,可以理解为面向API接口采集数据,不关注SDK的具体实现。业务方使用时,可以按需选择 SDK 库的具体实现。

  • API

    • Signal 协议的声明,如:Trace、Logs、Metrics,扩展性较好

    • scopes,包含作用域信息

    • context,可自动管理上下文

    • propagation,提供 context 传播机制

    • processor,支持对 Span 进行自定义处理

  • SDK

API 的具体实现,OTel 针对主流的语言实现了默认的SDK。OTel SDK 主要做的事情是实现对应Signal的协议,具备良好的扩展性,并基于OTLP协议默认实现了几个Exporters。

  • Exporter

数据导出,把采集的Span等数据导出到目标位置。

  • Instrumentation

具体要采集什么数据,一般需要通过Instrumentation实现。即:针对具体要采集的数据(如 HTTP ),把元数据按照通用的语义规范(semantic conventions),通过 OTel SDK 进行数据采集。

2. 准备工作

2.1 开通 Trace 实例

采集 Android/iOS Trace 数据时,需要先开通 Trace 服务。请参考创建 Trace 实例这篇文章开通 Trace 服务。

2.2 保留必要信息

Trace 服务开通后,需要保留以下信息,初始化 SDK 时会使用到后续也可以在 Trace 服务列表中找到相关信息。

  • instanceId:也叫实例ID。

  • project:Trace 实例关联的 Log Project。

  • endpoint:Log Project 所在的地域,在 Project 的项目概览页面可以看到该 Project 所在地域的服务入口

3. 接入 SDK

3.1 Android SDK 集成

在 Android 平台上,可以通过集成 OpenTelemetry Java SDK 来支持。

// BOM清单,用于同步依赖版本
implementation(platform("io.opentelemetry:opentelemetry-bom:1.22.0"))
// APIs
implementation("io.opentelemetry:opentelemetry-api")
implementation("io.opentelemetry:opentelemetry-context")
// API extensions,项目中使用到了 kotlin 时建议添加该依赖
implementation("io.opentelemetry:opentelemetry-extension-kotlin")
// SDKs
implementation('io.opentelemetry:opentelemetry-sdk')
// semantic conventions
implementation("io.opentelemetry:opentelemetry-semconv")
// exporters
// 官方 gRPC exporter
implementation("io.opentelemetry:opentelemetry-exporter-otlp")

Trace 数据的上报需要申请网络权限,请在 AndroidManifest.xml 文件中加入以下权限声明。

接下来,需要完成 OTel SDK 的初始化,一般建议在 Application 类的 onCreate 方法中对 SDK 进行初始化。

// 初始化 exporter。exporter 的主要作用是导出 trace 数据到 SLS logstore。
OtlpGrpcSpanExporter grpcSpanExporter = OtlpGrpcSpanExporter.builder()
    .setEndpoint("https://${endpoint}") // 注意,该endpoint需要带端口号
    .addHeader("x-sls-otel-project", "${project}")
    .addHeader("x-sls-otel-instance-id", "${instanceId}")
    .addHeader("x-sls-otel-ak-id", "${access-key-id}")
    .addHeader("x-sls-otel-ak-secret", "${access-key-secret}")
    .build();

// 初始化 tracer provider。tracer provider 暴露一些主要的API,用于对 Span 预处理,自定义Clock,
// 自定义TraceId、SpanId生成规则,自定义采样器等。可以根据实际的需要进行配置
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
    .addSpanProcessor(BatchSpanProcessor.builder(grpcSpanExporter).build())
    .setResource(Resource.create(Attributes.builder()
     .put(ResourceAttributes.SERVICE_NAME, "${service}")
     .put(ResourceAttributes.SERVICE_NAMESPACE, "${service.namespace}")
     .put(ResourceAttributes.SERVICE_VERSION, "${version}")
     .put(ResourceAttributes.HOST_NAME, "${host}")
     .put(ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "${environment}")
     .build()
    )
    )
    .build();

// 初始化 OpenTelemetrySdk。
OpenTelemetrySdk telemetrySdk = OpenTelemetrySdk.builder()
    .setTracerProvider(tracerProvider)
    .build(); // 如果通过 build() 方法生成实例,需要根据实际使用情况来决定是否对telemetrySdk进行全局持有

// 也可以初始化一个全局的 OpenTelemetrySdk
// OpenTelemetrySdk telemetrySdk = OpenTelemetrySdk.builder()
//    .setTracerProvider(tracerProvider)
//    .buildAndRegisterGlobal(); 
// 后续可通过 GlobalOpenTelemetry.get() 或 GlobalOpenTelemetry.getTracer() 进行访问

${endpoint}、${project} 等变量的说明,参见通过《OpenTelemetry接入Android Trace数据》文档。

3.2 iOS SDK 集成

在 iOS 平台上,可以通过 OpenTelemetry Swift SDK 来支持。如果您的 iOS 项目主要使用 ObjC 语言,还需要引用 opentelemetry-objc-extension SDK。opentelemetry-objc-extension SDK 是 SLS 团队基于 opentelemetry-swift SDK 开发的非官方扩展。

通过 Xcode 集成

在 Xcode 中点开 File 菜单,然后选择 Add Package,在弹出的 Package 添加窗口中输入以下链接。

https://github.com/open-telemetry/opentelemetry-swift
// ObjC 项目还要引入 opentelemetry-objc-extension
https://github.com/aliyun-sls/opentelemetry-objc-extension

接下来,选择您要使用的版本。对于新接入的项目,我们建议使用最新版本的 OpenTelemetry。

通过 Package.swift 集成

要通过Package.swift 清单将 OpenTelemetry 集成到 Swift 软件包,您可以将 OpenTelemetry 添加到软件包的 depenencies 数组中。如需了解详情,请参阅 Swift Package Manager 文档。

dependencies: [

    .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.4.0"),
    // ...
],

然后,在任何依赖 OpenTelemetry 的 target 中,将 OpenTelemetry 添加到该 target 的 dependencies 数组中。

.target(
    name: "YourTargetName", 
    dependencies: ["OpenTelemetryApi"] // 或者 dependencies: ["OpenTelemetrySdk"] 
)

接下来,需要完成 OTel SDK 的初始化。一般建议在 AppDelegate 类的 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法中对 SDK 进行初始化。

Swift 项目初始化

// 导入以下模块
import GRPC
import NIO
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporter
import StdoutExporter
import URLSessionInstrumentation

// 初始化 exporter。exporter 的主要作用是导出 trace 数据到 SLS logstore。
let client = ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1))
.connect(host: "cn-beijing.log.aliyuncs.com", port: 10010)    
let otlpTraceExporter = OtlpTraceExporter(
    channel: client,
    config: OtlpConfiguration(
        timeout: OtlpConfiguration.DefaultTimeoutInterval,
        headers:
        [
            ("x-sls-otel-project", "${project}"),
            ("x-sls-otel-instance-id", "${instanceId}"),
            ("x-sls-otel-ak-id", "${access-key-id}"),
            ("x-sls-otel-ak-secret", "${access-key-secret}")
        ]
    )
)

// 初始化 tracer provider。tracer provider 暴露一些主要的API,用于对 Span 预处理,自定义Clock,
// 自定义TraceId、SpanId生成规则,自定义采样器等。可以根据实际的需要进行配置。
let spanExporters = MultiSpanExporter(spanExporters: [StdoutExporter(isDebug: true), otlpTraceExporter])
let spanProcessor = SimpleSpanProcessor(spanExporter: spanExporters)
OpenTelemetry.registerTracerProvider(
    tracerProvider: TracerProviderBuilder()
    .add(spanProcessor: spanProcessor)
    .with(resource: 
          Resource(attributes:
                   [
                         ResourceAttributes.serviceName.rawValue: AttributeValue.string("${service}"),
                         ResourceAttributes.serviceNamespace.rawValue: AttributeValue.string("${service.namespace}"),
                         ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("${version}"),
                         ResourceAttributes.hostName.rawValue: AttributeValue.string("${host}"),
                         ResourceAttributes.deploymentEnvironment.rawValue: AttributeValue.string("${environment}"),
                   ]
                  )
         )
    .build()
)

// 配置其他的 Instrumentation 采集器。根据实际业务需要进行配置。如下配置是开启采集 NSUrlSession 网络库的请求信息。具体配置参考下文。
URLSessionInstrumentation(configuration: URLSessionInstrumentationConfiguration(
    shouldInstrument: { request in
        return true
    })
)
let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "OTel Application", instrumentationVersion: "1.0.0")

ObjC 项目初始化

// 导入以下模块
@import OpenTelemetryApiObjc;
@import OpenTelemetrySdkObjc;
@import OpenTelemetryProtocolExporterObjc;
@import URLSessionInstrumentationObjc;
@import StdoutExporterObjc;

// 初始化 exporter。exporter 的主要作用是导出 trace 数据到 sls logstore。
// ${endpoint}、${port}、${project} 等变量的说明,参见下表变量说明。
NSDictionary *headers = @{
    @"x-sls-otel-project": @"${project}",
    @"x-sls-otel-instance-id": @"${instanceId}",
    @"x-sls-otel-ak-id": @"${access-key-id}",
    @"x-sls-otel-ak-secret": @"${access-key-secret}"
};
OtlpConfigurationObjc *configuration = [OtlpConfigurationObjc configuration:OtlpConfigurationObjc.DefaultTimeoutInterval headers:headers];
OtlpTraceExporterObjc *exporter = [OtlpTraceExporterObjc exporter:@"https://${endpoint}"
                                   port:@"${port}"
                                   configuration:configuration
];

// 初始化 tracer provider。tracer provider 暴露一些主要的API,用于对 Span 预处理,自定义Clock,
// 自定义TraceId、SpanId生成规则,自定义采样器等。可以根据实际的需要进行配置
NSArray *exporters = @[
    [StdoutExporterObjc stdoutExporter:true], // 开发调试时建议开启,可在console中打印出Span的具体的内容
    exporter
];
SpanProcessorObjc *spanProcessor = [BatchSpanProcessorObjc processor: [MultiSpanExporterObjc multiSpanExporter:exporters]];

TracerProviderBuilderObjc *providerBuilder = [TracerProviderBuilderObjc builder];
NSDictionary *resources = @{
    ResourceAttributesObjc.serviceName: [AttributeValueObjc string:@"${service}"], // 当前要追踪trace的服务名称,建议取模块的名称,如:首页、会员中心
    ResourceAttributesObjc.serviceNamespace: [AttributeValueObjc string:@"${service.namespace}"], // 建议固定为 iOS/iPadOS/macOS/tvOS/watchOS
    ResourceAttributesObjc.serviceVersion: [AttributeValueObjc string:@"${version}"], // 建议和 App 版本号保持一致
    ResourceAttributesObjc.hostName: [AttributeValueObjc string:@"${host}"], // 建议固定为iOS
    ResourceAttributesObjc.deploymentEnvironment: [AttributeValueObjc string:@"${environment}"] // 开发环境,一般开发阶段建议设置为dev,线上环境设置为prod
};
providerBuilder = [providerBuilder withResource: [ResourceObjc resource:resources]];
providerBuilder = [providerBuilder addSpanProcessor: spanProcessor];

// 初始化 OpenTelemetrySdk。
[OpenTelemetryObjc registerTracerProvider:[providerBuilder build]];

// 配置其他的 Instrumentation 采集器。根据实际业务需要进行配置。如下配置是开启采集NSUrlSession网络库的请求信息。具体配置参考下文。
[URLSessionInstrumentationObjc urlSessionInstrumentation:
    [URLSessionInstrumentationConfigurationObjc urlSessionInstrumentationConfiguration:[TestURLSessionInstrumentation instrumentation]]
];

${endpoint}、${project} 等变量的说明,参见通过《OpenTelemetry接入iOS Trace数据》文档。

4. 生成 Trace 数据

Trace 数据通常也叫做追踪数据,即 Tracing。Tracing 通常由一组逻辑操作(Span)构成。Span 代表了事务中的操作,每个 Span 会封装操作名称、起止时间戳、属性、事件、父 Span 等信息。

4.1 创建 Tracer

在生成 Trace 数据之前,我们需要先创建一个 Tracer。

Java 示例:

Tracer tracer = telemetrySdk.getTracer("OTel App", "1.0.0");

ObjC 示例:

TracerObjc *tracer = [OpenTelemetryObjc.instance.tracerProvider get:@"OTel App" instrumentationVersion:@"1.0"]

Swift 示例:

let tracer: Tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "OTel App", instrumentationVersion: "1.0.0")

建议根据不同的业务场景来创建 Tracer。创建 Tracer 时需要传入 instrumentation scope name。不同的业务场景传入不同的 Instrumentation name 有利于对 Trace 数据按照 scope 进行区分。

4.2 创建常规 Span

如上图,是一个包含时间跨度信息(也指操作执行耗时信息,即:487.56ms),操作名称是“operation name”,来源服务是 Android,操作类型是 client 的 Trace。这种类型的 Trace 该如何生成?

Java 示例:

final Span span = tracer
    .spanBuilder("operation name")
    .setSpanKind(SpanKind.CLIENT)
    .startSpan();
// do stuff
// ...
span.end();

ObjC 示例:

SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"operation name"];
SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
// do stuff
// ...
[span end];

Swift 示例:

let spanBuilder = tracer.spanBuilder(spanName: "operation name")
let span = spanBuilder .setSpanKind(spanKind: .client).startSpan()
// do stuff
// ...
span.end()

以上示例中的 spanKind 建议根据实际情况设置。OTel SDK 中,spanKind 的默认值是 internal。

4.3 给 Span 带上属性、事件、状态

如上图:

  • 这条 Trace 数据的状态是 ERROR

  • 属性中携带item_id、product_id、shop_id 三个信息

  • 日志中包含了一条名为“item id is empty”的事件,扩展信息中包含 item_id 信息

我们看下如何产生这种类型的 Trace 数据。

Java 示例:

final Span span = tracer
            .spanBuilder("operation name")
            .setSpanKind(SpanKind.CLIENT)
    // 设置属性信息
    // 也可通过下面的方式设置属性信息:
    // span.setAttribute("product_id", 202302210002L)
            .setAttribute("product_id", 202302210002L)
            .setAttribute("shop_id", 201708080003L)
            .setAttribute("item_id", 202302220001L)
            .startSpan();
// do stuff
// ...
// 设置事件信息
span.addEvent("item id is empty", Attributes.of(AttributeKey.longKey("item_id"), 202302220001L));
// 设置状态信息
span.setStatus(StatusCode.ERROR, "item id is empty");

span.end();

ObjC 示例:

SpanBuilderObjc *builder = [[_tracer spanBuilder:@"operation name"] setSpanKind:SpanKindObjc.CLIENT];
// 设置属性信息
// 也可通过下面的方式设置属性信息:
// [span setAttribute:@"product_id" doubleValue:202302210002L)];
[builder setAttribute:@"product_id" doubleValue:202302210002L];
[builder setAttribute:@"shop_id" doubleValue:201708080003L];
[builder setAttribute:@"item_id" doubleValue:202302220001L];
SpanObjc *span = [builder startSpan];
// do stuff
// ...
// 设置事件信息
[span addEvent:@"item id is empty" attributes:@{
    @"item_id": [AttributeValueObjc double:202302220001L]
}];
// 设置状态信息
[span setStatus:[StatusObjc error: @"item id is empty"]];

[span end];

Swift 示例:

let span = tracer.spanBuilder(spanName: "operation name")
    .setSpanKind(spanKind: .client)
    // 设置属性信息
    // 也可通过下面的方式设置属性信息:
    // span.setAttribute(key: "product_id", value: AttributeValue.double(202302210002))
    .setAttribute(key: "product_id", value: 202302210002)
    .setAttribute(key: "shop_id", value: 201708080003)
    .setAttribute(key: "item_id", value: 202302220001)
    .startSpan()
// do stuff
// ...
// 设置事件信息
span.addEvent(name: "item id is empty", attributes: ["item_id": AttributeValue.double(0)])
// 设置状态信息
span.status = .error(description: "item id is empty")
span.end()

4.4 创建嵌套 Span

很多时候,一个操作会包含多个子操作,甚至子操作也会包含多个子操作。我们可以通过手动关联 Span,也可以通过 OTel 提供的 Api 自动关联。

Java 示例:

// 手动链接示例
void methodA() {
    final Span parent = tracer
                .spanBuilder("parent operation")
                .setSpanKind(SpanKind.CLIENT)
                .startSpan();
    methodB(parent);
    parent.end();
}

void methodB(Span parent) {
    final Span child = tracer
                .spanBuilder("child operation")
                .setSpanKind(SpanKind.CLIENT)
    // 手动链接
    .setParent(Context.current().with(parent))
                .startSpan();
    // do stuff
    // ...
    child.end();
}

// 自动链接示例
void methodA() {
    final Span parent = tracer
                .spanBuilder("parent operation")
                .setSpanKind(SpanKind.CLIENT)
                .startSpan();
    try (Scope ingored = parent.makeCurrent()) {
    methodB();
    } finally {
    parent.end();
    }
}

void methodB() {
    final Span child = tracer
                .spanBuilder("child operation")
                .setSpanKind(SpanKind.CLIENT)
                .startSpan();
    // do stuff
    // ...
    child.end();
}

ObjC 示例:

// 手动关联
- (void) methodA {
    SpanObjc *span = [[[_tracer spanBuilder:@"operation methodA"] setSpanKind:SpanKindObjc.CLIENT] startSpan];
    [self methodB: span];
    [span end];
}

- (void) methodB: (SpanObjc *)parent {
    SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"operation name"];
    [spanBuilder setParent: parent];
    SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
    // do stuff
    // ...
    [span end];
}

// 自动关联
- (void) methodA {
    SpanObjc *span = [[[_tracer spanBuilder:@"operation methodA"] setSpanKind:SpanKindObjc.CLIENT] startSpan];
    [OpenTelemetryObjc.instance.contextProvider setActiveSpan:span];
    [self methodB];
    // do stuff
    // ...
    [span end];
}

- (void) methodB {
    SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"operation name"];
    SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
    // do stuff
    // ...
    [span end];
}

Swift 示例:

// 手动关联
func methodA() {
    let span = tracer.spanBuilder(spanName: "operation methodA").setSpanKind(spanKind: .client).startSpan()
    method(span)
    span.end()
}

func methodB(_ parent: Span) {
    let spanBuilder = tracer.spanBuilder(spanName: "operation methodB")
    spanBuilder.setParent(parent)
    let child = spanBuilder .setSpanKind(spanKind: .client).startSpan()
    // do stuff
    // ...
    child.end()
}

// 自动关联
- (void) methodA {
    SpanObjc *span = [[[_tracer spanBuilder:@"operation methodA"] setSpanKind:SpanKindObjc.CLIENT] startSpan];
    [OpenTelemetryObjc.instance.contextProvider setActiveSpan:span];
    [self methodB];
    // do stuff
    // ...
    [span end];
}

- (void) methodB {
    SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"operation name"];
    SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
    // do stuff
    // ...
    [span end];
}

4.5 传播上下文信息

OTel 提供了一种使用文本传播上文信息的方法。基于这种方法可以使用 W3C Trace Context HTTP 头信息在客户端与微服务之间传递上下文信息,从而串联起端到端之间的调用。如下图:

Java 示例:

// 基于 HttpURLConnection Header setter
TextMapSetter setter =
    new TextMapSetter() {
        @Override
        public void set(HttpURLConnection carrier, String key, String value) {
            // Insert the context as Header
            carrier.setRequestProperty(key, value);
        }
    };

URL url = new URL("http://127.0.0.1:8088/resource/catalog");
Span httpSpan = tracer.spanBuilder("GET /resource/catalog").setSpanKind(SpanKind.CLIENT).startSpan();
try (Scope scope = httpSpan.makeCurrent()) {
    // 注入属性信息,记录HTTP请求的相关详情
    httpSpan.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
    httpSpan.setAttribute(SemanticAttributes.HTTP_URL, url.toString());
    HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();
    // 把当前的 Context 信息注入到 HTTP request 中
    telemetrySdk.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);
    // do stuff
} finally {
    httpSpan.end();
}

ObjC 示例:

// 在SDK初始化时,需要完成 URLSessionInstrumentationObjc 的初始化
// 初始化 URLSessionInstrumentationObjc 时,需要传入一个实现了URLSessionInstrumentationConfigurationImpl协议的类(可参考后文的TestURLSessionInstrumentation实现)
[URLSessionInstrumentationObjc urlSessionInstrumentation:
     [URLSessionInstrumentationConfigurationObjc urlSessionInstrumentationConfiguration:[TestURLSessionInstrumentation instrumentation]]
];

// TestURLSessionInstrumentation 的实现,供参考
#pragma mark - URLSessionInstrumentation
@interface TestURLSessionInstrumentation: NSObject
+ (instancetype) instrumentation;
@end

@implementation TestURLSessionInstrumentation
+ (instancetype) instrumentation {
    return [[TestURLSessionInstrumentation alloc] init];
}
- (void)createdRequest:(NSURLRequest * _Nonnull)request span:(SpanObjc * _Nonnull)span {
    // request 被创建时回调
    [span setAttribute:@"createdRequest" stringValue:@"request created"];
}
- (void)injectCustomHeaders:(NSURLRequest * _Nonnull)request span:(SpanObjc * _Nullable)span {
    // 注入自定义请求头
    [(NSMutableURLRequest *)request addValue:@"custom header" forHTTPHeaderField:@"injectCustomHeaders"];
}
- (NSString * _Nullable)nameSpan:(NSURLRequest * _Nonnull)request {
    // 重命名 Span
    if ([request.URL.host containsString:@"dns.alidns.com"]) {
        return @"请求解析DNS";
    }
    return nil;
}
- (void)receivedError:(NSError * _Nonnull)error dataOrFile:(NSObject * _Nullable)dataOrFile span:(SpanObjc * _Nonnull)span {
    // 接口失败时回调
}
- (void)receivedResponse:(NSURLResponse * _Nonnull)response dataOrFile:(NSObject * _Nullable)dataOrFile span:(SpanObjc * _Nonnull)span {
    // 接口成功时回调
    NSLog(@"receivedResponse: %@", dataOrFile);
}
- (BOOL)shouldInjectTracingHeaders:(NSURLRequest * _Nonnull)request {
    // 注入 tracing headers
    return YES;
}
- (BOOL)shouldInstrument:(NSURLRequest * _Nonnull)request {
    // 是否采集该request的数据
    return YES;
}
- (BOOL)shouldRecordPayload:(NSURLSession * _Nonnull)session {
    // 是否采集请求体信息
    return YES;
}
- (void)spanCustomization:(NSURLRequest * _Nonnull)request spanBuilder:(SpanBuilderObjc * _Nonnull)spanBuilder {
    // 自定义 Span 信息
    [spanBuilder setAttribute:@"spanCustomization" stringValue:@"customize span"];
}
@end

Swift 示例:

// 在SDK初始化时,需要完成 URLSessionInstrumentation 的初始化
// Swift 的初始化相对简单些,可以按需传入对应的参数
URLSessionInstrumentation(
    configuration: URLSessionInstrumentationConfiguration(
        shouldInstrument: { request in
                             // 是否采集该request的数据
                             return true
                          }
    )
)

5. 分析 Trace 数据

5.1 查询 Android、iOS Trace 数据

Trace 数据经过 OTel SDK 采集到日志服务之后,数据会存储在 SLS 对应的 Log Store 中。这些数据是经过协议化之后的原始数据,我们在进行分析时,直接对原始数据分析体感上并不友好。SLS Trace 服务提供了可视化的查询能力,我们可以在 Trace 分析页面中通过简单的操作,即可筛选出目标数据。

如上图,在 Trace 分析页面,通过筛选出 Service 字段的值,即可过滤出目标数据。其中,Service 字段需要与前文中接入 SDK 时配置的 ${service} 值一致。

还可以对 Attributes 进行过滤,如下图:

实际使用时,可以根据业务的需要对指定 attribute 进行过滤。

除此之外,还支持在高级查询中对 SpanKind 和 Resource 等进行过滤查询,如下图:

5.2 链路调用分析

在 Trace 分析页面,还可以直观看到每个 Span 之间的调用链路。找一个包含子 Span 的节点,然后进入 Trace 详情,如下图:

可以比较清晰的看到:

  • 每个操作的耗时信息

通过耗时信息,我们可以分析出整个调用链路中每个操作的耗时分布,进而找出对链路影响比较大的节点。

  • 操作之间的关联关系

  • 端与端之间的调用链路

5.3 耗时操作分析

通过慢 Trace 分析,可以自动的发现指定时间段内是否存在耗时异常的操作。

选取了 Service 和时间范围后,可以看到系统帮我们自动统计出了对应操作的平均响应时间、是否存在异常,并绘制出相关的趋势图。

系统也会自动识别出当前时间段内最耗时的 10 条 Trace,通过对应 Trace 的入口,可以直接跳转到对应的 Trace 详情。

通过 Trace 详情可以比较容易的知道当前耗时较长的操作所处的具体调用链路,方便进一步分析。

5.4 自定义分析

SLS Trace 服务支持使用 SQL92 语句对原始 Trace 日志进行自定义分析。例如:基于 Trace 数据做 UV、PV 的分析。

可以分别用下面两条 SQL 分析语句生成上图的结果:

用户数

* and service: Android | select 
    count(distinct json_extract(resource, '$["device.id"]')) as uv, 
    count(json_extract(resource, '$["device.id"]')) as pv
from log

用户数趋势

* and service: Android |  select 
    count(distinct json_extract(resource, '$["device.id"]')) as uv, 
    count(json_extract(resource, '$["device.id"]')) as pv,
    date_trunc('day', __time__) as day
from log
group by day

附:

阿里云 SLS 服务介绍

阿里云 SLS Trace服务介绍

联系阿里云 SLS



作者介绍
目录