Skip to content

Commit c1a31b8

Browse files
committed
[iOS] Add isTracingActive
1 parent 82c1672 commit c1a31b8

File tree

11 files changed

+181
-0
lines changed

11 files changed

+181
-0
lines changed

platform/swift/source/CaptureRustBridge.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ void capture_api_release_stream(stream_id);
307307
*/
308308
bool capture_runtime_bool_variable_value(logger_id logger_id, const char *variable_name, bool default_value);
309309

310+
/*
311+
* Checks whether tracing is currently active for the logger/session.
312+
*
313+
* @param logger_id the logger to query.
314+
*
315+
* @returns true if tracing is active, false otherwise.
316+
*/
317+
bool capture_is_tracing_active(logger_id logger_id);
318+
310319
/*
311320
* Returns the value of an integer runtime variable via client runtime configuration.
312321
*

platform/swift/source/CoreLogger.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import Foundation
1313
final class CoreLogger {
1414
private let underlyingLogger: LoggerBridging
1515

16+
var isTracingActive: Bool {
17+
self.underlyingLogger.isTracingActive
18+
}
19+
1620
/// Initializes a new instance of the logger using provided Rust Logger bridging logger creation closure.
1721
/// It breaks-down Rust logger initialization process into two separate stages:
1822
/// initialization and the actual start of the logger. This separation helps us to avoid data race

platform/swift/source/Logger.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,14 @@ extension Logger: Logging {
606606
// MARK: - Features
607607

608608
extension Logger {
609+
internal var isTracingActive: Bool {
610+
(self.underlyingLogger as? CoreLogger)?.isTracingActive == true
611+
}
612+
613+
internal func runtimeValue<T: RuntimeValue>(_ variable: RuntimeVariable<T>) -> T {
614+
self.underlyingLogger.runtimeValue(variable)
615+
}
616+
609617
static func setUpMemoryStateMonitoring(
610618
logger: CoreLogging
611619
) -> DispatchSourceMemoryMonitor

platform/swift/source/LoggerBridge.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ final class LoggerBridge: LoggerBridging {
6565
let loggerID: LoggerID
6666
private var blockingShutdown = false
6767

68+
var isTracingActive: Bool {
69+
capture_is_tracing_active(self.loggerID)
70+
}
71+
6872
init?(
6973
apiKey: String,
7074
bufferDirectoryPath: String,

platform/swift/source/LoggerBridging.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ typealias InternalFields = [CapturePassable.Field]
1414
/// An abstraction around Rust logger calls. It's main purpose is to help with dependency injection so that
1515
/// tests do not call into Rust methods.
1616
protocol LoggerBridging {
17+
var isTracingActive: Bool { get }
18+
1719
func log(
1820
level: LogLevel,
1921
message: @autoclosure () -> String,

platform/swift/source/integrations/url_session/TracePropagation.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ enum URLSessionTracePropagation {
4040
static let xB3TraceIDHeader = "X-B3-TraceId"
4141
static let xB3SpanIDHeader = "X-B3-SpanId"
4242
static let xB3SampledHeader = "X-B3-Sampled"
43+
44+
static func traceparentValue(traceContext: URLSessionTraceContext) -> String {
45+
"00-\(traceContext.traceID)-\(traceContext.spanID)-01"
46+
}
47+
48+
static func b3SingleValue(traceContext: URLSessionTraceContext) -> String {
49+
"\(traceContext.traceID)-\(traceContext.spanID)-1"
50+
}
4351
}
4452

4553
struct URLSessionTraceContext {

platform/swift/source/integrations/url_session/URLSessionIntegration.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ final class URLSessionIntegration {
7070
return self.underlyingResponseFieldProvider.load()
7171
}
7272

73+
var tracePropagationMode: URLSessionTracePropagationMode {
74+
guard let logger = Logger.getShared() as? Logger else {
75+
return .disabled
76+
}
77+
78+
let runtimeValue = logger.runtimeValue(.tracePropagationMode)
79+
return URLSessionTracePropagationMode(runtimeValue: runtimeValue)
80+
}
81+
82+
var isTracingActive: Bool {
83+
(Logger.getShared() as? Logger)?.isTracingActive == true
84+
}
85+
7386
func start(
7487
logger: Logging,
7588
disableSwizzling: Bool,

platform/swift/source/integrations/url_session/extensions/URLSessionTask+Swizzling.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,41 @@ extension URLSessionTask {
1919
return
2020
}
2121

22+
let integration = URLSessionIntegration.shared
23+
let mode = integration.tracePropagationMode
24+
guard mode != .disabled, integration.isTracingActive else {
25+
return
26+
}
27+
28+
let traceContext = URLSessionTraceContext.make()
29+
self.cap_traceContext = traceContext
30+
31+
if let request = self.originalRequest, let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest {
32+
switch mode {
33+
case .w3c:
34+
mutableRequest.setValue(
35+
URLSessionTracePropagation.traceparentValue(traceContext: traceContext),
36+
forHTTPHeaderField: URLSessionTracePropagation.traceparentHeader
37+
)
38+
case .b3Single:
39+
mutableRequest.setValue(
40+
URLSessionTracePropagation.b3SingleValue(traceContext: traceContext),
41+
forHTTPHeaderField: URLSessionTracePropagation.b3Header
42+
)
43+
case .b3Multi:
44+
mutableRequest.setValue(traceContext.traceID, forHTTPHeaderField: URLSessionTracePropagation.xB3TraceIDHeader)
45+
mutableRequest.setValue(traceContext.spanID, forHTTPHeaderField: URLSessionTracePropagation.xB3SpanIDHeader)
46+
mutableRequest.setValue("1", forHTTPHeaderField: URLSessionTracePropagation.xB3SampledHeader)
47+
case .disabled:
48+
break
49+
}
50+
51+
mutableRequest.setValue(traceContext.traceID, forHTTPHeaderField: URLSessionTracePropagation.traceIDHeader)
52+
try? ObjCWrapper.doTry {
53+
self.setValue(mutableRequest as URLRequest, forKey: "originalRequest")
54+
}
55+
}
56+
2257
URLSessionTaskTracker.shared.taskWillStart(self)
2358
try? ObjCWrapper.doTry {
2459
self.delegate = ProxyURLSessionTaskDelegate(target: self.delegate)

platform/swift/source/src/bridge.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,15 @@ extern "C" fn capture_runtime_bool_variable_value(
652652
)
653653
}
654654

655+
#[no_mangle]
656+
extern "C" fn capture_is_tracing_active(logger_id: LoggerId<'_>) -> bool {
657+
with_handle_unexpected_or(
658+
move || Ok(logger_id.is_tracing_active()),
659+
false,
660+
"swift is tracing active",
661+
)
662+
}
663+
655664
#[no_mangle]
656665
extern "C" fn capture_runtime_uint32_variable_value(
657666
logger_id: LoggerId<'_>,

test/platform/swift/unit_integration/integrations/URLSessionIntegrationTests.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,88 @@ final class URLSessionIntegrationTests: XCTestCase {
959959
return url
960960
}
961961
}
962+
963+
final class URLSessionTracePropagationTests: XCTestCase {
964+
private var loggerBridge: MockLoggerBridging!
965+
966+
override func setUp() {
967+
super.setUp()
968+
self.loggerBridge = MockLoggerBridging()
969+
URLSessionIntegration.shared.disableURLSessionTaskSwizzling()
970+
971+
let logger: Logger? = Logger(
972+
withAPIKey: "123",
973+
remoteErrorReporter: nil,
974+
configuration: .init(rootFileURL: FileManager.default.temporaryDirectory.appendingPathComponent("bitdrift_test_\(UUID().uuidString)")),
975+
sessionStrategy: .fixed(),
976+
dateProvider: nil,
977+
fieldProviders: [],
978+
enableNetwork: false,
979+
storageProvider: MockStorageProvider(),
980+
timeProvider: MockTimeProvider(),
981+
loggerBridgingFactoryProvider: MockLoggerBridgingFactory(logger: self.loggerBridge)
982+
)
983+
guard let sharedLogger = logger else {
984+
XCTFail("failed to initialize test logger")
985+
return
986+
}
987+
Logger.resetShared(logger: sharedLogger)
988+
989+
URLSessionIntegration.shared.start(
990+
logger: sharedLogger,
991+
disableSwizzling: false,
992+
requestFieldProvider: nil,
993+
responseFieldProvider: nil
994+
)
995+
}
996+
997+
override func tearDown() {
998+
URLSessionIntegration.shared.disableURLSessionTaskSwizzling()
999+
Logger.resetShared()
1000+
super.tearDown()
1001+
}
1002+
1003+
func testCapResume_whenTracingInactive_shouldNotAttachTraceContext() throws {
1004+
self.loggerBridge.tracingActive = false
1005+
self.loggerBridge.mockRuntimeVariable(.tracePropagationMode, with: "w3c")
1006+
1007+
let session = URLSession(configuration: .default)
1008+
let task = session.dataTask(with: URL(staticString: "https://api-fe.bitdrift.io/fe/ping?q=test"))
1009+
1010+
task.cap_resume()
1011+
1012+
XCTAssertNil(task.cap_traceContext)
1013+
task.cancel()
1014+
session.invalidateAndCancel()
1015+
}
1016+
1017+
func testCapResume_whenModeDisabled_shouldNotAttachTraceContext() throws {
1018+
self.loggerBridge.tracingActive = true
1019+
self.loggerBridge.mockRuntimeVariable(.tracePropagationMode, with: "none")
1020+
1021+
let session = URLSession(configuration: .default)
1022+
let task = session.dataTask(with: URL(staticString: "https://api-fe.bitdrift.io/fe/ping?q=test"))
1023+
1024+
task.cap_resume()
1025+
1026+
XCTAssertNil(task.cap_traceContext)
1027+
task.cancel()
1028+
session.invalidateAndCancel()
1029+
}
1030+
1031+
func testCapResume_whenW3CEnabled_shouldAttachTraceContext() throws {
1032+
self.loggerBridge.tracingActive = true
1033+
self.loggerBridge.mockRuntimeVariable(.tracePropagationMode, with: "w3c")
1034+
1035+
let session = URLSession(configuration: .default)
1036+
let task = session.dataTask(with: URL(staticString: "https://api-fe.bitdrift.io/fe/ping?q=test"))
1037+
1038+
task.cap_resume()
1039+
1040+
let traceContext = try XCTUnwrap(task.cap_traceContext)
1041+
XCTAssertEqual(32, traceContext.traceID.count)
1042+
XCTAssertEqual(16, traceContext.spanID.count)
1043+
task.cancel()
1044+
session.invalidateAndCancel()
1045+
}
1046+
}

0 commit comments

Comments
 (0)