Skip to content

Commit 92c5f2d

Browse files
authored
More specific types in attributes, removal of case __namespace hack (#9)
* review feedback, accept Int64 in attributes * more like otel, restrict and dont allow mixed or nested arrays * wip * wip * specific types only allowed in span attrs; remove __namespace hack * NestedAttributes -> NestedSpanAttributes * fix swift 5.0
1 parent b735bbe commit 92c5f2d

9 files changed

+301
-133
lines changed

Sources/Tracing/Span.swift

Lines changed: 238 additions & 62 deletions
Large diffs are not rendered by default.

Sources/TracingOpenTelemetrySupport/SpanAttribute+EndUser.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,18 @@ public struct EndUserAttributes: SpanAttributeNamespace {
4949
self.attributes = attributes
5050
}
5151

52-
public struct NestedAttributes: NestedSpanAttributesProtocol {
52+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
5353
public init() {}
5454

5555
/// Username or client_id extracted from the access token or Authorization header in the inbound request from outside the system.
56-
public var id: SpanAttributeKey<String> { .init(name: SpanAttributeName.EndUser.id) }
56+
public var id: SpanAttribute.Key<String> { .init(name: SpanAttributeName.EndUser.id) }
5757

5858
/// Actual/assumed role the client is making the request under extracted from token or application security context.
59-
public var role: SpanAttributeKey<String> { .init(name: SpanAttributeName.EndUser.role) }
59+
public var role: SpanAttribute.Key<String> { .init(name: SpanAttributeName.EndUser.role) }
6060

6161
/// Scopes or granted authorities the client currently possesses extracted from token or application security context.
6262
/// The value would come from the scope associated with an OAuth 2.0 Access Token or an attribute value in a SAML 2.0 Assertion.
63-
public var scope: SpanAttributeKey<String> { .init(name: SpanAttributeName.EndUser.scope) }
63+
public var scope: SpanAttribute.Key<String> { .init(name: SpanAttributeName.EndUser.scope) }
6464
}
6565
}
6666
#endif

Sources/TracingOpenTelemetrySupport/SpanAttribute+GRPCSemantics.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,22 @@ public struct GRPCAttributes: SpanAttributeNamespace {
5151
self.attributes = attributes
5252
}
5353

54-
public struct NestedAttributes: NestedSpanAttributesProtocol {
54+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
5555
public init() {}
5656

5757
/// The type of message, e.g. "SENT" or "RECEIVED".
58-
public var messageType: SpanAttributeKey<String> { .init(name: SpanAttributeName.GRPC.messageType) }
58+
public var messageType: SpanAttribute.Key<String> { .init(name: SpanAttributeName.GRPC.messageType) }
5959

6060
/// The message id calculated as two different counters starting from 1, one for sent messages and one for received messages.
61-
public var messageID: SpanAttributeKey<Int> { .init(name: SpanAttributeName.GRPC.messageID) }
61+
public var messageID: SpanAttribute.Key<Int> { .init(name: SpanAttributeName.GRPC.messageID) }
6262

6363
/// The compressed message size in bytes.
64-
public var messageCompressedSize: SpanAttributeKey<Int> {
64+
public var messageCompressedSize: SpanAttribute.Key<Int> {
6565
.init(name: SpanAttributeName.GRPC.messageCompressedSize)
6666
}
6767

6868
/// The uncompressed message size in bytes.
69-
public var messageUncompressedSize: SpanAttributeKey<Int> {
69+
public var messageUncompressedSize: SpanAttribute.Key<Int> {
7070
.init(name: SpanAttributeName.GRPC.messageUncompressedSize)
7171
}
7272
}

Sources/TracingOpenTelemetrySupport/SpanAttribute+HTTPSemantics.swift

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,72 +75,72 @@ public struct HTTPAttributes: SpanAttributeNamespace {
7575
self.attributes = attributes
7676
}
7777

78-
public struct NestedAttributes: NestedSpanAttributesProtocol {
78+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
7979
public init() {}
8080

8181
/// HTTP request method. E.g. "GET".
82-
public var method: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.method) }
82+
public var method: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.method) }
8383

8484
/// Full HTTP request URL in the form scheme://host[:port]/path?query[#fragment].
8585
/// Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless.
86-
public var url: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.url) }
86+
public var url: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.url) }
8787

8888
/// The full request target as passed in a HTTP request line or equivalent, e.g. "/path/12314/?q=ddds#123".
89-
public var target: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.target) }
89+
public var target: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.target) }
9090

