Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ExampleApp/ExampleApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
print(once)
_ = once
return true
}
}
21 changes: 19 additions & 2 deletions ExampleApp/ExampleApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,32 @@
import SwiftUI

struct ContentView: View {
@Environment(Browser.self) var browser

var body: some View {
VStack(spacing: 32) {
Button {
fatalError()
} label: {
Text("Crash")
}
NetworkRequestView()
FeatureFlagView()
Button {
browser.navigate(to: .automaticInstrumentation)
} label: {
Text("Automatic Instrumentation")
}
Button {
browser.navigate(to: .evaluation)
} label: {
Text("Flag evaluation")
}
Button {
browser.navigate(to: .manualInstrumentation)
} label: {
Text("Manual Instrumentation")
}
// NetworkRequestView()
// FeatureFlagView()
}
.padding()
}
Expand Down
18 changes: 18 additions & 0 deletions ExampleApp/ExampleApp/DMButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SwiftUI

struct DMButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
.bold()
.frame(width: 100)
.opacity(configuration.isPressed ? 0.8 : 1)
}
}

extension ButtonStyle where Self == DMButtonStyle {
static var ldStyle: Self { .init() }
}
19 changes: 18 additions & 1 deletion ExampleApp/ExampleApp/ExampleAppApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@ import SwiftUI
@main
struct ExampleAppApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@State private var browser = Browser()


var body: some Scene {
WindowGroup {
ContentView()
NavigationStack(path: $browser.path) {
ContentView()
.navigationDestination(for: Path.self) { path in
switch path {
case .home:
ContentView()
case .manualInstrumentation:
InstrumentationView()
case .automaticInstrumentation:
NetworkRequestView()
case .evaluation:
FeatureFlagView()
}
}
}
.environment(browser)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftUI
import LaunchDarklyObservability
import OpenTelemetryApi

struct InstrumentationView: View {
var body: some View {
VStack {
TraceView()
}
.padding()
}
}

#Preview {
InstrumentationView()
}
39 changes: 39 additions & 0 deletions ExampleApp/ExampleApp/Instrumentation/Manual/TraceView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI
import LaunchDarklyObservability
import OpenTelemetryApi


struct TraceView: View {
@State private var name: String = ""
@State private var started = false
@State private var span = Optional<Span>.none

var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text("Traces")
.bold()
HStack {
TextField(text: $name) {
Text("Span name:")
}
.textCase(.lowercase)
.textInputAutocapitalization(.never)
.textFieldStyle(.roundedBorder)
Spacer()
Text("is started")
Toggle(isOn: $started) {
Text("started")
}
.labelsHidden()
.disabled(name.isEmpty)
.task(id: started) {
guard started else {
span?.end()
return name = ""
}
span = LDObserve.shared.startSpan(name: name)
}
}
}
}
}
24 changes: 24 additions & 0 deletions ExampleApp/ExampleApp/Path.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI

enum Path: Hashable {
case home
case manualInstrumentation
case automaticInstrumentation
case evaluation
}

