本文介绍通过OpenTelemetry将iOS应用的Trace数据接入到日志服务的操作步骤。
前提条件
已创建Trace实例。更多信息,请参见创建Trace实例。
步骤一:集成SDK
Swift项目
导入opentelemetry-swift。
在Xcode中,选择File>Add Package。
在添加Package文本框中,输入如下链接。
https://github.com/open-telemetry/opentelemetry-swift
选择Dependency Rule为Exact Version,设置版本号为1.4.0。
更多信息,请参见OpenTelemetry iOS SDK Release。
选择Swift Products。
建议选择以下Products。
OpenTelemetryApi
OpenTelemetryProtocolExporter
OpenTelemetrySdk
URLSessionInstrumentation
StdoutExporter
ResourceExtension
关于Swift Products的更多信息,请参见附录:OpenTelemetry Products说明。
Objc项目
opentelemetry-swift对Objc语言的兼容较差,日志服务基于opentelemetry-swift实现了一个Objc扩展。Objc项目除了需要完成Swift项目的配置外,还需要增加opentelemetry-objc-extension。
完成Swift项目的配置。
导入opentelemetry-objc-extension。
在Xcode中,选择File>Add Package。
在添加Package文本框中,输入如下链接。
https://github.com/aliyun-sls/opentelemetry-objc-extension
选择Dependency Rule为Exact Version,输入版本号为1.0.0。
选择Objc Extension Products。
建议选择以下Products。
OpenTelemetryApiObjc
OpenTelemetryProtocolExporterObjc
OpenTelemetrySdkObjc
ResourceExtensionObjc
URLSessionInstrumentationObjc
StdoutExporterObjc
关于Objc Extension Products的更多信息,请参见附录:OpenTelemetry Products说明。
步骤二:初始化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数据到日志服务Logstore。
let client = ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1))
.connect(host: "${endpoint}", port: ${port})
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数据到日志服务Logstore。
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]]
];
变量说明如下表所示:
变量 | 说明 | 示例 |
| 服务入口是访问一个Project及其内部数据的URL,日志服务提供私网域名和公网域名。更多信息,请参见服务入口。
| cn-hangzhou.log.aliyuncs.com:10010 |
| 日志服务Project名称,更多信息,请参见管理Project。 | test-project |
| Trace服务实例ID。更多信息,请参见创建Trace实例。 | test-traces |
| AccessKey ID用于标识用户,更多信息,请参见访问密钥。 建议您遵循最小化原则,按需授予RAM用户必要的权限。关于授权的具体操作,请参见创建RAM用户及授权,RAM自定义授权示例。 | 无 |
| AccessKey Secret是用户用于加密签名字符串和日志服务用来验证签名字符串的密钥,必须保密。 | 无 |
| 服务归属的命名空间。 | order |
| 服务名,根据您的实际场景配置。 | payment |
| 服务版本号,建议按照 | v1.0.0 |
| 主机名。 | localhost |
| 部署环境,例如测试环境、生产环境。 | pre |
步骤三:使用SDK
创建Tracer
建议根据不同的业务场景创建Tracer。创建Tracer时,需要传入instrumentation scope name,利于按照scope区分不同的Trace数据。
Swift示例
let tracer: Tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "OTel Application", instrumentationVersion: "1.0.0")
ObjC示例
TracerObjc *tracer = [OpenTelemetryObjc.instance.tracerProvider getWithInstrumentationName:@"app" instrumentationVersion:@"1.0"];
创建基本Span
Span代表了事务中的操作,每个Span都封装了操作名称、起止时间戳、属性信息、事件信息和Context信息等。
Swift示例
let spanBuilder = tracer.spanBuilder(spanName: "operation name")
let span = spanBuilder .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];
创建嵌套Span
当您希望为嵌套操作关联Span时,OpenTelemetry支持在进程内和跨远程进程进行跟踪。例如针对methodA调用方法methodB ,您可以通过以下方式创建嵌套Span。
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()
}
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];
}
OpenTemetry API还提供了一种自动化的方式来传播parentSpan。
Swift示例
func methodA() {
let span = tracer.spanBuilder(spanName: "operation methodA").setSpanKind(spanKind: .client).startSpan()
OpenTelemetry.instance.contextProvider.setActiveSpan(span)
methodB(span)
span.end()
}
func methodB(_ parent: Span) {
let child = tracer.spanBuilder(spanName: "operation methodB").setSpanKind(spanKind: .client).startSpan()
// do stuff
// ...
child.end()
}
ObjC示例
- (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];
}
创建带属性的Span
您可以通过属性在Span上提供特定操作的上下文信息。例如执行结果、关联的其他业务信息等,示例如下所示。
Swift示例
let spanBuilder = tracer.spanBuilder(spanName: "GET /resource/catalog")
let span = spanBuilder .setSpanKind(spanKind: .client).startSpan()
span.setAttribute(key: "http.method", value: "GET")
span.setAttribute(key: "http.url", value: "http url")
ObjC示例
SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"GET /resource/catalog"];
SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
[span setAttribute:@"http.method" stringValue:@"GET"];
[span setAttribute:@"http.url" stringValue:request.URL.baseURL];
创建带事件的Span
您可以通过携带多个事件的方式对Span进行注释。
Swift示例
span.addEvent(name: "start")
// do stuff
// ...
span.addEvent(name: "start")
// 也可以携带属性。
span.addEvent(name: "event with attributes",
attributes: [ "key1": AttributeValue.string("value1"),
"key2": AttributeValue.double(2.2)
]
)
ObjC示例
[chilsSpan addEvent:@"start"];
// do stuff
// ...
[chilsSpan addEvent:@"end"];
// 也可以携带属性。
[chilsSpan addEvent:@"event with attributes" attributes:@{
@"key1": [AttributeValueObjc string:@"value1"],
@"key2": [AttributeValueObjc double:1.1],
}];
创建带链接的Span
一个 Span可以链接一个或多个因果相关的其他Span。
Swift示例
spanBuilder.addLink(spanContext: parent.context)
let span = spanBuilder .setSpanKind(spanKind: .client).startSpan()
ObjC示例
SpanBuilderObjc *spanBuilder = [_tracer spanBuilder:@"Another"];
[spanBuilder addLink:parent.context];
SpanObjc *span = [[spanBuilder setSpanKind:SpanKindObjc.CLIENT] startSpan];
从远程进程中读取上下文信息的具体操作,请参见Context Propagation。
给Span添加状态
Span包含StatusCode.UNSET
、StatusCode.OK
、StatusCode.ERROR
三个状态,分别表示默认状态、成功状态、操作包含错误。例如参考如下示例,为Span添加操作包含错误的状态。
Swift示例
let span = tracer.spanBuilder(spanName: "operation name").startSpan()
do {
try expression
// do stuff
// ...
} catch {
span.status = .error(description: "\(error)")
}
ObjC示例
SpanObjc *span = [[_tracer spanBuilder:@"operation name"] startSpan];
@try {
// do stuff
// ...
} @catch (NSException *exception) {
[span setStatus:[StatusObjc error: exception.description]];
} @finally {
[span end];
}
传播上下文信息
OpenTelemetry提供了一种基于文本的方法,传播上下文信息。以下是使用 NSURLSession
发出HTTP请求的示例。
Swift示例
// 在SDK初始化时,需要完成URLSessionInstrumentation的初始化。
// 按需传入对应的参数。
URLSessionInstrumentation(
configuration: URLSessionInstrumentationConfiguration(
shouldInstrument: { request in
// 是否采集该request的数据。
return true
}
)
)
ObjC示例
// 在SDK初始化时,需要完成URLSessionInstrumentationObjc的初始化。
// 初始化URLSessionInstrumentationObjc时,需要传入一个实现了URLSessionInstrumentationConfigurationImpl协议的类(可参见下面的TestURLSessionInstrumentation实现)
[URLSessionInstrumentationObjc urlSessionInstrumentation:
[URLSessionInstrumentationConfigurationObjc urlSessionInstrumentationConfiguration:[TestURLSessionInstrumentation instrumentation]]
];
// TestURLSessionInstrumentation的实现示例。
#pragma mark - URLSessionInstrumentation
@interface TestURLSessionInstrumentation: NSObject<URLSessionInstrumentationConfigurationImpl>
+ (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
目前,OpenTelemetry SDK支持按照W3C Trace Context标准传播上下文信息。更多信息,请参见W3CTraceContextPropagator类。
关于OpenTelemetry SDK的更多信息,请参见官方文档。
附录:OpenTelemetry Products说明
Swift | Objc | 用途 |
OpenTelemetryApi | OpenTelemetryApiObjc | OpenTelemetry API的约定和最小实现。 |
OpenTelemetrySdk | OpenTelemetrySdkObjc | OpenTelemetry API的最小实现。 |
OpenTelemetryProtocolExporter | OpenTelemetryProtocolExporterObjc | OpenTelemetry Protocol实现。 |
StdoutExporter | StdoutExporterObjc | 标准输出Exporter,用于将Trace数据打印在console。 |
ResourceExtension | ResourceExtensionObjc | Resource的扩展采集实现。 |
URLSessionInstrumentation | URLSessionInstrumentationObjc | URLSession网络库的自动采集实现。 |
常见问题
问题:使用Swift SDK后,无数据上报到日志服务。
解决方法:当前SDK需要通过OtlpTraceExporter进行数据上报,请确认OtlpTraceExporter的配置是否正确。以下为正确配置的示例。
重要host
中不能添加http://
或https://
。port
必须设置为10010。
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", "your trace project"), ("x-sls-otel-instance-id", "your trace instance id"), ("x-sls-otel-ak-id", "your access key id"), ("x-sls-otel-ak-secret", "your access key secret") ]))