通过OpenTelemetry上报Swift应用数据

通过OpenTelemetry为应用埋点并上报链路数据至可观测链路 OpenTelemetry 版后,可观测链路 OpenTelemetry 版即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetrySwift应用埋点并上报数据。

前提条件

获取接入点信息

  1. 登录ARMS控制台,在左侧导航栏单击接入中心

  2. 服务端应用区域单击OpenTelemetry卡片。

  3. 在弹出的OpenTelemetry面板中选择数据需要上报的地域。

    说明

    初次接入的地域将会自动进行资源初始化。

  4. 选择连接方式上报方式,然后复制接入点信息。

    • 连接方式:若您的服务部署在阿里云上,且所属地域与选择的接入地域一致,推荐使用阿里云内网方式,否则选择公网方式。

    • 上报方式:根据客户端支持的协议类型选择HTTPgRPC协议上报数据。

    image.png

示例Demo

本文将通过具体示例介绍如何通过OpenTelemetry上报Swift语言编写的macOS命令行应用数据,该方法同样适用于iOS应用。

示例代码仓库地址:opentelemetry-swift-demo

步骤一:创建应用并添加依赖

  1. 选择要创建的应用,例如macOSCommand Line Tool。

    Command Line Tool

  2. XCode中选择File > Add Packages...,然后在搜索框中输入https://github.com/open-telemetry/opentelemetry-swift,选择1.4.1版本。

    说明

    更多版本信息,请参考opentelemetry-swift releases信息

    opentelemetry-swift

  3. 选中所需的Package Products。

    本文中使用到的Package Products如下:Package Products

步骤二:OpenTelemetry初始化

  1. 创建用于导出观测数据的组件。

    从以下3种方法中选择1种上报Trace数据。

    • 方法一:通过gRPC协议上报Trace数据

      • 请分别将<gRPC-endpoint><gRPC-port>替换为从前提条件中获取的接入点信息,例如:host: "http://tracing-analysis-dc-hz.aliyuncs.com", port:8090

      • 请将<your-token>替换为从前提条件中获取的鉴权Token。

      let grpcChannel = ClientConnection(
          configuration: ClientConnection.Configuration.default(
              target: .hostAndPort("<gRPC-endpoint>", 8090), // 接入点填写示例:tracing-analysis-dc-hz.aliyuncs.com,不需要添加"http://"
              eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1)
          )
      )
      
      let otlpGrpcConfiguration = OtlpConfiguration(
          timeout: OtlpConfiguration.DefaultTimeoutInterval,
          headers: [
              ("Authentication","xxxxxx")
          ]
      
      )
      let otlpGrpcTraceExporter = OtlpTraceExporter(channel: grpcChannel, config: otlpGrpcConfiguration)
    • 方法二:通过HTTP协议上报Trace数据

      请将<HTTP-endpoint>替换为从前提条件中获取的接入点信息,例如:http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxx@xxxx_xxxx@xxxx/api/otlp/traces

      let url = URL(string: "<HTTP-endpoint>")
      let otlpHttpTraceExporter = OtlpHttpTraceExporter(endpoint: url!)
    • 方法三:在命令行输出Trace数据

      let consoleTraceExporter = StdoutExporter(isDebug: true)
  2. 获取用于创建SpanTracer。

    • 请将<your-service-name>替换为要上报的应用名,<your-host-name>替换为主机名。

    • 替换<trace-exporter>,根据步骤1上报Trace方法不同替换为不同值。

      • 方式一:<trace-exporter>替换为otlpGrpcTraceExporter

      • 方式二:<trace-exporter>替换为otlpHttpTraceExporter

      • 方式三:<trace-exporter>替换为consoleTraceExporter

    // 设置应用名与主机名
    let resource = Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string("<your-service-name>"),
        ResourceAttributes.hostName.rawValue: AttributeValue.string("<your-host-name>")
    ])
    
    // 配置TracerProvider
    OpenTelemetry.registerTracerProvider(tracerProvider: TracerProviderBuilder()
                                         .add(spanProcessor: BatchSpanProcessor(spanExporter: <trace-exporter>)) // 上报至可观测链路 OpenTelemetry 版
                                         .with(resource: resource)
                                         .build())
    
    // 获取tracer,用来创建Span
    let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "instrumentation-library-name", instrumentationVersion: "1.0.0")

