Skip to content

Commit 45b618d

Browse files
feat: plugin implementation
1 parent 647e2b4 commit 45b618d

File tree

14 files changed

+771
-98
lines changed

14 files changed

+771
-98
lines changed

Package.resolved

Lines changed: 30 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ let package = Package(
77
products: [
88
.library(
99
name: "LaunchDarklyObservability",
10-
targets: ["LaunchDarklyObservability"]),
10+
targets: ["Client", "LaunchDarklyObservability"]),
1111
],
1212
dependencies: [
13-
.package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.0.0"),
13+
.package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "2.0.0"),
14+
.package(url: "https://github.com/launchdarkly/ios-client-sdk.git", from: "9.14.0"),
1415
],
1516
targets: [
17+
.target(
18+
name: "ObserveAPI",
19+
dependencies: [
20+
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
21+
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
22+
]
23+
),
1624
.target(
1725
name: "Instrumentation",
1826
dependencies: [
@@ -29,17 +37,38 @@ let package = Package(
2937
.target(
3038
name: "LaunchDarklyObservability",
3139
dependencies: [
32-
"Instrumentation",
40+
"ObserveAPI",
41+
"Client",
3342
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
3443
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
35-
.product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"),
36-
.product(name: "StdoutExporter", package: "opentelemetry-swift"),
37-
.product(name: "ResourceExtension", package: "opentelemetry-swift"),
3844
]
3945
),
4046
.testTarget(
4147
name: "LaunchDarklyObservabilityTests",
4248
dependencies: ["LaunchDarklyObservability"]
4349
),
50+
.target(
51+
name: "Client",
52+
dependencies: [
53+
"ObserveAPI",
54+
"Instrumentation",
55+
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
56+
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
57+
.product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"),
58+
.product(name: "StdoutExporter", package: "opentelemetry-swift"),
59+
.product(name: "ResourceExtension", package: "opentelemetry-swift"),
60+
]
61+
),
62+
.target(
63+
name: "ObservabilityPlugins",
64+
dependencies: [
65+
"Client",
66+
"LaunchDarklyObservability",
67+
"Instrumentation",
68+
.product(name: "ResourceExtension", package: "opentelemetry-swift"),
69+
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
70+
.product(name: "LaunchDarkly", package: "ios-client-sdk"),
71+
]
72+
)
4473
]
4574
)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import Foundation.NSDate
2+
import UIKit.UIApplication
3+
import ObserveAPI
4+
import OpenTelemetryApi
5+
import OpenTelemetrySdk
6+
import ResourceExtension
7+
@_exported import Instrumentation
8+
9+
import Combine
10+
11+
public final class ObservabilityClient: @unchecked Sendable, Observe {
12+
private let tracerFacade: TracerFacade
13+
private let loggerFacade: LoggerFacade
14+
private let meterFacade: MeterFacade
15+
private var session: Session
16+
17+
private var cachedSpans = [String: Span]()
18+
private var cancellables = Set<AnyCancellable>()
19+
20+
private var onWillEndSession: @Sendable (_ sessionId: String) -> Void {
21+
{ [weak self] sessionId in
22+
self?.willEndSession(sessionId)
23+
}
24+
}
25+
private var onDidStartSession: @Sendable (_ sessionId: String) -> Void {
26+
{ [weak self] sessionId in
27+
self?.didStartSession(sessionId)
28+
}
29+
}
30+
31+
public init(configuration: Configuration = .init(), sdkKey: String = "") {
32+
self.tracerFacade = TracerFacade(configuration: configuration)
33+
self.loggerFacade = LoggerFacade(configuration: configuration)
34+
self.meterFacade = MeterFacade(configuration: configuration)
35+
self.session = Session(options: SessionOptions(timeout: configuration.sessionTimeout))
36+
self.registerPropagators()
37+
38+
39+
self.session.start(
40+
onWillEndSession: onWillEndSession,
41+
onDidStartSession: onDidStartSession
42+
)
43+
}
44+
45+
private func didStartSession(_ id: String) {
46+
let span = spanBuilder(spanName: "app.session.started")
47+
.setSpanKind(spanKind: .client)
48+
.startSpan()
49+
cachedSpans[id] = span
50+
}
51+
52+
private func willEndSession(_ id: String) {
53+
guard let span = cachedSpans[id] else { return }
54+
span.end()
55+
}
56+
57+
private func registerPropagators() {
58+
OpenTelemetry.registerPropagators(
59+
textPropagators: [
60+
W3CTraceContextPropagator(),
61+
B3Propagator(),
62+
JaegerPropagator(),
63+
],
64+
baggagePropagator: W3CBaggagePropagator()
65+
)
66+
}
67+
68+
// MARK: - Public API
69+
70+
public static func defaultResource() -> Resource {
71+
DefaultResources().get()
72+
}
73+
74+
public func spanBuilder(spanName: String) -> SpanBuilder {
75+
tracerFacade.spanBuilder(spanName: spanName)
76+
}
77+
78+
79+
public func spanBuilder(spanName: String, attributes: [String: AttributeValue]) -> Span {
80+
let builder = tracerFacade
81+
.spanBuilder(spanName: spanName)
82+
83+
attributes.forEach {
84+
builder.setAttribute(key: $0.key, value: $0.value)
85+
}
86+
87+
return builder.startSpan()
88+
}
89+
90+
91+
public func recordMetric(metric: ObserveAPI.Metric) {
92+
var gauge = meterFacade.meter
93+
.gaugeBuilder(name: metric.name)
94+
.build()
95+
gauge.record(value: metric.value, attributes: metric.attributes)
96+
}
97+
98+
public func recordCount(metric: ObserveAPI.Metric) {
99+
var counter = meterFacade.meter.counterBuilder(name: metric.name).ofDoubles().build()
100+
counter.add(value: metric.value, attributes: metric.attributes)
101+
}
102+
103+
public func recordIncr(metric: ObserveAPI.Metric) {
104+
var counter = meterFacade.meter.counterBuilder(name: metric.name).build()
105+
counter.add(value: 1, attributes: metric.attributes)
106+
}
107+
108+
public func recordHistogram(metric: ObserveAPI.Metric) {
109+
var histogram = meterFacade.meter.histogramBuilder(name: metric.name).build()
110+
histogram.record(value: metric.value, attributes: metric.attributes)
111+
}
112+
113+
public func recordUpDownCounter(metric: ObserveAPI.Metric) {
114+
var upDownCounter = meterFacade.meter.upDownCounterBuilder(name: metric.name).ofDoubles().build()
115+
upDownCounter.add(value: metric.value, attributes: metric.attributes)
116+
}
117+
118+
public func recordError(error: any Error, attributes: [String : OpenTelemetryApi.AttributeValue]) {
119+
let builder = tracerFacade.tracer.spanBuilder(spanName: "highlight.error")
120+
121+
if let parent = tracerFacade.currentSpan {
122+
builder.setParent(parent)
123+
}
124+
125+
attributes.forEach {
126+
builder.setAttribute(key: $0.key, value: $0.value)
127+
}
128+
129+
let span = builder.startSpan()
130+
span.recordException(ErrorSpanException(error: error), attributes: attributes)
131+
span.end()
132+
}
133+
134+
public func recordLog(message: String, severity: Severity, attributes: [String : OpenTelemetryApi.AttributeValue]) {
135+
loggerFacade.logger.logRecordBuilder()
136+
.setBody(.string(message))
137+
.setTimestamp(.now)
138+
.setSeverity(severity)
139+
.setAttributes(attributes)
140+
.emit()
141+
}
142+
143+
public func startSpan(name: String, attributes: [String : OpenTelemetryApi.AttributeValue]) -> any OpenTelemetryApi.Span {
144+
let builder = tracerFacade
145+
.spanBuilder(spanName: name)
146+
147+
if let parent = tracerFacade.currentSpan {
148+
builder.setParent(parent)
149+
}
150+
151+
attributes.forEach {
152+
builder.setAttribute(key: $0.key, value: $0.value)
153+
}
154+
155+
return builder.startSpan()
156+
}
157+
}
158+
159+
struct ErrorSpanException: SpanException {
160+
private let error: Error
161+
var type: String {
162+
String(describing: error)
163+
}
164+
165+
var message: String? {
166+
String(describing: error)
167+
}
168+
169+
var stackTrace: [String]? {
170+
Thread.callStackSymbols
171+
}
172+
173+
init(error: Error) {
174+
self.error = error
175+
}
176+
}

0 commit comments

Comments
 (0)