Skip to content

Commit 3ae6490

Browse files
authored
Enable mutating sampled flag (#9)
* Represent trace flags as OptionSet * Document TraceFlags 📖
1 parent 34dee32 commit 3ae6490

File tree

5 files changed

+84
-21
lines changed

5 files changed

+84
-21
lines changed

Sources/W3CTraceContext/TraceContext.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ public struct TraceContext: Equatable {
3030
self.state = state
3131
}
3232

33+
/// Whether the caller may have recorded trace data.
34+
///
35+
/// - SeeAlso: [W3C TraceContext: Sampled flag](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#sampled-flag)
36+
public var sampled: Bool {
37+
get {
38+
self.parent.traceFlags.contains(.sampled)
39+
}
40+
set {
41+
if newValue {
42+
self.parent.traceFlags.insert(.sampled)
43+
} else {
44+
self.parent.traceFlags.remove(.sampled)
45+
}
46+
}
47+
}
48+
3349
/// Create a `TraceContext` by parsing the given header values.
3450
///
3551
/// - Parameters:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift W3C Trace Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift W3C Trace Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
/// Represents an 8-bit field that controls tracing flags such as sampling.
15+
public struct TraceFlags: OptionSet {
16+
public let rawValue: UInt8
17+
18+
public init(rawValue: UInt8) {
19+
self.rawValue = rawValue
20+
}
21+
22+
/// [W3C TraceContext: Sampled flag](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#sampled-flag)
23+
public static let sampled = TraceFlags(rawValue: 1 << 0)
24+
}
25+
26+
extension TraceFlags: CustomStringConvertible {
27+
public var description: String {
28+
let radix = 2
29+
let unpadded = String(self.rawValue, radix: radix, uppercase: false)
30+
return String(repeating: "0", count: radix - unpadded.count) + unpadded
31+
}
32+
}

Sources/W3CTraceContext/TraceParent.swift

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,14 @@ public struct TraceParent {
3131
/// An 8-bit field that controls tracing flags such as sampling, trace level, etc.
3232
///
3333
/// - SeeAlso: [W3C TraceContext: trace-flags](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#trace-flags)
34-
public let traceFlags: String
34+
public internal(set) var traceFlags: TraceFlags
3535

36-
init(traceID: String, parentID: String, traceFlags: String) {
36+
init(traceID: String, parentID: String, traceFlags: TraceFlags) {
3737
self.traceID = traceID
3838
self.parentID = parentID
3939
self.traceFlags = traceFlags
4040
}
4141

42-
/// When `true`, the least significant bit (right-most), denotes that the caller may have recorded trace data.
43-
/// When `false`, the caller did not record trace data out-of-band.
44-
///
45-
/// - SeeAlso: [W3C TraceContext: Sampled flag](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#sampled-flag)
46-
public var sampled: Bool {
47-
self.traceFlags == "01"
48-
}
49-
5042
/// The HTTP header name for `TraceParent`.
5143
public static let headerName = "traceparent"
5244

@@ -90,9 +82,8 @@ extension TraceParent: RawRepresentable {
9082
self.parentID = String(parentIDComponent)
9183

9284
// trace-flags
93-
let traceFlagsComponent = components[3]
94-
guard traceFlagsComponent.count == 2 else { return nil }
95-
self.traceFlags = String(traceFlagsComponent)
85+
guard let traceFlags = UInt8(components[3], radix: 2).map(TraceFlags.init) else { return nil }
86+
self.traceFlags = traceFlags.rawValue <= 1 ? traceFlags : []
9687
}
9788

9889
/// A `String` representation of this trace parent, suitable for injecting into HTTP headers.
@@ -115,7 +106,7 @@ extension TraceParent {
115106
public static func random<G: RandomNumberGenerator>(using generator: inout G) -> TraceParent {
116107
let traceID = Self.randomTraceID(using: &generator)
117108
let parentID = Self.randomParentID(using: &generator)
118-
return .init(traceID: traceID, parentID: parentID, traceFlags: UInt64(0).paddedHexString(radix: 2))
109+
return .init(traceID: traceID, parentID: parentID, traceFlags: [])
119110
}
120111

121112
/// Returns a random `TraceParent` using the system random number generator.

Tests/W3CTraceContextTests/TraceContextTests.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ final class TraceContextTests: XCTestCase {
3030
parent: TraceParent(
3131
traceID: "0af7651916cd43dd8448eb211c80319c",
3232
parentID: "b7ad6b7169203331",
33-
traceFlags: "01"
33+
traceFlags: .sampled
3434
),
3535
state: TraceState([("rojo", "00f067aa0ba902b7")])
3636
)
@@ -55,7 +55,7 @@ final class TraceContextTests: XCTestCase {
5555
parent: TraceParent(
5656
traceID: "0af7651916cd43dd8448eb211c80319c",
5757
parentID: "b7ad6b7169203331",
58-
traceFlags: "01"
58+
traceFlags: .sampled
5959
),
6060
state: TraceState([])
6161
)
@@ -77,4 +77,17 @@ final class TraceContextTests: XCTestCase {
7777

7878
XCTAssertNotEqual(newTraceContext.parent.parentID, traceContext.parent.parentID)
7979
}
80+
81+
func test_update_sampled_flag_sets_parent_trace_flags() {
82+
var traceContext = TraceContext(parent: .random(), state: .none)
83+
XCTAssertFalse(traceContext.sampled)
84+
85+
traceContext.sampled = true
86+
XCTAssert(traceContext.sampled)
87+
XCTAssert(traceContext.parent.traceFlags.contains(.sampled))
88+
89+
traceContext.sampled = false
90+
XCTAssertFalse(traceContext.sampled)
91+
XCTAssertFalse(traceContext.parent.traceFlags.contains(.sampled))
92+
}
8093
}

Tests/W3CTraceContextTests/TraceParentTests.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class TraceParentRawRepresentableTests: XCTestCase {
2121
let traceParent = TraceParent(
2222
traceID: "0af7651916cd43dd8448eb211c80319c",
2323
parentID: "b7ad6b7169203331",
24-
traceFlags: "01"
24+
traceFlags: .sampled
2525
)
2626

2727
XCTAssertEqual(traceParent.rawValue, "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
@@ -38,10 +38,21 @@ final class TraceParentRawRepresentableTests: XCTestCase {
3838

3939
XCTAssertEqual(
4040
traceParent,
41-
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: "01")
41+
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: .sampled)
4242
)
43+
}
4344

44-
XCTAssert(traceParent.sampled)
45+
func testDecodeValidTraceParentStringWithUnsupportedTraceFlags() {
46+
let rawValue = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-11"
47+
guard let traceParent = TraceParent(rawValue: rawValue) else {
48+
XCTFail("Could not decode valid trace parent")
49+
return
50+
}
51+
52+
XCTAssertEqual(
53+
traceParent,
54+
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: [])
55+
)
4556
}
4657

4758
func testDecodeValidTraceParentStringWithoutSampledFlag() {
@@ -53,10 +64,10 @@ final class TraceParentRawRepresentableTests: XCTestCase {
5364

5465
XCTAssertEqual(
5566
traceParent,
56-
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: "00")
67+
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: [])
5768
)
5869

59-
XCTAssertFalse(traceParent.sampled)
70+
XCTAssertFalse(traceParent.traceFlags.contains(.sampled))
6071
}
6172

6273
func testDecodeFailsWithTooLongRawValue() {

0 commit comments

Comments
 (0)