@Observable final class Browser {
var path = NavigationPath()

func navigate(to path: Path) {
self.path.append(path)
}

func pop() {
self.path.removeLast()
}

func reset() {
self.path = NavigationPath()
}
}
36 changes: 36 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,52 @@ let package = Package(
.product(name: "Installations", package: "KSCrash")
]
),
.target(
name: "Sampling",
dependencies: [
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift")
]
),
.target(
name: "SamplingLive",
dependencies: [
"Sampling",
"Common",
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift")
]
),
.testTarget(
name: "SamplingLiveTests",
dependencies: [
"Sampling",
"SamplingLive",
"Common",
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift")
],
resources: [
.copy("Resources/Stubs/Config.json"),
.copy("Resources/Stubs/MinConfig.json")
]
),
.target(
name: "Observability",
dependencies: [
"Common",
"API",
"CrashReporter",
"CrashReporterLive",
"Sampling",
"SamplingLive",
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift"),
.product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"),
.product(name: "URLSessionInstrumentation", package: "opentelemetry-swift"),
],
resources: [
.copy("Resources/Config.json"),
]
),
.target(
Expand Down
8 changes: 6 additions & 2 deletions Sources/Common/SemanticConventionLD.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
public enum SemanticConvention: String {
case highlightSessionId = "highlight.session_id"
public enum SemanticConvention {
public static let highlightSessionId = "highlight.session_id"
}

public enum LDSemanticAttribute {
public static let ATTR_SAMPLING_RATIO = "launchdarkly.sampling.ratio"
}
6 changes: 3 additions & 3 deletions Sources/LaunchDarklyObservability/LDObserve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,19 @@ public final class LDObserve: @unchecked Sendable, Observe {
client.recordUpDownCounter(metric: metric)
}

public func recordError(error: any Error, attributes: [String : AttributeValue]) {
public func recordError(error: any Error, attributes: [String : AttributeValue] = [:]) {
lock.lock()
defer { lock.unlock() }
client.recordError(error: error, attributes: attributes)
}

public func recordLog(message: String, severity: Severity, attributes: [String : AttributeValue]) {
public func recordLog(message: String, severity: Severity, attributes: [String : AttributeValue] = [:]) {
lock.lock()
defer { lock.unlock() }
client.recordLog(message: message, severity: severity, attributes: attributes)
}

public func startSpan(name: String, attributes: [String : AttributeValue]) -> any Span {
public func startSpan(name: String, attributes: [String : AttributeValue] = [:]) -> any Span {
lock.lock()
defer { lock.unlock() }
return client.startSpan(name: name, attributes: attributes)
Expand Down
51 changes: 42 additions & 9 deletions Sources/Observability/InstrumentationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import URLSessionInstrumentation

import Common
import API
import Sampling
import SamplingLive

private let tracesPath = "/v1/traces"
private let logsPath = "/v1/logs"
Expand All @@ -26,18 +28,30 @@ final class InstrumentationManager {
private var cachedLongCounters = AtomicDictionary<String, LongCounter>()
private var cachedHistograms = AtomicDictionary<String, DoubleHistogram>()
private var cachedUpDownCounters = AtomicDictionary<String, DoubleUpDownCounter>()
private let sampler: ExportSampler

public init(sdkKey: String, options: Options, sessionManager: SessionManager) {
self.sdkKey = sdkKey
self.options = options
self.sessionManager = sessionManager


let sampler = ExportSampler.customSampler()
/// Here is how we will inject the sampling config coming from backend, if the next lines
/// are uncommented, them will inject a example local config for testing purposes only.
///let samplingConfig = loadSampleConfig()
///sampler.setConfig(samplingConfig)


let processorAndProvider = URL(string: options.otlpEndpoint)
.flatMap { $0.appending(path: logsPath) }
.map { url in
OtlpHttpLogExporter(
endpoint: url,
envVarHeaders: options.customHeaders
SamplingLogExporterDecorator(
exporter: OtlpHttpLogExporter(
endpoint: url,
envVarHeaders: options.customHeaders
),
sampler: sampler
)
}
.map { exporter in
Expand Down Expand Up @@ -71,9 +85,12 @@ final class InstrumentationManager {
URL(string: options.otlpEndpoint)
.flatMap { $0.appending(path: tracesPath) }
.map { url in
OtlpHttpTraceExporter(
endpoint: url,
envVarHeaders: options.customHeaders
SamplingTraceExporterDecorator(
exporter: OtlpHttpTraceExporter(
endpoint: url,
envVarHeaders: options.customHeaders
),
sampler: sampler
)
}
.map { exporter in
Expand Down Expand Up @@ -153,6 +170,8 @@ final class InstrumentationManager {
tracer: self.otelTracer
)
)

self.sampler = sampler
}

func recordMetric(metric: Metric) {
Expand Down Expand Up @@ -206,7 +225,7 @@ final class InstrumentationManager {
var attributes = attributes
let sessionId = sessionManager.sessionInfo.id
if !sessionId.isEmpty {
attributes[SemanticConvention.highlightSessionId.rawValue] = .string(sessionId)
attributes[SemanticConvention.highlightSessionId] = .string(sessionId)
}
otelLogger?.logRecordBuilder()
.setBody(.string(message))
Expand All @@ -230,8 +249,8 @@ final class InstrumentationManager {

let sessionId = sessionManager.sessionInfo.id
if !sessionId.isEmpty {
builder?.setAttribute(key: SemanticConvention.highlightSessionId.rawValue, value: sessionId)
attributes[SemanticConvention.highlightSessionId.rawValue] = .string(sessionId)
builder?.setAttribute(key: SemanticConvention.highlightSessionId, value: sessionId)
attributes[SemanticConvention.highlightSessionId] = .string(sessionId)
}


Expand Down Expand Up @@ -265,3 +284,17 @@ final class InstrumentationManager {
return builder.startSpan()
}
}

func loadSampleConfig() -> SamplingConfig? {
guard let url = Bundle.module.url(forResource: "Config", withExtension: "json") else {
return nil
}
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let root = try decoder.decode(Root.self, from: data)
return root.data.sampling
} catch {
return nil
}
}
Loading