9191
/// The value of the HTTP host header. When the header is empty or not present, this attribute should be the same.
92-
public var host: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.host) }
92+
public var host: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.host) }
9393

9494
/// The URI scheme identifying the used protocol: "http" or "https"
95-
public var scheme: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.scheme) }
95+
public var scheme: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.scheme) }
9696

9797
/// HTTP response status code. E.g. 200.
98-
public var statusCode: SpanAttributeKey<Int> { .init(name: SpanAttributeName.HTTP.statusCode) }
98+
public var statusCode: SpanAttribute.Key<Int> { .init(name: SpanAttributeName.HTTP.statusCode) }
9999

100100
/// HTTP reason phrase. E.g. "OK".
101-
public var statusText: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.statusText) }
101+
public var statusText: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.statusText) }
102102

103103
/// Kind of HTTP protocol used: "1.0", "1.1", "2", "SPDY" or "QUIC".
104-
public var flavor: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.flavor) }
104+
public var flavor: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.flavor) }
105105

106106
/// Value of the HTTP User-Agent header sent by the client.
107-
public var userAgent: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.userAgent) }
107+
public var userAgent: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.userAgent) }
108108

109109
/// The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often,
110110
/// but not always, present as the Content-Length header. For requests using transport encoding, this should be the
111111
/// compressed size.
112-
public var requestContentLength: SpanAttributeKey<Int> {
112+
public var requestContentLength: SpanAttribute.Key<Int> {
113113
.init(name: SpanAttributeName.HTTP.requestContentLength)
114114
}
115115

116116
/// The size of the uncompressed request payload body after transport decoding. Not set if transport encoding not used.
117-
public var requestContentLengthUncompressed: SpanAttributeKey<Int> {
117+
public var requestContentLengthUncompressed: SpanAttribute.Key<Int> {
118118
.init(name: SpanAttributeName.HTTP.requestContentLengthUncompressed)
119119
}
120120

121121
/// The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and
122122
/// is often, but not always, present as the Content-Length header. For requests using transport encoding, this
123123
/// should be the compressed size.
124-
public var responseContentLength: SpanAttributeKey<Int> {
124+
public var responseContentLength: SpanAttribute.Key<Int> {
125125
.init(name: SpanAttributeName.HTTP.responseContentLength)
126126
}
127127

128128
/// The size of the uncompressed response payload body after transport decoding. Not set if transport encoding not used.
129-
public var responseContentLengthUncompressed: SpanAttributeKey<Int> {
129+
public var responseContentLengthUncompressed: SpanAttribute.Key<Int> {
130130
.init(name: SpanAttributeName.HTTP.responseContentLengthUncompressed)
131131
}
132132

133133
/// The primary server name of the matched virtual host. This should be obtained via configuration.
134134
/// If no such configuration can be obtained, this attribute MUST NOT be set (`net.hostName` should be used instead).
135-
public var serverName: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.serverName) }
135+
public var serverName: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.serverName) }
136136

137137
/// The matched route (path template). E.g. "/users/:userID?".
138-
public var serverRoute: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.serverRoute) }
138+
public var serverRoute: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.serverRoute) }
139139

140140
/// The IP address of the original client behind all proxies, if known (e.g. from X-Forwarded-For).
141141
/// Note that this is not necessarily the same as `net.peerIP`, which would identify the network-level peer,
142142
/// which may be a proxy.
143-
public var serverClientIP: SpanAttributeKey<String> { .init(name: SpanAttributeName.HTTP.serverClientIP) }
143+
public var serverClientIP: SpanAttribute.Key<String> { .init(name: SpanAttributeName.HTTP.serverClientIP) }
144144
}
145145
}
146146
#endif

Sources/TracingOpenTelemetrySupport/SpanAttribute+NetSemantics.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,29 +57,29 @@ public struct NetAttributes: SpanAttributeNamespace {
5757
self.attributes = attributes
5858
}
5959

60-
public struct NestedAttributes: NestedSpanAttributesProtocol {
60+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
6161
public init() {}
6262

6363
/// Transport protocol used.
64-
public var transport: SpanAttributeKey<String> { .init(name: SpanAttributeName.Net.transport) }
64+
public var transport: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Net.transport) }
6565

6666
/// Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6).
67-
public var peerIP: SpanAttributeKey<String> { .init(name: SpanAttributeName.Net.peerIP) }
67+
public var peerIP: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Net.peerIP) }
6868

