本文中含有需要您注意的重要提示信息,忽略該信息可能對您的業務造成影響,請務必仔細閱讀。
在使用網絡質量分析器排查問題時可能需要把業務數據串聯起來。本文主要介紹在app中同時集成網絡質量分析器SDK和Trace SDK來實現數據的交互串聯。
前提條件
已開通Trace應用。具體操作,請參見開通Trace應用。
Trace應用開通后,記錄project、project關聯的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初始化
網絡質量探測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) } }
Trace SDK初始化
警告必須確保網絡探測SDK已經初始化,才能進行這一步。
Trace SDK基于OpenTelemetry SDK實現,使用方式可參考如下文檔:
Android請參見通過OpenTelemetry接入Android Trace數據。
iOS請參見通過OpenTelemetry接入iOS Trace數據。
示例代碼如下:
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())
變量說明
上文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分析中增加事件配置。
在Trace詳情頁簽中,單擊事件配置。
在Drilldown配置面板中,完成如下配置:
單擊添加字段,然后選擇attribute.detection.type
然后單擊添加事件,并選擇自定義HTTP鏈接
自定義名稱:建議輸入“網絡診斷”
協議:選擇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}}
是否轉碼:默認配置即可
打開新窗口:建議開啟
最后單擊確定按鈕保存配置
驗證配置
按照以上方式接入數據后,您可以運行一下代碼以便產生數據。數據上報成功后,在Trace詳情頁面看到以下頁面即接入成功。
其中:玩家連接、玩家登錄賬號、GET /mall/api/productcategory/list 是業務Trace數據,http是網絡探測數據。
在右側面板,鼠標放在“ping”字段上,會自動彈出一個浮層,單擊“網絡診斷”即可。
業務鏈路串聯示例
Trace SDK通過Span來跟蹤調用鏈路。在Trace SDK的設計理念中,每個Span表示應用程序執行路徑的一段操作。Span與Span之間通過TraceId、SpanId、ParentSpanId表示次序和依賴關系。如:
不同的Span通過同樣的TraceId可以關聯為一條Trace,即調用鏈路;
不同的Span通過ParentSpanId可以表示依賴關系,如:B的parentSpanId是A的spanId,則B是A的子操作。
這里舉一個調用鏈路的例子,供使用Trace SDK時作為參考。有以下玩家進入游戲的過程:
以上過程有以下特性:
“開始”表示用戶發起游戲連接起始,“結束”表示已經進入游戲,或游戲進入失敗
進入游戲成功的條件:網絡連接、資源下載、進入游戲環節都成功
每個環節都有主備鏈路,主鏈路調用失敗后會發起一次網絡探測
每個環節的調用順序是串聯,只有前一個環節成功后,才會調用下一個環節
每次“開始”,都是一次獨立的調用鏈路
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()