Skip to content

Commit ffbdc90

Browse files
author
Gal Shelef
committed
Make Metric and SpanData conform to Codable to support persisting them.
* Moved existing StdoutExporter-specific Encodable implementations to StdoutExporter. * Made Metric (and its dependencies) conform to Codable. * Made SpanData (and its dependencies) conform to Codable. * Added MetricData Codable specializations. * Wrote explicit encoding & decoding when needed (enums with associated values, types with tuple members). * Added tests for encoding and decoding Metric & SpanData objects. * Made Metric (and its dependencies) conform to Equatable as required for some tests' verifications.
1 parent 2c903eb commit ffbdc90

File tree

24 files changed

+784
-43
lines changed

24 files changed

+784
-43
lines changed

Sources/Exporters/Stdout/StdoutExporter.swift

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class StdoutExporter: SpanExporter {
5858
public func shutdown() {}
5959
}
6060

61-
private struct SpanExporterData: Encodable {
61+
private struct SpanExporterData {
6262
private let span: String
6363
private let traceId: String
6464
private let spanId: String
@@ -83,3 +83,92 @@ private struct SpanExporterData: Encodable {
8383
self.attributes = span.attributes
8484
}
8585
}
86+
87+
extension SpanExporterData: Encodable {
88+
enum CodingKeys: String, CodingKey {
89+
case span
90+
case traceId
91+
case spanId
92+
case spanKind
93+
case traceFlags
94+
case traceState
95+
case parentSpanId
96+
case start
97+
case duration
98+
case attributes
99+
}
100+
101+
enum TraceFlagsCodingKeys: String, CodingKey {
102+
case sampled
103+
}
104+
105+
enum TraceStateCodingKeys: String, CodingKey {
106+
case entries
107+
}
108+
109+
enum TraceStateEntryCodingKeys: String, CodingKey {
110+
case key
111+
case value
112+
}
113+
114+
struct AttributesCodingKeys: CodingKey {
115+
var stringValue: String
116+
var intValue: Int?
117+
118+
init?(intValue: Int) {
119+
self.stringValue = "\(intValue)"
120+
self.intValue = intValue
121+
}
122+
123+
init?(stringValue: String) {
124+
self.stringValue = stringValue
125+
}
126+
}
127+
128+
enum AttributeValueCodingKeys: String, CodingKey {
129+
case description
130+
}
131+
132+
func encode(to encoder: Encoder) throws {
133+
var container = encoder.container(keyedBy: CodingKeys.self)
134+
135+
try container.encode(span, forKey: .span)
136+
try container.encode(traceId, forKey: .traceId)
137+
try container.encode(spanId, forKey: .spanId)
138+
try container.encode(spanKind, forKey: .spanKind)
139+
140+
var traceFlagsContainer = container.nestedContainer(keyedBy: TraceFlagsCodingKeys.self, forKey: .traceFlags)
141+
try traceFlagsContainer.encode(traceFlags.sampled, forKey: .sampled)
142+
143+
var traceStateContainer = container.nestedContainer(keyedBy: TraceStateCodingKeys.self, forKey: .traceState)
144+
var traceStateEntriesContainer = traceStateContainer.nestedUnkeyedContainer(forKey: .entries)
145+
146+
try traceState.entries.forEach { entry in
147+
var traceStateEntryContainer = traceStateEntriesContainer.nestedContainer(keyedBy: TraceStateEntryCodingKeys.self)
148+
149+
try traceStateEntryContainer.encode(entry.key, forKey: .key)
150+
try traceStateEntryContainer.encode(entry.value, forKey: .value)
151+
}
152+
153+
try container.encodeIfPresent(parentSpanId, forKey: .parentSpanId)
154+
try container.encode(start, forKey: .start)
155+
try container.encode(duration, forKey: .duration)
156+
157+
var attributesContainer = container.nestedContainer(keyedBy: AttributesCodingKeys.self, forKey: .attributes)
158+
159+
try attributes.forEach { attribute in
160+
161+
if let attributeValueCodingKey = AttributesCodingKeys(stringValue: attribute.key) {
162+
var attributeValueContainer = attributesContainer.nestedContainer(keyedBy: AttributeValueCodingKeys.self, forKey: attributeValueCodingKey)
163+
164+
try attributeValueContainer.encode(attribute.value.description, forKey: .description)
165+
} else {
166+
// this should never happen
167+
let encodingContext = EncodingError.Context(codingPath: attributesContainer.codingPath,
168+
debugDescription: "Failed to create coding key")
169+
170+
throw EncodingError.invalidValue(attribute, encodingContext)
171+
}
172+
}
173+
}
174+
}

Sources/OpenTelemetryApi/Common/AttributeValue.swift

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,70 @@ public enum AttributeValue: Equatable, CustomStringConvertible, Hashable {
6161
}
6262
}
6363

