Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: make testing distributed header injection easier in Example app #1954

Merged
merged 2 commits into from
Jul 17, 2024
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
4 changes: 4 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
3C5D636D2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; };
3C5D691F2B76825500C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; };
3C5D69222B76826000C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; };
3C62C3612C3E852F00C7E336 /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */; };
3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; };
3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; };
3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; };
Expand Down Expand Up @@ -2105,6 +2106,7 @@
3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationMonitorTests.swift; sourceTree = "<group>"; };
3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = "<group>"; };
3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = "<group>"; };
3C62C3602C3E852F00C7E336 /* MultiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelector.swift; sourceTree = "<group>"; };
3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = "<group>"; };
3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = "<group>"; };
3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+Datadog.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4616,6 +4618,7 @@
61441C902461A648003D8BB8 /* ConsoleOutputInterceptor.swift */,
61441C912461A648003D8BB8 /* UIButton+Disabling.swift */,
D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */,
3C62C3602C3E852F00C7E336 /* MultiSelector.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -8411,6 +8414,7 @@
618236892710560900125326 /* DebugWebviewViewController.swift in Sources */,
61F74AF426F20E4600E5F5ED /* DebugCrashReportingWithRUMViewController.swift in Sources */,
1434A4662B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */,
3C62C3612C3E852F00C7E336 /* MultiSelector.swift in Sources */,
61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */,
61441C0524616DE9003D8BB8 /* ExampleAppDelegate.swift in Sources */,
61020C2C2758E853005EEAEA /* DebugBackgroundEventsViewController.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,32 @@ internal class DebugManualTraceInjectionViewController: UIHostingController<Debu

private var currentSession: URLSession? = nil

extension TraceContextInjection {
func toString() -> String {
switch self {
case .all:
return "All"
case .sampled:
return "Sampled"
}
}
}

@available(iOS 14.0, *)
internal struct DebugManualTraceInjectionView: View {
enum TraceHeaderType: String, CaseIterable {
enum TraceHeaderType: String, CaseIterable, Identifiable {
case datadog = "Datadog"
case w3c = "W3C"
case b3Single = "B3-Single"
case b3Multiple = "B3-Multiple"

var id: String { rawValue }
}

@State private var spanName = "network request"
@State private var requestURL = "http://127.0.0.1:8000"
@State private var selectedTraceHeaderType: TraceHeaderType = .datadog
@State private var requestURL = "https://httpbin.org/get"
@State private var selectedTraceHeaderTypes: Set<TraceHeaderType> = [.datadog, .w3c]
@State private var selectedTraceContextInjection: TraceContextInjection = .all
@State private var sampleRate: Float = 100.0
@State private var isRequestPending = false

Expand Down Expand Up @@ -59,12 +73,18 @@ internal struct DebugManualTraceInjectionView: View {
Section(header: Text("Span name:")) {
TextField("", text: $spanName)
}
Picker("Trace header type:", selection: $selectedTraceHeaderType) {
ForEach(TraceHeaderType.allCases, id: \.self) { headerType in
Text(headerType.rawValue)
Picker("Trace context injection:", selection: $selectedTraceContextInjection) {
ForEach(TraceContextInjection.allCases, id: \.self) { headerType in
Text(headerType.toString())
}
}
.pickerStyle(.inline)
MultiSelector(
label: Text("Trace header type:"),
options: TraceHeaderType.allCases,
optionToString: { $0.rawValue },
selected: $selectedTraceHeaderTypes
)
Section(header: Text("Trace sample Rate")) {
Slider(
value: $sampleRate,
Expand Down Expand Up @@ -101,42 +121,44 @@ internal struct DebugManualTraceInjectionView: View {
}

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpMethod = "GET"

let span = Tracer.shared().startRootSpan(operationName: spanName)

switch selectedTraceHeaderType {
case .datadog:
let writer = HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
traceContextInjection: .all
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .w3c:
let writer = W3CHTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
tracestate: [:],
traceContextInjection: .all
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .b3Single:
let writer = B3HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
injectEncoding: .single,
traceContextInjection: .all
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .b3Multiple:
let writer = B3HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
injectEncoding: .multiple,
traceContextInjection: .all
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
for selectedTraceHeaderType in selectedTraceHeaderTypes {
switch selectedTraceHeaderType {
case .datadog:
let writer = HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
traceContextInjection: selectedTraceContextInjection
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .w3c:
let writer = W3CHTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
tracestate: [:],
traceContextInjection: selectedTraceContextInjection
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .b3Single:
let writer = B3HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
injectEncoding: .single,
traceContextInjection: selectedTraceContextInjection
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
case .b3Multiple:
let writer = B3HTTPHeadersWriter(
samplingStrategy: .custom(sampleRate: sampleRate),
injectEncoding: .multiple,
traceContextInjection: selectedTraceContextInjection
)
Tracer.shared().inject(spanContext: span.context, writer: writer)
writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
}
}

send(request: request) {
Expand All @@ -147,7 +169,17 @@ internal struct DebugManualTraceInjectionView: View {

private func send(request: URLRequest, completion: @escaping () -> Void) {
isRequestPending = true
let task = session.dataTask(with: request) { _, _, _ in
let task = session.dataTask(with: request) { data, response, _ in
let httpResponse = response as! HTTPURLResponse
print("🚀 Request completed with status code: \(httpResponse.statusCode)")

// pretty print response
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let json = json {
print("🚀 Response: \(json)")
}
}
completion()
DispatchQueue.main.async { self.isRequestPending = false }
}
Expand Down
85 changes: 85 additions & 0 deletions Datadog/Example/Utils/MultiSelector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import SwiftUI

@available(iOS 14.0, *)
struct MultiSelector<LabelView: View, Selectable: Identifiable & Hashable>: View {
let label: LabelView
let options: [Selectable]
let optionToString: (Selectable) -> String

var selected: Binding<Set<Selectable>>

private var formattedSelectedListString: String {
ListFormatter.localizedString(
byJoining: selected.wrappedValue.map {
optionToString($0)
}
)
}

var body: some View {
NavigationLink(destination: multiSelectionView()) {
HStack {
label
Spacer()
Text(formattedSelectedListString)
.foregroundColor(.gray)
.multilineTextAlignment(.trailing)
}
}
}

private func multiSelectionView() -> some View {
MultiSelectionView(
options: options,
optionToString: optionToString,
selected: selected
)
}
}


@available(iOS 13.0, *)
struct MultiSelectionView<Selectable: Identifiable & Hashable>: View {
let options: [Selectable]
let optionToString: (Selectable) -> String

@Binding var selected: Set<Selectable>

var body: some View {
List {
ForEach(options) { selectable in
Button(action: {
toggleSelection(selectable: selectable)
}) {
HStack {
Text(optionToString(selectable))
.foregroundColor(.black)
Spacer()
if selected.contains(where: {
$0.id == selectable.id
}) {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
.tag(selectable.id)
}
}
.listStyle(GroupedListStyle())
}

private func toggleSelection(selectable: Selectable) {
if let existingIndex = selected.firstIndex(where: { $0.id == selectable.id }) {
selected.remove(at: existingIndex)
} else {
selected.insert(selectable)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// Defines whether the trace context should be injected into all requests or only sampled ones.
public enum TraceContextInjection {
public enum TraceContextInjection: CaseIterable {
/// Injects trace context into all requests irrespective of the sampling decision.
case all

Expand Down