6969
/// Remote port number as an integer. E.g., 80.
70-
public var peerPort: SpanAttributeKey<Int> { .init(name: SpanAttributeName.Net.peerPort) }
70+
public var peerPort: SpanAttribute.Key<Int> { .init(name: SpanAttributeName.Net.peerPort) }
7171

7272
/// Remote hostname or similar.
73-
public var peerName: SpanAttributeKey<String> { .init(name: SpanAttributeName.Net.peerName) }
73+
public var peerName: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Net.peerName) }
7474

7575
/// Like `peerIP` but for the host IP. Useful in case of a multi-IP host.
76-
public var hostIP: SpanAttributeKey<String> { .init(name: SpanAttributeName.Net.hostIP) }
76+
public var hostIP: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Net.hostIP) }
7777

7878
/// Like `peerPort` but for the host port.
79-
public var hostPort: SpanAttributeKey<Int> { .init(name: SpanAttributeName.Net.hostPort) }
79+
public var hostPort: SpanAttribute.Key<Int> { .init(name: SpanAttributeName.Net.hostPort) }
8080

8181
/// Local hostname or similar.
82-
public var hostName: SpanAttributeKey<String> { .init(name: SpanAttributeName.Net.hostName) }
82+
public var hostName: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Net.hostName) }
8383
}
8484
}
8585
#endif

Sources/TracingOpenTelemetrySupport/SpanAttribute+PeerSemantics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ public struct PeerAttributes: SpanAttributeNamespace {
4545
self.attributes = attributes
4646
}
4747

48-
public struct NestedAttributes: NestedSpanAttributesProtocol {
48+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
4949
public init() {}
5050

5151
/// The service.name of the remote service. SHOULD be equal to the actual service.name resource attribute of the remote service if any.
52-
public var service: SpanAttributeKey<String> { .init(name: SpanAttributeName.Peer.service) }
52+
public var service: SpanAttribute.Key<String> { .init(name: SpanAttributeName.Peer.service) }
5353
}
5454
}
5555
#endif

Sources/TracingOpenTelemetrySupport/SpanAttribute+RPCSemantics.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ public struct RPCAttributes: SpanAttributeNamespace {
4949
self.attributes = attributes
5050
}
5151

52-
public struct NestedAttributes: NestedSpanAttributesProtocol {
52+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
5353
public init() {}
5454

5555
/// A string identifying the remoting system, e.g., "grpc", "java_rmi" or "wcf".
56-
public var system: SpanAttributeKey<String> { .init(name: SpanAttributeName.RPC.system) }
56+
public var system: SpanAttribute.Key<String> { .init(name: SpanAttributeName.RPC.system) }
5757

5858
/// The full name of the service being called, including its package name, if applicable.
59-
public var service: SpanAttributeKey<String> { .init(name: SpanAttributeName.RPC.service) }
59+
public var service: SpanAttribute.Key<String> { .init(name: SpanAttributeName.RPC.service) }
6060

6161
/// The name of the method being called, must be equal to the $method part in the span name.
62-
public var method: SpanAttributeKey<String> { .init(name: SpanAttributeName.RPC.method) }
62+
public var method: SpanAttribute.Key<String> { .init(name: SpanAttributeName.RPC.method) }
6363
}
6464
}
6565
#endif

Tests/TracingTests/SpanTests.swift

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,12 @@ final class SpanTests: XCTestCase {
6969
}
7070

7171
func testSpanAttributeIsExpressibleByArrayLiteral() {
72-
let attributes: SpanAttribute = [true, "test"]
73-
guard case .array(let arrayValue) = attributes else {
74-
XCTFail("Expected array attribute, got \(attributes).")
75-
return
76-
}
77-
78-
guard case .bool(let boolValue) = arrayValue[0] else {
79-
XCTFail("Expected bool attribute, got \(arrayValue[0])")
80-
return
81-
}
82-
XCTAssert(boolValue)
83-
84-
guard case .string(let stringValue) = arrayValue[1] else {
85-
XCTFail("Expected string attribute, got \(arrayValue[1])")
86-
return
87-
}
88-
XCTAssertEqual(stringValue, "test")
72+
let s = InstrumentationSystem.tracer.startSpan("", baggage: .topLevel)
73+
s.attributes["hi"] = [42, 21]
74+
s.attributes["hi"] = [42.10, 21.0]
75+
s.attributes["hi"] = [true, false]
76+
s.attributes["hi"] = ["one", "two"]
77+
s.attributes["hi"] = [1, 2, 34]
8978
}
9079