步骤三:创建Span追踪链路数据

  1. 创建Span,为Span添加属性(Attribute)和事件(Event),并输出当前SpanTraceId。

    let span = tracer.spanBuilder(spanName: "first span").startSpan()
    // 设置属性
    span.setAttribute(key: "http.method", value: "GET")
    span.setAttribute(key: "http.url", value: "www.aliyun.com")
    let attributes = [
        "key": AttributeValue.string("value"),
        "result": AttributeValue.int(100)
    ]
    
    // your code...
    
    // 设置Event
    span.addEvent(name: "computation complete", attributes: attributes)
    
    // 打印TraceId
    print(span.context.traceId.hexString)
    
    // your code...
    
    // 结束当前span
    span.end()
  2. 创建嵌套的Span。

    let parentSpan = tracer.spanBuilder(spanName: "parent span").startSpan()
    
    // your code...
    
    let childSpan = tracer.spanBuilder(spanName: "child span").setParent(parentSpan).startSpan()
    
    // your code...
    
    childSpan.end()
    
    // your code...
    
    parentSpan.end()
  3. 启动应用。

    登录ARMS控制台后,在应用监控 > 应用列表页面选择目标应用,查看链路数据。

    说明

    语言列显示image图标的应用为接入应用监控的应用,显示-图标的应用为接入可观测链路 OpenTelemetry 版的应用。

