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
100 changes: 10 additions & 90 deletions ios/PublicApi/Measure.swiftinterface

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ios/Sources/MeasureSDK/Swift/Config/ConfigLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct BaseConfigLoader: ConfigLoader {

func loadDynamicConfig(onLoaded: @escaping (DynamicConfig) -> Void) {
let cachedConfig = loadConfigFromDisk()
onLoaded(cachedConfig ?? BaseDynamicConfig.default())
onLoaded(cachedConfig ?? BaseDynamicConfig())
refreshConfigFromServer()
}

Expand Down
3 changes: 2 additions & 1 deletion ios/Sources/MeasureSDK/Swift/Config/ConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class BaseConfigProvider: ConfigProvider {
}
private let defaultConfig: Config
private let lockQueue = DispatchQueue(label: "sh.measure.config-provider")
private var dynamicConfig: DynamicConfig = BaseDynamicConfig.default()
private var dynamicConfig: DynamicConfig = BaseDynamicConfig()
private var httpPatternState = HttpPatternState(
disableEventPatterns: [],
blocklistPatterns: [],
Expand Down Expand Up @@ -132,6 +132,7 @@ final class BaseConfigProvider: ConfigProvider {
var anrTakeScreenshot: Bool { dynamicConfig.anrTakeScreenshot }
var launchSamplingRate: Float { dynamicConfig.launchSamplingRate }
var gestureClickTakeSnapshot: Bool { dynamicConfig.gestureClickTakeSnapshot }
var httpSamplingRate: Float { dynamicConfig.httpSamplingRate }
var httpDisableEventForUrls: [String] { dynamicConfig.httpDisableEventForUrls }
var httpTrackRequestForUrls: [String] { dynamicConfig.httpTrackRequestForUrls }
var httpTrackResponseForUrls: [String] { dynamicConfig.httpTrackResponseForUrls }
Expand Down
26 changes: 26 additions & 0 deletions ios/Sources/MeasureSDK/Swift/Config/DefaultConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,30 @@ struct DefaultConfig {
static let journeyEvents: [EventType] = [.lifecycleSwiftUI,
.lifecycleViewController,
.screenView]

static let maxEventsInBatch: Number = 10_000
static let crashTimelineDurationSeconds: Number = 300
static let anrTimelineDurationSeconds: Number = 300
static let bugReportTimelineDurationSeconds: Number = 300
static let traceSamplingRate: Float = 0.01
static let journeySamplingRate: Float = 0.01
static let screenshotMaskLevel: ScreenshotMaskLevel = .allTextAndMedia
static let cpuUsageInterval: Number = 5
static let memoryUsageInterval: Number = 5
static let crashTakeScreenshot: Bool = true
static let anrTakeScreenshot: Bool = true
static let launchSamplingRate: Float = 0.01
static let gestureClickTakeSnapshot: Bool = true
static let httpSamplingRate: Float = 0.01
static let httpDisableEventForUrls: [String] = []
static let httpTrackRequestForUrls: [String] = []
static let httpTrackResponseForUrls: [String] = []
static let httpBlockedHeaders: [String] = [
"Authorization",
"Cookie",
"Set-Cookie",
"Proxy-Authorization",
"WWW-Authenticate",
"X-Api-Key",
]
}
93 changes: 67 additions & 26 deletions ios/Sources/MeasureSDK/Swift/Config/DynamicConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ protocol DynamicConfig {
/// Defaults to true.
var gestureClickTakeSnapshot: Bool { get }

/// Sampling rate for htto events.
/// Defaults to 0.01%.
var httpSamplingRate: Float { get }

/// URLs for which HTTP events should be disabled.
var httpDisableEventForUrls: [String] { get }

Expand Down Expand Up @@ -86,36 +90,72 @@ struct BaseDynamicConfig: DynamicConfig, Codable {
let anrTakeScreenshot: Bool
let launchSamplingRate: Float
let gestureClickTakeSnapshot: Bool
let httpSamplingRate: Float
let httpDisableEventForUrls: [String]
let httpTrackRequestForUrls: [String]
let httpTrackResponseForUrls: [String]
let httpBlockedHeaders: [String]

static func `default`() -> BaseDynamicConfig {
BaseDynamicConfig(maxEventsInBatch: 10_000,
crashTimelineDurationSeconds: 300,
anrTimelineDurationSeconds: 300,
bugReportTimelineDurationSeconds: 300,
traceSamplingRate: 0.01,
journeySamplingRate: 0.01,
screenshotMaskLevel: .allTextAndMedia,
cpuUsageInterval: 5,
memoryUsageInterval: 5,
crashTakeScreenshot: true,
anrTakeScreenshot: true,
launchSamplingRate: 0.01,
gestureClickTakeSnapshot: true,
httpDisableEventForUrls: [],
httpTrackRequestForUrls: [],
httpTrackResponseForUrls: [],
httpBlockedHeaders: [
"Authorization",
"Cookie",
"Set-Cookie",
"Proxy-Authorization",
"WWW-Authenticate",
"X-Api-Key",
])

init(maxEventsInBatch: Number = DefaultConfig.maxEventsInBatch,
crashTimelineDurationSeconds: Number = DefaultConfig.crashTimelineDurationSeconds,
anrTimelineDurationSeconds: Number = DefaultConfig.anrTimelineDurationSeconds,
bugReportTimelineDurationSeconds: Number = DefaultConfig.bugReportTimelineDurationSeconds,
traceSamplingRate: Float = DefaultConfig.traceSamplingRate,
journeySamplingRate: Float = DefaultConfig.journeySamplingRate,
screenshotMaskLevel: ScreenshotMaskLevel = DefaultConfig.screenshotMaskLevel,
cpuUsageInterval: Number = DefaultConfig.cpuUsageInterval,
memoryUsageInterval: Number = DefaultConfig.memoryUsageInterval,
crashTakeScreenshot: Bool = DefaultConfig.crashTakeScreenshot,
anrTakeScreenshot: Bool = DefaultConfig.anrTakeScreenshot,
launchSamplingRate: Float = DefaultConfig.launchSamplingRate,
gestureClickTakeSnapshot: Bool = DefaultConfig.gestureClickTakeSnapshot,
httpSamplingRate: Float = DefaultConfig.httpSamplingRate,
httpDisableEventForUrls: [String] = DefaultConfig.httpDisableEventForUrls,
httpTrackRequestForUrls: [String] = DefaultConfig.httpTrackRequestForUrls,
httpTrackResponseForUrls: [String] = DefaultConfig.httpTrackResponseForUrls,
httpBlockedHeaders: [String] = DefaultConfig.httpBlockedHeaders
) {
self.maxEventsInBatch = maxEventsInBatch
self.crashTimelineDurationSeconds = crashTimelineDurationSeconds
self.anrTimelineDurationSeconds = anrTimelineDurationSeconds
self.bugReportTimelineDurationSeconds = bugReportTimelineDurationSeconds
self.traceSamplingRate = traceSamplingRate
self.journeySamplingRate = journeySamplingRate
self.screenshotMaskLevel = screenshotMaskLevel
self.cpuUsageInterval = cpuUsageInterval
self.memoryUsageInterval = memoryUsageInterval
self.crashTakeScreenshot = crashTakeScreenshot
self.anrTakeScreenshot = anrTakeScreenshot
self.launchSamplingRate = launchSamplingRate
self.gestureClickTakeSnapshot = gestureClickTakeSnapshot
self.httpSamplingRate = httpSamplingRate
self.httpDisableEventForUrls = httpDisableEventForUrls
self.httpTrackRequestForUrls = httpTrackRequestForUrls
self.httpTrackResponseForUrls = httpTrackResponseForUrls
self.httpBlockedHeaders = httpBlockedHeaders
}

init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)

maxEventsInBatch = try c.decodeIfPresent(Number.self, forKey: .maxEventsInBatch) ?? DefaultConfig.maxEventsInBatch
crashTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .crashTimelineDurationSeconds) ?? DefaultConfig.crashTimelineDurationSeconds
anrTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .anrTimelineDurationSeconds) ?? DefaultConfig.anrTimelineDurationSeconds
bugReportTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .bugReportTimelineDurationSeconds) ?? DefaultConfig.bugReportTimelineDurationSeconds
traceSamplingRate = try c.decodeIfPresent(Float.self, forKey: .traceSamplingRate) ?? DefaultConfig.traceSamplingRate
journeySamplingRate = try c.decodeIfPresent(Float.self, forKey: .journeySamplingRate) ?? DefaultConfig.journeySamplingRate
screenshotMaskLevel = try c.decodeIfPresent(ScreenshotMaskLevel.self, forKey: .screenshotMaskLevel) ?? DefaultConfig.screenshotMaskLevel
cpuUsageInterval = try c.decodeIfPresent(Number.self, forKey: .cpuUsageInterval) ?? DefaultConfig.cpuUsageInterval
memoryUsageInterval = try c.decodeIfPresent(Number.self, forKey: .memoryUsageInterval) ?? DefaultConfig.memoryUsageInterval
crashTakeScreenshot = try c.decodeIfPresent(Bool.self, forKey: .crashTakeScreenshot) ?? DefaultConfig.crashTakeScreenshot
anrTakeScreenshot = try c.decodeIfPresent(Bool.self, forKey: .anrTakeScreenshot) ?? DefaultConfig.anrTakeScreenshot
launchSamplingRate = try c.decodeIfPresent(Float.self, forKey: .launchSamplingRate) ?? DefaultConfig.launchSamplingRate
gestureClickTakeSnapshot = try c.decodeIfPresent(Bool.self, forKey: .gestureClickTakeSnapshot) ?? DefaultConfig.gestureClickTakeSnapshot
httpSamplingRate = try c.decodeIfPresent(Float.self, forKey: .httpSamplingRate) ?? DefaultConfig.httpSamplingRate
httpDisableEventForUrls = try c.decodeIfPresent([String].self, forKey: .httpDisableEventForUrls) ?? DefaultConfig.httpDisableEventForUrls
httpTrackRequestForUrls = try c.decodeIfPresent([String].self, forKey: .httpTrackRequestForUrls) ?? DefaultConfig.httpTrackRequestForUrls
httpTrackResponseForUrls = try c.decodeIfPresent([String].self, forKey: .httpTrackResponseForUrls) ?? DefaultConfig.httpTrackResponseForUrls
httpBlockedHeaders = try c.decodeIfPresent([String].self, forKey: .httpBlockedHeaders) ?? DefaultConfig.httpBlockedHeaders
}

private enum CodingKeys: String, CodingKey {
Expand All @@ -132,6 +172,7 @@ struct BaseDynamicConfig: DynamicConfig, Codable {
case anrTakeScreenshot = "anr_take_screenshot"
case launchSamplingRate = "launch_sampling_rate"
case gestureClickTakeSnapshot = "gesture_click_take_snapshot"
case httpSamplingRate = "http_sampling_rate"
case httpDisableEventForUrls = "http_disable_event_for_urls"
case httpTrackRequestForUrls = "http_track_request_for_urls"
case httpTrackResponseForUrls = "http_track_response_for_urls"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ final class BaseInternalSignalCollector: InternalSignalCollector {
attachments: nil,
userDefinedAttributes: serializedUserDefinedAttributes,
threadName: threadName,
needsReporting: false
needsReporting: signalSampler.shouldSampleHttpEvent()
)

case EventType.bugReport.rawValue:
Expand Down
9 changes: 9 additions & 0 deletions ios/Sources/MeasureSDK/Swift/Events/SignalSampler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ protocol SignalSampler {
func shouldTrackLaunchEvents() -> Bool
func shouldSampleTrace(_ traceId: String) -> Bool
func shouldTrackJourneyForSession(sessionId: String) -> Bool
func shouldSampleHttpEvent() -> Bool
}

final class BaseSignalSampler: SignalSampler {
Expand Down Expand Up @@ -69,6 +70,14 @@ final class BaseSignalSampler: SignalSampler {

return stableSamplingValue(sessionId: sessionId) < samplingRate
}

func shouldSampleHttpEvent() -> Bool {
if configProvider.enableFullCollectionMode {
return true
}
return shouldTrack(configProvider.httpSamplingRate / 100)
}


/// Generates a stable sampling value in [0, 1] from a session ID.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ final class BaseUserTriggeredEventCollector: UserTriggeredEventCollector {
responseBody: shouldTrackResponseHttpBody ? responseBody : nil,
client: client)

track(data, type: .http, needsReporting: false)
track(data, type: .http, needsReporting: signalSampler.shouldSampleHttpEvent())
}

private func track(_ data: Codable, type: EventType, userDefinedAttributes: String? = nil, needsReporting: Bool?) {
Expand All @@ -190,7 +190,7 @@ final class BaseUserTriggeredEventCollector: UserTriggeredEventCollector {
attachments: nil,
userDefinedAttributes: userDefinedAttributes,
threadName: nil,
needsReporting: needsReporting)
needsReporting: signalSampler.shouldSampleHttpEvent())
}

private func getContentType(headers: [String: String]?) -> String? {
Expand Down
4 changes: 4 additions & 0 deletions ios/Sources/MeasureSDK/Swift/Http/HttpEventCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
private let httpInterceptorCallbacks: HttpInterceptorCallbacks
private let client: Client
private let configProvider: ConfigProvider
private let signalSampler: SignalSampler
private let httpEventValidator: HttpEventValidator
private var isEnabled = AtomicBool(false)

Expand All @@ -30,6 +31,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
httpInterceptorCallbacks: HttpInterceptorCallbacks,
client: Client,
configProvider: ConfigProvider,
signalSampler: SignalSampler,
httpEventValidator: HttpEventValidator) {
self.logger = logger
self.signalProcessor = signalProcessor
Expand All @@ -38,6 +40,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
self.httpInterceptorCallbacks = httpInterceptorCallbacks
self.client = client
self.configProvider = configProvider
self.signalSampler = signalSampler
self.httpEventValidator = httpEventValidator
self.httpInterceptorCallbacks.httpDataCallback = onHttpCompletion(data:)
}
Expand All @@ -51,6 +54,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
URLSessionTaskInterceptor.shared.setHttpInterceptorCallbacks(httpInterceptorCallbacks)
URLSessionTaskInterceptor.shared.setTimeProvider(timeProvider)
URLSessionTaskInterceptor.shared.setConfigProvider(configProvider)
URLSessionTaskInterceptor.shared.setSignalSampler(signalSampler)
logger.log(level: .info, message: "HttpEventCollector enabled.", error: nil, data: nil)
}
}
Expand Down
10 changes: 10 additions & 0 deletions ios/Sources/MeasureSDK/Swift/Http/URLSessionTaskInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class URLSessionTaskInterceptor {
private var configProvider: ConfigProvider?
private var recentRequests: [String: UInt64] = [:]
private let dedupeWindowMs: UInt64 = 300
private var signalSampler: SignalSampler?

private init() {}

Expand All @@ -29,18 +30,27 @@ final class URLSessionTaskInterceptor {
func setConfigProvider(_ configProvider: ConfigProvider) {
self.configProvider = configProvider
}

func setSignalSampler(_ signalSampler: SignalSampler) {
self.signalSampler = signalSampler
}

func urlSessionTask(_ task: URLSessionTask, setState state: URLSessionTask.State) { // swiftlint:disable:this function_body_length
guard !MSRNetworkInterceptor.isEnabled else { return }

guard let httpInterceptorCallbacks = self.httpInterceptorCallbacks,
let timeProvider = self.timeProvider,
let configProvider = self.configProvider,
let signalSampler = self.signalSampler,
let url = task.currentRequest?.url?.absoluteString else { return }

guard configProvider.shouldTrackHttpUrl(url: url) else {
return
}

guard signalSampler.shouldTrackLaunchEvents() else {
return
}

if state == .running, taskStartTimes[task] == nil {
taskStartTimes[task] = UnsignedNumber(timeProvider.millisTime)
Expand Down
1 change: 1 addition & 0 deletions ios/Sources/MeasureSDK/Swift/MeasureInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ final class BaseMeasureInitializer: MeasureInitializer {
httpInterceptorCallbacks: HttpInterceptorCallbacks(),
client: client,
configProvider: configProvider,
signalSampler: signalSampler,
httpEventValidator: httpEventValidator)
self.internalSignalCollector = BaseInternalSignalCollector(logger: logger,
timeProvider: timeProvider,
Expand Down
Loading
Loading