9180
func testSpanAttributesUX() {
@@ -99,9 +88,9 @@ final class SpanTests: XCTestCase {
9988
attributes["bools"] = [true, false, true]
10089
attributes["alive"] = false
10190

102-
XCTAssertEqual(attributes["thing.name"], SpanAttribute.string("hello"))
103-
XCTAssertEqual(attributes["meaning.of.life"], SpanAttribute.int(42))
104-
XCTAssertEqual(attributes["alive"], SpanAttribute.bool(false))
91+
XCTAssertEqual(attributes["thing.name"]?.toSpanAttribute(), SpanAttribute.string("hello"))
92+
XCTAssertEqual(attributes["meaning.of.life"]?.toSpanAttribute(), SpanAttribute.int(42))
93+
XCTAssertEqual(attributes["alive"]?.toSpanAttribute(), SpanAttribute.bool(false))
10594

10695
// using swift 5.2, we can improve upon that by using type-safe, keypath-based subscripts:
10796
#if swift(>=5.2)
@@ -124,14 +113,18 @@ final class SpanTests: XCTestCase {
124113
// normally we can use just the span attribute values, and it is not type safe or guided in any way:
125114
attributes.sampleHttp.customType = CustomAttributeValue()
126115

127-
XCTAssertEqual(attributes["http.custom_value"], SpanAttribute.stringConvertible(CustomAttributeValue()))
116+
XCTAssertEqual(attributes["http.custom_value"]?.toSpanAttribute(), SpanAttribute.stringConvertible(CustomAttributeValue()))
128117
XCTAssertEqual(String(reflecting: attributes.sampleHttp.customType), "Optional(CustomAttributeValue())")
129118
XCTAssertEqual(attributes.sampleHttp.customType, CustomAttributeValue())
130119
#endif
131120
}
132121

133122
func testSpanAttributesAreIterable() {
134-
let attributes: SpanAttributes = ["0": 0, "1": true, "2": "test"]
123+
let attributes: SpanAttributes = [
124+
"0": 0,
125+
"1": true,
126+
"2": "test",
127+
]
135128

136129
var dictionary = [String: SpanAttribute]()
137130
attributes.forEach { name, attribute in
@@ -176,21 +169,20 @@ final class SpanTests: XCTestCase {
176169
XCTAssertEqual(child.links[0].baggage[TestBaggageContextKey.self], "test")
177170
#if swift(>=5.2)
178171
XCTAssertEqual(child.links[0].attributes.sampleHttp.statusCode, 418)
179-
#else
180-
guard case .some(.int(let statusCode)) = child.links[0].attributes["http.status_code"] else {
172+
#endif
173+
guard case .some(.int(let statusCode)) = child.links[0].attributes["http.status_code"]?.toSpanAttribute() else {
181174
XCTFail("Expected int value for http.status_code")
182175
return
183176
}
184177
XCTAssertEqual(statusCode, 418)
185-
#endif
186178
}
187179
}
188180

189181
// ==== ----------------------------------------------------------------------------------------------------------------
190182
// MARK: Example Span attributes
191183

192184
extension SpanAttribute {
193-
var name: SpanAttributeKey<String> {
185+
var name: Key<String> {
194186
return "name"
195187
}
196188
}
@@ -214,18 +206,18 @@ public struct HTTPAttributes: SpanAttributeNamespace {
214206
self.attributes = attributes
215207
}
216208

217-
public struct NestedAttributes: NestedSpanAttributesProtocol {
209+
public struct NestedSpanAttributes: NestedSpanAttributesProtocol {
218210
public init() {}
219211

220-
public var statusCode: SpanAttributeKey<Int> {
212+
public var statusCode: SpanAttribute.Key<Int> {
221213
"http.status_code"
222214
}
223215

224-
public var codesArray: SpanAttributeKey<[Int]> {
216+
public var codesArray: SpanAttribute.Key<[Int]> {
225217
"http.codes_array"
226218
}
227219

228-
public var customType: SpanAttributeKey<CustomAttributeValue> {
220+
public var customType: SpanAttribute.Key<CustomAttributeValue> {
229221
"http.custom_value"
230222
}
231223
}

Tests/TracingTests/TracerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ struct FakeHTTPServer {
9999
let span = tracer.startSpan("GET \(request.path)", baggage: baggage)
100100

101101
let response = self.catchAllHandler(span.baggage, request, self.client)
102-
span.attributes["http.status"] = .int(response.status)
102+
span.attributes["http.status"] = response.status
103103

104104
span.end()
105105
}

0 commit comments

Comments
 (0)