64-
extension AttributeValue: Encodable {
64+
// this explicit Codable implementation for AttributeValue will probably be redundant with Swift 5.5
65+
extension AttributeValue: Codable {
6566
enum CodingKeys: String, CodingKey {
66-
case description
67+
case string
68+
case bool
69+
case int
70+
case double
71+
case stringArray
72+
case boolArray
73+
case intArray
74+
case doubleArray
75+
}
76+
77+
public init(from decoder: Decoder) throws {
78+
let container = try decoder.container(keyedBy: CodingKeys.self)
79+
80+
guard container.allKeys.count == 1 else {
81+
let context = DecodingError.Context(
82+
codingPath: container.codingPath,
83+
debugDescription: "Invalid number of keys found, expected one.")
84+
throw DecodingError.typeMismatch(Status.self, context)
85+
}
86+
87+
switch container.allKeys.first.unsafelyUnwrapped {
88+
case .string:
89+
self = .string(try container.decode(String.self, forKey: .string))
90+
case .bool:
91+
self = .bool(try container.decode(Bool.self, forKey: .bool))
92+
case .int:
93+
self = .int(try container.decode(Int.self, forKey: .int))
94+
case .double:
95+
self = .double(try container.decode(Double.self, forKey: .double))
96+
case .stringArray:
97+
self = .stringArray(try container.decode([String].self, forKey: .stringArray))
98+
case .boolArray:
99+
self = .boolArray(try container.decode([Bool].self, forKey: .boolArray))
100+
case .intArray:
101+
self = .intArray(try container.decode([Int].self, forKey: .intArray))
102+
case .doubleArray:
103+
self = .doubleArray(try container.decode([Double].self, forKey: .doubleArray))
104+
}
67105
}
68106

69107
public func encode(to encoder: Encoder) throws {
108+
70109
var container = encoder.container(keyedBy: CodingKeys.self)
71-
try container.encode(description, forKey: .description)
110+
111+
switch self {
112+
case .string(let value):
113+
try container.encode(value, forKey: .string)
114+
case .bool(let value):
115+
try container.encode(value, forKey: .bool)
116+
case .int(let value):
117+
try container.encode(value, forKey: .int)
118+
case .double(let value):
119+
try container.encode(value, forKey: .double)
120+
case .stringArray(let value):
121+
try container.encode(value, forKey: .stringArray)
122+
case .boolArray(let value):
123+
try container.encode(value, forKey: .boolArray)
124+
case .intArray(let value):
125+
try container.encode(value, forKey: .intArray)
126+
case .doubleArray(let value):
127+
try container.encode(value, forKey: .doubleArray)
128+
}
72129
}
73130
}

Sources/OpenTelemetryApi/Trace/SpanContext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Foundation
88
/// A class that represents a span context. A span context contains the state that must propagate to
99
/// child Spans and across process boundaries. It contains the identifiers race_id and span_id
1010
/// associated with the Span and a set of options.
11-
public struct SpanContext: Equatable, CustomStringConvertible, Hashable {
11+
public struct SpanContext: Equatable, CustomStringConvertible, Hashable, Codable {
1212
/// The trace identifier associated with this SpanContext
1313
public private(set) var traceId: TraceId
1414

Sources/OpenTelemetryApi/Trace/SpanId.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Foundation
77

88
/// A struct that represents a span identifier. A valid span identifier is an 8-byte array with at
99
/// least one non-zero byte.
10-
public struct SpanId: Equatable, Comparable, Hashable, CustomStringConvertible {
10+
public struct SpanId: Equatable, Comparable, Hashable, CustomStringConvertible, Codable {
1111
public static let size = 8
1212
public static let invalidId: UInt64 = 0
1313
public static let invalid = SpanId(id: invalidId)

Sources/OpenTelemetryApi/Trace/SpanKind.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Foundation
77

88
/// Type of span. Can be used to specify additional relationships between spans in addition to a
99
/// parent/child relationship
10-
public enum SpanKind: String, Equatable {
10+
public enum SpanKind: String, Equatable, Codable {
1111
/// Default value. Indicates that the span is used internally.
1212
case `internal`
1313
/// ndicates that the span covers server-side handling of an RPC or other remote request.

Sources/OpenTelemetryApi/Trace/Status.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,58 @@ extension Status: CustomStringConvertible {
6060
}
6161
}
6262
}
63+
64+
// this explicit Codable implementation for Status will probably be redundant with Swift 5.5
65+
extension Status: Codable {
66+
enum CodingKeys: String, CodingKey {
67+
case ok
68+
case unset
69+
case error
70+
}
71+
72+
enum EmptyCodingKeys: CodingKey {
73+
74+
}
75+
76+
enum ErrorCodingKeys: String, CodingKey {
77+
case description
78+
}
79+
80+
public init(from decoder: Decoder) throws {
81+
let container = try decoder.container(keyedBy: CodingKeys.self)
82+
83+
guard container.allKeys.count == 1 else {
84+
let context = DecodingError.Context(
85+
codingPath: container.codingPath,
86+
debugDescription: "Invalid number of keys found, expected one.")
87+
throw DecodingError.typeMismatch(Status.self, context)
88+
}
89+
90+
switch container.allKeys.first.unsafelyUnwrapped {
91+
case .ok:
92+
_ = try container.nestedContainer(keyedBy: EmptyCodingKeys.self, forKey: .ok)
93+
self = .ok
94+
case .unset:
95+
_ = try container.nestedContainer(keyedBy: EmptyCodingKeys.self, forKey: .unset)
96+
self = .unset
97+
case .error:
98+
let nestedContainer = try container.nestedContainer(keyedBy: ErrorCodingKeys.self, forKey: .error)
99+
self = .error(description: try nestedContainer.decode(String.self, forKey: .description))
100+
}
101+
}
102+
103+
public func encode(to encoder: Encoder) throws {
104+
105+
var container = encoder.container(keyedBy: CodingKeys.self)
106+
107+
switch self {
108+
case .ok:
109+
_ = container.nestedContainer(keyedBy: EmptyCodingKeys.self, forKey: .ok)
110+
case .unset:
111+
_ = container.nestedContainer(keyedBy: EmptyCodingKeys.self, forKey: .unset)
112+
case .error(let description):
113+
var nestedContainer = container.nestedContainer(keyedBy: ErrorCodingKeys.self, forKey: .error)
114+
try nestedContainer.encode(description, forKey: .description)
115+
}
116+
}
117+
}

Sources/OpenTelemetryApi/Trace/TraceFlags.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Foundation
88
/// A struct that represents global trace options. These options are propagated to all child spans.
99
/// These determine features such as whether a Span should be traced. It is
1010
/// implemented as a bitmask.
11-
public struct TraceFlags: Equatable, CustomStringConvertible {
11+
public struct TraceFlags: Equatable, CustomStringConvertible, Codable {
1212
/// Default options. Nothing set.
1313
private static let defaultOptions: UInt8 = 0
1414
/// Bit to represent whether trace is sampled or not.
@@ -88,14 +88,3 @@ public struct TraceFlags: Equatable, CustomStringConvertible {
8888
"TraceFlags{sampled=\(sampled)}"
8989
}
9090
}
91-
92-
extension TraceFlags: Encodable {
93-
enum CodingKeys: String, CodingKey {
94-
case sampled
95-
}
96-
97-
public func encode(to encoder: Encoder) throws {
98-
var container = encoder.container(keyedBy: CodingKeys.self)
99-
try container.encode(sampled, forKey: .sampled)
100-
}
101-
}

Sources/OpenTelemetryApi/Trace/TraceId.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Foundation
77

88
/// A struct that represents a trace identifier. A valid trace identifier is a 16-byte array with at
99
/// least one non-zero byte.
10-
public struct TraceId: Comparable, Hashable, CustomStringConvertible, Equatable {
10+
public struct TraceId: Comparable, Hashable, CustomStringConvertible, Equatable, Codable {
1111
public static let size = 16
1212
public static let invalidId: UInt64 = 0
1313
public static let invalid = TraceId()

Sources/OpenTelemetryApi/Trace/TraceState.swift

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Foundation
1313
/// forward slashes /.
1414
/// Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the
1515
/// range 0x20 to 0x7E) except comma , and =.
16-
public struct TraceState: Equatable {
16+
public struct TraceState: Equatable, Codable {
1717
private static let maxKeyValuePairs = 32
1818

1919
public private(set) var entries = [Entry]()
@@ -78,7 +78,7 @@ public struct TraceState: Equatable {
7878
}
7979

8080
/// Immutable key-value pair for TraceState
81-
public struct Entry: Equatable, Encodable {
81+
public struct Entry: Equatable, Codable {
8282
/// The key of the Entry
8383
public private(set) var key: String
8484

@@ -99,14 +99,3 @@ public struct TraceState: Equatable {
9999
}
100100
}
101101
}
102-
103-
extension TraceState: Encodable {
104-
enum CodingKeys: String, CodingKey {
105-
case entries
106-
}
107-
108-
public func encode(to encoder: Encoder) throws {
109-
var container = encoder.container(keyedBy: CodingKeys.self)
110-
try container.encode(entries, forKey: .entries)
111-
}
112-
}

Sources/OpenTelemetrySdk/Common/InstrumentationLibraryInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Foundation
77

88
/// Holds information about the instrumentation library specified when creating an instance of
99
/// TracerSdk using TracerProviderSdk.
10-
public struct InstrumentationLibraryInfo: Hashable {
10+
public struct InstrumentationLibraryInfo: Hashable, Codable {
1111
public private(set) var name: String = ""
1212
public private(set) var version: String?
1313

0 commit comments

Comments
 (0)