From 76b833c4da002a37c389bcfacd3ba61948bfc5a5 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:11:16 -0800 Subject: [PATCH 1/4] Added -fprofile-instr-generate to fix watchos build (#642) * Added -fprofile-instr-generate to fix watchos build * Update BuildAndTest.yml updated runner to macos-15 * Update Makefile specified os versions to avoid destination ambiguity --- .github/workflows/BuildAndTest.yml | 10 +++++----- Makefile | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/BuildAndTest.yml b/.github/workflows/BuildAndTest.yml index 865d5192..1d0b4252 100644 --- a/.github/workflows/BuildAndTest.yml +++ b/.github/workflows/BuildAndTest.yml @@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch] jobs: macOS: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - name: Build and Test for macOS @@ -16,7 +16,7 @@ jobs: xcrun llvm-cov export -ignore-filename-regex="pb\.swift|grpc\.swift" -format="lcov" .build/debug/opentelemetry-swiftPackageTests.xctest/Contents/MacOS/opentelemetry-swiftPackageTests -instr-profile .build/debug/codecov/default.profdata > .build/debug/codecov/coverage_report.lcov ./codecov -f .build/debug/codecov/coverage_report.lcov iOS: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - name: Install Homebrew kegs @@ -26,7 +26,7 @@ jobs: - name: Test for iOS run: make test-without-building-ios tvOS: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - name: Install Homebrew kegs @@ -36,7 +36,7 @@ jobs: - name: Test for tvOS run: make test-without-building-tvos watchOS: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - name: Install Homebrew kegs @@ -53,4 +53,4 @@ jobs: - name: Build tests for Linux run: swift build --build-tests - name: Run tests for Linux - run: swift test \ No newline at end of file + run: swift test diff --git a/Makefile b/Makefile index cac2f142..ab0fb8fd 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PROJECT_NAME="opentelemetry-swift-Package" XCODEBUILD_OPTIONS_IOS=\ -configuration Debug \ - -destination platform='iOS Simulator,name=iPhone 15,OS=latest' \ + -destination platform='iOS Simulator,name=iPhone 16,OS=18.2' \ -scheme $(PROJECT_NAME) \ -test-iterations 5 \ -retry-tests-on-failure \ @@ -18,7 +18,7 @@ XCODEBUILD_OPTIONS_TVOS=\ XCODEBUILD_OPTIONS_WATCHOS=\ -configuration Debug \ - -destination platform='watchOS Simulator,name=Apple Watch Series 8 (45mm),OS=latest' \ + -destination platform='watchOS Simulator,name=Apple Watch Series 10 (46mm),OS=11.2' \ -scheme $(PROJECT_NAME) \ -test-iterations 5 \ -retry-tests-on-failure \ @@ -50,7 +50,7 @@ build-for-testing-tvos: .PHONY: build-for-testing-watchos build-for-testing-watchos: - set -o pipefail && xcodebuild $(XCODEBUILD_OPTIONS_WATCHOS) build-for-testing | xcbeautify + set -o pipefail && xcodebuild OTHER_LDFLAGS="$(OTHER_LDFLAGS) -fprofile-instr-generate" $(XCODEBUILD_OPTIONS_WATCHOS) build-for-testing | xcbeautify .PHONY: test-ios test-ios: From 7001892a682fc99c66ad41af676ec48b0bb78025 Mon Sep 17 00:00:00 2001 From: Batuhan Saka Date: Fri, 22 Nov 2024 12:33:12 +0100 Subject: [PATCH 2/4] Add support for Default Baggage Injection in URLSessionInstrumentation (#639) * add default baggage provider to urlsessioninstrumentation * add missing `DataCompression` dep to `OpenTelemetryProtocolExporterHttp` * add missing deps to all package files --- Package.swift | 3 +- Package@swift-5.6.swift | 3 +- Package@swift-5.9.swift | 3 +- ...LSessionInstrumentationConfiguration.swift | 14 +++- .../URLSession/URLSessionLogger.swift | 25 +++++-- .../URLSessionInstrumentationTests.swift | 70 +++++++++++++++++-- 6 files changed, 105 insertions(+), 13 deletions(-) diff --git a/Package.swift b/Package.swift index 15a43e33..2d091fe5 100644 --- a/Package.swift +++ b/Package.swift @@ -104,7 +104,8 @@ let package = Package( path: "Sources/Exporters/OpenTelemetryProtocolCommon"), .target(name: "OpenTelemetryProtocolExporterHttp", dependencies: ["OpenTelemetrySdk", - "OpenTelemetryProtocolExporterCommon"], + "OpenTelemetryProtocolExporterCommon", + "DataCompression"], path: "Sources/Exporters/OpenTelemetryProtocolHttp"), .target(name: "OpenTelemetryProtocolExporterGrpc", dependencies: ["OpenTelemetrySdk", diff --git a/Package@swift-5.6.swift b/Package@swift-5.6.swift index 2d25e584..3340cc29 100644 --- a/Package@swift-5.6.swift +++ b/Package@swift-5.6.swift @@ -109,7 +109,8 @@ let package = Package( path: "Sources/Exporters/OpenTelemetryProtocolCommon"), .target(name: "OpenTelemetryProtocolExporterHttp", dependencies: ["OpenTelemetrySdk", - "OpenTelemetryProtocolExporterCommon"], + "OpenTelemetryProtocolExporterCommon", + "DataCompression"], path: "Sources/Exporters/OpenTelemetryProtocolHttp"), .target(name: "OpenTelemetryProtocolExporterGrpc", dependencies: ["OpenTelemetrySdk", diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index 49ff7747..3f4f5ddb 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -64,7 +64,8 @@ let package = Package( path: "Sources/Exporters/OpenTelemetryProtocolCommon"), .target(name: "OpenTelemetryProtocolExporterHttp", dependencies: ["OpenTelemetrySdk", - "OpenTelemetryProtocolExporterCommon"], + "OpenTelemetryProtocolExporterCommon", + "DataCompression"], path: "Sources/Exporters/OpenTelemetryProtocolHttp"), .target(name: "OpenTelemetryProtocolExporterGrpc", dependencies: ["OpenTelemetrySdk", diff --git a/Sources/Instrumentation/URLSession/URLSessionInstrumentationConfiguration.swift b/Sources/Instrumentation/URLSession/URLSessionInstrumentationConfiguration.swift index 9566f206..2636244d 100644 --- a/Sources/Instrumentation/URLSession/URLSessionInstrumentationConfiguration.swift +++ b/Sources/Instrumentation/URLSession/URLSessionInstrumentationConfiguration.swift @@ -24,7 +24,8 @@ public struct URLSessionInstrumentationConfiguration { createdRequest: ((URLRequest, Span) -> Void)? = nil, receivedResponse: ((URLResponse, DataOrFile?, Span) -> Void)? = nil, receivedError: ((Error, DataOrFile?, HTTPStatus, Span) -> Void)? = nil, - delegateClassesToInstrument: [AnyClass]? = nil) + delegateClassesToInstrument: [AnyClass]? = nil, + defaultBaggageProvider: (() -> (Baggage)?)? = nil) { self.shouldRecordPayload = shouldRecordPayload self.shouldInstrument = shouldInstrument @@ -36,6 +37,7 @@ public struct URLSessionInstrumentationConfiguration { self.receivedResponse = receivedResponse self.receivedError = receivedError self.delegateClassesToInstrument = delegateClassesToInstrument + self.defaultBaggageProvider = defaultBaggageProvider } // Instrumentation Callbacks @@ -73,4 +75,14 @@ public struct URLSessionInstrumentationConfiguration { /// The array of URLSession delegate classes that will be instrumented by the library, will autodetect if nil is passed. public var delegateClassesToInstrument: [AnyClass]? + + /// Implement this callback to provide a default baggage instance for all instrumented requests. + /// The provided baggage is merged with active baggage (if any) to create a combined baggage. + /// The combined baggage is then injected into request headers using the configured `TextMapBaggagePropagator`. + /// This allows consistent propagation across requests, regardless of the active context. + /// + /// Note: The injected baggage depends on the propagator in use (e.g., W3C or custom). + /// Returns: A `Baggage` instance or `nil` if no default baggage is needed. + public let defaultBaggageProvider: (() -> (Baggage)?)? + } diff --git a/Sources/Instrumentation/URLSession/URLSessionLogger.swift b/Sources/Instrumentation/URLSession/URLSessionLogger.swift index bda5788d..c41fc34c 100644 --- a/Sources/Instrumentation/URLSession/URLSessionLogger.swift +++ b/Sources/Instrumentation/URLSession/URLSessionLogger.swift @@ -160,7 +160,14 @@ class URLSessionLogger { var instrumentedRequest = request objc_setAssociatedObject(instrumentedRequest, URLSessionInstrumentation.instrumentedKey, true, .OBJC_ASSOCIATION_COPY_NONATOMIC) let propagators = OpenTelemetry.instance.propagators - var traceHeaders = tracePropagationHTTPHeaders(span: span, textMapPropagator: propagators.textMapPropagator, textMapBaggagePropagator: propagators.textMapBaggagePropagator) + + let defaultBaggage = instrumentation.configuration.defaultBaggageProvider?() + + var traceHeaders = tracePropagationHTTPHeaders(span: span, + defaultBaggage: defaultBaggage, + textMapPropagator: propagators.textMapPropagator, + textMapBaggagePropagator: propagators.textMapBaggagePropagator) + if let originalHeaders = request.allHTTPHeaderFields { traceHeaders.merge(originalHeaders) { _, new in new } } @@ -168,7 +175,7 @@ class URLSessionLogger { return instrumentedRequest } - private static func tracePropagationHTTPHeaders(span: Span?, textMapPropagator: TextMapPropagator, textMapBaggagePropagator: TextMapBaggagePropagator) -> [String: String] { + private static func tracePropagationHTTPHeaders(span: Span?, defaultBaggage: Baggage?, textMapPropagator: TextMapPropagator, textMapBaggagePropagator: TextMapBaggagePropagator) -> [String: String] { var headers = [String: String]() struct HeaderSetter: Setter { @@ -182,9 +189,19 @@ class URLSessionLogger { } textMapPropagator.inject(spanContext: currentSpan.context, carrier: &headers, setter: HeaderSetter()) - if let baggage = OpenTelemetry.instance.contextProvider.activeBaggage { - textMapBaggagePropagator.inject(baggage: baggage, carrier: &headers, setter: HeaderSetter()) + let baggageBuilder = OpenTelemetry.instance.baggageManager.baggageBuilder() + + if let activeBaggage = OpenTelemetry.instance.contextProvider.activeBaggage { + activeBaggage.getEntries().forEach { baggageBuilder.put(key: $0.key, value: $0.value, metadata: $0.metadata) } } + + if let defaultBaggage { + defaultBaggage.getEntries().forEach { baggageBuilder.put(key: $0.key, value: $0.value, metadata: $0.metadata) } + } + + let combinedBaggage = baggageBuilder.build() + textMapBaggagePropagator.inject(baggage: combinedBaggage, carrier: &headers, setter: HeaderSetter()) + return headers } } diff --git a/Tests/InstrumentationTests/URLSessionTests/URLSessionInstrumentationTests.swift b/Tests/InstrumentationTests/URLSessionTests/URLSessionInstrumentationTests.swift index 9a21702b..d941df53 100644 --- a/Tests/InstrumentationTests/URLSessionTests/URLSessionInstrumentationTests.swift +++ b/Tests/InstrumentationTests/URLSessionTests/URLSessionInstrumentationTests.swift @@ -39,6 +39,9 @@ class URLSessionInstrumentationTests: XCTestCase { static var requestCopy: URLRequest! static var responseCopy: HTTPURLResponse! + static var activeBaggage: Baggage! + static var defaultBaggage: Baggage! + static var config = URLSessionInstrumentationConfiguration(shouldRecordPayload: nil, shouldInstrument: { req in checker.shouldInstrumentCalled = true @@ -76,13 +79,15 @@ class URLSessionInstrumentationTests: XCTestCase { }, receivedError: { _, _, _, _ in URLSessionInstrumentationTests.checker.receivedErrorCalled = true + }, + defaultBaggageProvider: { + defaultBaggage }) static var checker = Check() static var semaphore: DispatchSemaphore! var sessionDelegate: SessionDelegate! static var instrumentation: URLSessionInstrumentation! - static var baggage: Baggage! static let server = HttpTestServer(url: URL(string: "http://localhost:33333"), config: nil) @@ -90,11 +95,15 @@ class URLSessionInstrumentationTests: XCTestCase { OpenTelemetry.registerPropagators(textPropagators: [W3CTraceContextPropagator()], baggagePropagator: W3CBaggagePropagator()) OpenTelemetry.registerTracerProvider(tracerProvider: TracerProviderSdk()) - baggage = DefaultBaggageManager.instance.baggageBuilder() + defaultBaggage = DefaultBaggageManager.instance.baggageBuilder() + .put(key: EntryKey(name: "bar")!, value: EntryValue(string: "baz")!, metadata: nil) + .build() + + activeBaggage = DefaultBaggageManager.instance.baggageBuilder() .put(key: EntryKey(name: "foo")!, value: EntryValue(string: "bar")!, metadata: nil) .build() - OpenTelemetry.instance.contextProvider.setActiveBaggage(baggage) + OpenTelemetry.instance.contextProvider.setActiveBaggage(activeBaggage) let sem = DispatchSemaphore(value: 0) DispatchQueue.global(qos: .default).async { @@ -111,7 +120,8 @@ class URLSessionInstrumentationTests: XCTestCase { override class func tearDown() { server.stop() - OpenTelemetry.instance.contextProvider.removeContextForBaggage(baggage) + defaultBaggage = nil + OpenTelemetry.instance.contextProvider.removeContextForBaggage(activeBaggage) } override func setUp() { @@ -262,13 +272,63 @@ class URLSessionInstrumentationTests: XCTestCase { XCTAssertTrue(URLSessionInstrumentationTests.checker.shouldInstrumentCalled) + XCTAssertEqual(1, URLSessionLogger.runningSpans.count) + + let span = try XCTUnwrap(URLSessionLogger.runningSpans["111"]) + XCTAssertEqual("HTTP GET", span.name) + } + + public func testShouldInstrumentRequest_PropagateCombinedActiveAndDefaultBaggages() throws { + let request1 = URLRequest(url: URL(string: "http://defaultName.com")!) + let request2 = URLRequest(url: URL(string: "http://dontinstrument.com")!) + + let processedRequest1 = try XCTUnwrap(URLSessionLogger.processAndLogRequest(request1, sessionTaskId: "111", instrumentation: URLSessionInstrumentationTests.instrumentation, shouldInjectHeaders: true)) + let processedRequest2 = URLSessionLogger.processAndLogRequest(request2, sessionTaskId: "222", instrumentation: URLSessionInstrumentationTests.instrumentation, shouldInjectHeaders: true) + + // `processedRequest2` is expected to be nil, because its URL was marked as not to be instrumented. + XCTAssertNil(processedRequest2) + + XCTAssertTrue(URLSessionInstrumentationTests.checker.shouldInstrumentCalled) + + let processedHeaders1 = try XCTUnwrap(processedRequest1.allHTTPHeaderFields) + + // headers injected from `TextMapPropagator` implementation + XCTAssertTrue(processedHeaders1.contains(where: { $0.key == W3CTraceContextPropagator.traceparent })) + + // headers injected from `TextMapBaggagePropagator` implementation + let baggageHeaderValue = try XCTUnwrap(processedHeaders1[W3CBaggagePropagator.headerBaggage]) + + // foo=bar propagated through active baggage defined in `setUp` + XCTAssertTrue(baggageHeaderValue.contains("foo=bar")) + + // bar=baz propagated through default baggage provided in `URLSessionInstrumentationConfiguration` + XCTAssertTrue(baggageHeaderValue.contains("bar=baz")) + + XCTAssertEqual(1, URLSessionLogger.runningSpans.count) + + let span = try XCTUnwrap(URLSessionLogger.runningSpans["111"]) + XCTAssertEqual("HTTP GET", span.name) + } + + public func testShouldInstrumentRequest_PropagateOnlyActiveBaggage() throws { + Self.defaultBaggage = nil + + let request1 = URLRequest(url: URL(string: "http://defaultName.com")!) + + let processedRequest1 = try XCTUnwrap(URLSessionLogger.processAndLogRequest(request1, sessionTaskId: "111", instrumentation: URLSessionInstrumentationTests.instrumentation, shouldInjectHeaders: true)) + + XCTAssertTrue(URLSessionInstrumentationTests.checker.shouldInstrumentCalled) + let processedHeaders1 = try XCTUnwrap(processedRequest1.allHTTPHeaderFields) // headers injected from `TextMapPropagator` implementation XCTAssertTrue(processedHeaders1.contains(where: { $0.key == W3CTraceContextPropagator.traceparent })) // headers injected from `TextMapBaggagePropagator` implementation - XCTAssertTrue(processedHeaders1.contains(where: { $0.key == W3CBaggagePropagator.headerBaggage && $0.value == "foo=bar" })) + let baggageHeaderValue = try XCTUnwrap(processedHeaders1[W3CBaggagePropagator.headerBaggage]) + + // bar=baz propagated through default baggage provided in `URLSessionInstrumentationConfiguration` + XCTAssertEqual(baggageHeaderValue, "foo=bar") XCTAssertEqual(1, URLSessionLogger.runningSpans.count) From 9332fec228499ff87765736cff34ece7cd81dac9 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Fri, 22 Nov 2024 05:41:51 -0800 Subject: [PATCH 3/4] added other AV task exceptions in NSURLSession instrumentation (#640) --- .../URLSessionInstrumentation.swift | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Sources/Instrumentation/URLSession/URLSessionInstrumentation.swift b/Sources/Instrumentation/URLSession/URLSessionInstrumentation.swift index a67920eb..20714ea6 100644 --- a/Sources/Instrumentation/URLSession/URLSessionInstrumentation.swift +++ b/Sources/Instrumentation/URLSession/URLSessionInstrumentation.swift @@ -26,16 +26,21 @@ struct NetworkRequestState { private var idKey: Void? public class URLSessionInstrumentation { - private var requestMap = [String: NetworkRequestState]() - - var configuration: URLSessionInstrumentationConfiguration - - private let queue = DispatchQueue(label: "io.opentelemetry.ddnetworkinstrumentation") - - static var instrumentedKey = "io.opentelemetry.instrumentedCall" - - static let avAssetDownloadTask: AnyClass? = NSClassFromString("__NSCFBackgroundAVAssetDownloadTask") - + private var requestMap = [String: NetworkRequestState]() + + var configuration: URLSessionInstrumentationConfiguration + + private let queue = DispatchQueue(label: "io.opentelemetry.ddnetworkinstrumentation") + + static var instrumentedKey = "io.opentelemetry.instrumentedCall" + + static let AVTaskClassList : [AnyClass] = { + ["__NSCFBackgroundAVAggregateAssetDownloadTask", + "__NSCFBackgroundAVAssetDownloadTask", + "__NSCFBackgroundAVAggregateAssetDownloadTaskNoChildTask" ] + .compactMap { NSClassFromString($0) } + }() + public private(set) var tracer: Tracer public var startedRequestSpans: [Span] { @@ -592,10 +597,7 @@ public class URLSessionInstrumentation { private func urlSessionTaskWillResume(_ task: URLSessionTask) { // AV Asset Tasks cannot be auto instrumented, they dont include request attributes, skip them - if let avAssetTaskClass = Self.avAssetDownloadTask, - task.isKind(of: avAssetTaskClass) { - return - } + guard !Self.AVTaskClassList.contains(where: {task.isKind(of:$0)}) else { return } // We cannot instrument async background tasks because they crash if you assign a delegate if #available(OSX 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) { From e9c6ae6a1a5093432667ccedc86cb21e3e604ee5 Mon Sep 17 00:00:00 2001 From: mamunto <96148746+mamunto@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:48:21 -0500 Subject: [PATCH 4/4] adding exporter metric to StableOtlpHTTPMetricExporter (#638) --- .../metric/StableOtlpHTTPMetricExporter.swift | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/Sources/Exporters/OpenTelemetryProtocolHttp/metric/StableOtlpHTTPMetricExporter.swift b/Sources/Exporters/OpenTelemetryProtocolHttp/metric/StableOtlpHTTPMetricExporter.swift index bb852c24..cd0ad0c4 100644 --- a/Sources/Exporters/OpenTelemetryProtocolHttp/metric/StableOtlpHTTPMetricExporter.swift +++ b/Sources/Exporters/OpenTelemetryProtocolHttp/metric/StableOtlpHTTPMetricExporter.swift @@ -5,6 +5,7 @@ import Foundation import OpenTelemetrySdk +import OpenTelemetryApi import OpenTelemetryProtocolExporterCommon #if canImport(FoundationNetworking) import FoundationNetworking @@ -15,11 +16,12 @@ public func defaultStableOtlpHTTPMetricsEndpoint() -> URL { } public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMetricExporter { - var aggregationTemporalitySelector: AggregationTemporalitySelector - var defaultAggregationSelector: DefaultAggregationSelector + var aggregationTemporalitySelector: AggregationTemporalitySelector + var defaultAggregationSelector: DefaultAggregationSelector - var pendingMetrics: [StableMetricData] = [] - private let exporterLock = Lock() + var pendingMetrics: [StableMetricData] = [] + private let exporterLock = Lock() + private var exporterMetrics: ExporterMetrics? // MARK: - Init @@ -31,6 +33,40 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet super.init(endpoint: endpoint, config: config, useSession: useSession, envVarHeaders: envVarHeaders) } + /// A `convenience` constructor to provide support for exporter metric using`StableMeterProvider` type + /// - Parameters: + /// - endpoint: Exporter endpoint injected as dependency + /// - config: Exporter configuration including type of exporter + /// - meterProvider: Injected `StableMeterProvider` for metric + /// - aggregationTemporalitySelector: aggregator + /// - defaultAggregationSelector: default aggregator + /// - useSession: Overridden `URLSession` if any + /// - envVarHeaders: Extra header key-values + convenience public init( + endpoint: URL, + config: OtlpConfiguration = OtlpConfiguration(), + meterProvider: StableMeterProvider, + aggregationTemporalitySelector: AggregationTemporalitySelector = AggregationTemporality.alwaysCumulative(), + defaultAggregationSelector: DefaultAggregationSelector = AggregationSelector.instance, + useSession: URLSession? = nil, + envVarHeaders: [(String, String)]? = EnvVarHeaders.attributes) { + self.init( + endpoint: endpoint, + config: config, + aggregationTemporalitySelector: aggregationTemporalitySelector, + defaultAggregationSelector: defaultAggregationSelector, + useSession: useSession, + envVarHeaders: envVarHeaders + ) + self.exporterMetrics = ExporterMetrics( + type: "otlp", + meterProvider: meterProvider, + exporterName: "metric", + transportName: config.exportAsJson ? + ExporterMetrics.TransporterType.httpJson : + ExporterMetrics.TransporterType.grpc + ) + } // MARK: - StableMetricsExporter @@ -44,14 +80,16 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet let body = Opentelemetry_Proto_Collector_Metrics_V1_ExportMetricsServiceRequest.with { $0.resourceMetrics = MetricsAdapter.toProtoResourceMetrics(stableMetricData: sendingMetrics) } - + self.exporterMetrics?.addSeen(value: sendingMetrics.count) var request = createRequest(body: body, endpoint: endpoint) request.timeoutInterval = min(TimeInterval.greatestFiniteMagnitude, config.timeout) httpClient.send(request: request) { [weak self] result in switch result { case .success(_): + self?.exporterMetrics?.addSuccess(value: sendingMetrics.count) break case .failure(let error): + self?.exporterMetrics?.addFailed(value: sendingMetrics.count) self?.exporterLock.withLockVoid { self?.pendingMetrics.append(contentsOf: sendingMetrics) } @@ -75,11 +113,13 @@ public class StableOtlpHTTPMetricExporter: StableOtlpHTTPExporterBase, StableMet let semaphore = DispatchSemaphore(value: 0) var request = createRequest(body: body, endpoint: endpoint) request.timeoutInterval = min(TimeInterval.greatestFiniteMagnitude, config.timeout) - httpClient.send(request: request) { result in + httpClient.send(request: request) { [weak self] result in switch result { case .success(_): + self?.exporterMetrics?.addSuccess(value: pendingMetrics.count) break case .failure(let error): + self?.exporterMetrics?.addFailed(value: pendingMetrics.count) print(error) exporterResult = .failure }