步骤四:打通客户端与服务端应用链路

  1. 修改Header中的Trace透传格式。

    • 不同协议使用不同的HTTP Header向下游传递Trace上下文,如OpenTelemtry默认使用W3C Trace Context格式(也支持修改为其他格式),而Zipkin使用B3B3 Multi格式。关于透传格式的更多信息,请参见OpenTelemetry指定透传Header格式

    • 根据服务端使用的协议类型,在客户端中设置对应的透传格式(textPropagatorsbaggagePropagator),以实现iOS客户端应用与服务端应用链路打通:

      • 如果服务端使用OpenTelemetry默认的W3C Trace Context格式,客户端无需设置textPropagatorsbaggagePropagator。

      • 如果服务端使用ZipkinB3/B3 Multi格式,客户端的textPropagators需设置为B3Propagator,baggagePropagator设置为ZipkinBaggagePropagator。

        // 设置B3透传格式。
        OpenTelemetry.registerPropagators(textPropagators: [B3Propagator()],
                                          baggagePropagator: ZipkinBaggagePropagator())
      • 如果服务端使用Jaeger协议,客户端的textPropagators需设置为JaegerPropagator,baggagePropagator设置为JaegerBaggagePropagator。

        // 设置Jaeger透传格式。
        OpenTelemetry.registerPropagators(textPropagators: [JaegerPropagator()],
                                          baggagePropagator: JaegerBaggagePropagator())
      • 也可以同时设置多种Trace透传格式。

        // 同时使用W3C Trace Context、B3、Jaeger三种Trace透传格式。
        OpenTelemetry.registerPropagators(textPropagators: [W3CTraceContextPropagator(), 
                                                            B3Propagator(), 
                                                            JaegerPropagator()],
                                          baggagePropagator: W3CBaggagePropagator())
  2. 导入URLSessionInstrumentation。

    URLSessionInstrumentationOpenTelemetry提供的针对URLSession的自动埋点插件,可以自动拦截所有通过URLSession发出的网络请求并创建调用链。

    import URLSessionInstrumentation
    
    ...
    let networkInstrumentation = URLSessionInstrumentation(configuration: URLSessionInstrumentationConfiguration())
  3. 使用URLSession进行网络请求,访问服务端。

    let url = URL(string: "<服务端地址>")!
    let request = URLRequest(url: url)
    let semaphore = DispatchSemaphore(value: 0)
    
    let task = URLSession.shared.dataTask(with: request) { data, _, _ in
        if let data = data {
            let string = String(decoding: data, as: UTF8.self)
            print(string)
        }
        semaphore.signal()
    }
    task.resume()
    
    semaphore.wait()

    展开查看完整示例代码

    OpenTelemetryUtil.swift

    import Foundation
    
    import GRPC
    import NIO
    import OpenTelemetryApi
    import OpenTelemetrySdk
    import OpenTelemetryProtocolExporter
    import StdoutExporter
    
    class OpenTelemetryUtil {
        
        private static let tracerProvider: TracerProvider = {
            // 通过gRPC协议上报
            let grpcChannel = ClientConnection(
                configuration: ClientConnection.Configuration.default(
                    target: .hostAndPort("tracing-analysis-dc-hz.aliyuncs.com", 8090),
                    eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1)
                )
            )
            
    
            let otlpGrpcConfiguration = OtlpConfiguration(
                timeout: OtlpConfiguration.DefaultTimeoutInterval,
                headers: [
                    ("Authentication","${鉴权Token}")
                ]
    
            )
            let otlpGrpcTraceExporter = OtlpTraceExporter(channel: grpcChannel, config: otlpGrpcConfiguration)
            let consoleTraceExporter = StdoutExporter(isDebug: true)
            
            // 设置应用名与主机名
            let resource = Resource(attributes: [
                ResourceAttributes.serviceName.rawValue: AttributeValue.string("otel-swift-demo-grpc"),
                ResourceAttributes.hostName.rawValue: AttributeValue.string("adam.mac")
            ])
    
            let tracerProvider = TracerProviderBuilder()
                .add(spanProcessor: BatchSpanProcessor(spanExporter: otlpGrpcTraceExporter))
    //            .add(spanProcessor: BatchSpanProcessor(spanExporter: consoleTraceExporter)) // 控制台打印 Trace,调试时使用
                .with(resource: resource)
                .build()
            
            
            // 配置TracerProvider
            OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
            // 修改透传格式
            OpenTelemetry.registerPropagators(textPropagators: [W3CTraceContextPropagator(),
                                                                B3Propagator(),
                                                                JaegerPropagator()],
                                              baggagePropagator: W3CBaggagePropagator())
            
            return tracerProvider
            
        }()
        
        
        // 获取tracer,用来创建Span
        static func getTracer(name: String, version: String) -> Tracer {
            return tracerProvider.get(instrumentationName: name, instrumentationVersion: version)
        }
    
    }
    

    main.swift

    import Foundation
    
    
    import OpenTelemetryApi
    import OpenTelemetrySdk
    // 对网络请求进行自动埋点
    import URLSessionInstrumentation
    
    let tracer = OpenTelemetryUtil.getTracer(name: "ios-demo", version: "1.0.0")
    
    // 创建嵌套的Span
    let parentSpan = tracer.spanBuilder(spanName: "parent span").startSpan()
    
    let childSpan = tracer.spanBuilder(spanName: "child span").setParent(parentSpan).startSpan()
    
    // 设置属性
    childSpan.setAttribute(key: "http.method", value: "GET")
    childSpan.setAttribute(key: "http.url", value: "www.aliyun.com")
    let attributes = [
        "stringKey": AttributeValue.string("value"),
        "intKey": AttributeValue.int(100)
    ]
    // 设置Event
    childSpan.addEvent(name: "event", attributes: attributes)
    // 打印TraceId
    print(childSpan.context.traceId.hexString)
    
    // 发送网络请求访问服务端
    let networkInstrumentation = URLSessionInstrumentation(configuration: URLSessionInstrumentationConfiguration())
    
    let url = URL(string: "${服务端地址}")!
    let request = URLRequest(url: url)
    let semaphore = DispatchSemaphore(value: 0)
    
    let task = URLSession.shared.dataTask(with: request) { data, _, _ in
        if let data = data {
            let string = String(decoding: data, as: UTF8.self)
            print(string)
        }
        semaphore.signal()
    }
    task.resume()
    
    semaphore.wait()
    
    
    // 手动结束手动创建的 Span
    childSpan.end()
    parentSpan.end()
    
    sleep(60)
    
    print("end")
    
  4. 启动应用,在调用链分析页面查看客户端与服务端打通的调用链。

    如下图所示,HTTP GETiOS应用,zipkin-demo-server为服务端应用。

    image