Sources/Instrumentation/AliyunLogURLSession/AliyunLogURLSessionInstrumentationConfiguration.swift (135 lines of code) (raw):
//
// Copyright 2023 aliyun-sls Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
//#if canImport(URLSessionInstrumentation)
import URLSessionInstrumentation
import OpenTelemetryApi
//#endif
public struct AliyunURLSessionInstrumentationConfiguration {
//#if canImport(URLSessionInstrumentation)
let configuration: URLSessionInstrumentationConfiguration?
//#endif
public var headersInWhiteList: [String]?
public var headersInBlackList: [String]?
public var shouldRecordRequestBody: ((URLRequest) -> Bool)?
public var shouldRecordResponseBody: ((URLResponse) -> Bool)?
//#if canImport(URLSessionInstrumentation)
public init(_ configuration: URLSessionInstrumentationConfiguration?,
_ headersInWhiteList: [String]?,
_ headersInBlackList: [String]?,
_ shouldRecordRequestBody: ((URLRequest) -> Bool)? = nil,
_ shouldRecordResponseBody: ((URLResponse) -> Bool)? = nil)
{
self.configuration = configuration
self.headersInBlackList = headersInBlackList
self.shouldRecordRequestBody = shouldRecordRequestBody
self.shouldRecordResponseBody = shouldRecordResponseBody
}
//#else
// public init(_ headersInWhiteList: [String]?,
// _ headersInBlackList: [String]?,
// _ shouldRecordRequestBody: ((URLRequest) -> Bool)? = nil,
// _ shouldRecordResponseBody: ((URLResponse) -> Bool)? = nil)
// {
// self.headersInBlackList = headersInBlackList
// self.shouldRecordRequestBody = shouldRecordRequestBody
// self.shouldRecordResponseBody = shouldRecordResponseBody
// }
//#endif
}
//#if canImport(URLSessionInstrumentation)
extension URLSessionInstrumentationConfiguration {
public init(configuration: AliyunURLSessionInstrumentationConfiguration?) {
self.init(
shouldRecordPayload: { session in
return configuration?.configuration?.shouldRecordPayload?(session) ?? true
}, shouldInstrument: { request in
return configuration?.configuration?.shouldInstrument?(request) ?? true
}, nameSpan: { request in
return configuration?.configuration?.nameSpan?(request) ?? "\(request.httpMethod?.uppercased() ?? "") \(request.url?.path ?? "")"
}, spanCustomization: { (request, spanBuilder) in
URLSessionInstrumentationConfiguration.recordHeaders(
configuarion: configuration,
headers: request.allHTTPHeaderFields,
spanBuilder: spanBuilder,
span: nil
)
// 对齐最新的SemanticAttributes
spanBuilder.setAttribute(key: SemanticAttributes.urlPath.rawValue, value: request.url?.absoluteString ?? "")
spanBuilder.setAttribute(key: SemanticAttributes.urlScheme.rawValue, value: request.url?.scheme ?? "")
spanBuilder.setAttribute(key: SemanticAttributes.urlFull.rawValue, value: request.url?.absoluteString ?? "")
spanBuilder.setAttribute(key: SemanticAttributes.serverAddress.rawValue, value: request.url?.host ?? "")
if let method = request.httpMethod?.uppercased(),
"POST" == method || "PUT" == method || "PATCH" == method || "DELETE" == method {
guard configuration?.shouldRecordRequestBody?(request) ?? true else {
return
}
guard let httpBody = request.httpBody else {
return
}
spanBuilder.setAttribute(key: SemanticAttributes.httpRequestBodySize.rawValue, value: httpBody.count)
guard let body = String(data: httpBody, encoding: .utf8) else {
return
}
spanBuilder.setAttribute(key: "http.request.body", value: body)
} else {
spanBuilder.setAttribute(key: SemanticAttributes.urlQuery.rawValue, value: request.url?.query ?? "")
}
}, shouldInjectTracingHeaders: { request in
return true
}, injectCustomHeaders: { request, span in
}, createdRequest: { request, span in
// remove http.method
span.setAttribute(key: SemanticAttributes.httpMethod.rawValue, value: nil)
// remove http.scheme
span.setAttribute(key: SemanticAttributes.httpScheme.rawValue, value: nil)
// remove http.url
span.setAttribute(key: SemanticAttributes.httpUrl.rawValue, value: nil)
// remote http.target
span.setAttribute(key: SemanticAttributes.httpTarget.rawValue, value: nil)
// add http.request.method
span.setAttribute(key: "http.request.method", value: request.httpMethod ?? "unknown_method")
}, receivedResponse: { response, dataOrFile, span in
guard let res = response as? HTTPURLResponse else {
return
}
URLSessionInstrumentationConfiguration.recordHeaders(
configuarion: configuration,
headers: URLSessionInstrumentationConfiguration.converAllHeaders(res.allHeaderFields),
spanBuilder: nil,
span: span
)
// remove http.status_code
span.setAttribute(key: SemanticAttributes.httpStatusCode.rawValue, value: nil)
// add http.response.status_code
span.setAttribute(key: SemanticAttributes.httpResponseStatusCode.rawValue, value: res.statusCode)
guard configuration?.shouldRecordResponseBody?(response) ?? true else {
return
}
guard let d = dataOrFile as? Data, let data = String(data: d, encoding: .utf8) else {
return
}
span.setAttribute(key: "http.response.body", value: data)
}, receivedError: { error, dataOrFile, status, span in
span.setAttribute(key: "http.response.error", value: "\(error)")
span.setAttribute(key: SemanticAttributes.httpResponseStatusCode.rawValue, value: status)
}, delegateClassesToInstrument: {
return nil
}()
)
}
static func recordHeaders(configuarion: AliyunURLSessionInstrumentationConfiguration?, headers: [String: String]?, spanBuilder: SpanBuilder?, span: Span?) {
guard let _ = headers else {
return
}
let whiteList: [String] = configuarion?.headersInWhiteList ?? []
let blackList: [String] = configuarion?.headersInBlackList ?? []
for (k, v) in headers! {
if "User-Agent" == k {
if let builder = spanBuilder {
builder.setAttribute(key: SemanticAttributes.userAgentOriginal.rawValue, value: v)
} else if let span = span {
span.setAttribute(key: SemanticAttributes.userAgentOriginal.rawValue, value: v)
}
continue
}
if blackList.count > 0, blackList.contains(k) {
continue
}
if whiteList.count > 0, !whiteList.contains(k) {
continue
}
if let builder = spanBuilder {
builder.setAttribute(key: "http.request.header.\(k)", value: v)
} else if let span = span {
span.setAttribute(key: "http.response.header.\(k)", value: v)
}
}
}
static func converAllHeaders(_ src: [AnyHashable: Any]) -> [String: String] {
var dest = [String: String]()
for (k, v) in src {
guard let key = k as? String else {
continue
}
guard let value = v as? String else {
continue
}
dest[key] = value
}
return dest
}
}
//#endif