本文介紹通過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") ]))