Skip to content

DEMRUM-2090: Implement UserConfiguration and SessionConfiguration #316

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

Open
wants to merge 3 commits into
base: feature/next-gen
Choose a base branch
from
Open
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: 2 additions & 2 deletions Applications/DevelApp/DevelApp/DevelAppApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ struct DevelAppApp: App {
)
.enableDebugLogging(true)
// Sampled-out agent
//.sessionSamplingRate(0)
.sessionSamplingRate(1)
//.sessionConfiguration(SessionConfiguration(samplingRate: 0))
.sessionConfiguration(SessionConfiguration(samplingRate: 1))

let agent = try! SplunkRum.install(with: agentConfig)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ protocol AgentConfigurationProtocol: Codable, Equatable {

var appVersion: String { get set }
var enableDebugLogging: Bool { get set }
var sessionSamplingRate: Double { get set }
var globalAttributes: MutableAttributes { get set }
var spanInterceptor: ((SpanData) -> SpanData?)? { get set }

var user: UserConfiguration { get set }
var session: SessionConfiguration { get set }


// MARK: - Remote configuration parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension SplunkRum {
var settings = [String: String]()

settings["enableDebugLogging"] = String(agentConfigurationHandler.configuration.enableDebugLogging)
settings["sessionSamplingRate"] = String(agentConfigurationHandler.configuration.sessionSamplingRate)
settings["sessionSamplingRate"] = String(agentConfigurationHandler.configuration.session.samplingRate)

if let modulesConfigurations = modulesManager?.modulesConfigurationDescription {
settings.merge(modulesConfigurations) { $1 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ final class DefaultAgentSessionSampler: AgentSessionSampler {
/// - Parameter configuration: An object conforming to `AgentConfigurationProtocol` that
/// supplies the `sessionSamplingRate`.
func configure(with configuration: any AgentConfigurationProtocol) {
probability = configuration.sessionSamplingRate
probability = configuration.session.samplingRate
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ public struct AgentConfiguration: AgentConfigurationProtocol, Codable, Equatable
/// Defaults to `false`.
public var enableDebugLogging: Bool = ConfigurationDefaults.enableDebugLogging

/// Sets the sampling rate with at which sessions will be sampled. The sampling rate is from the `<0.0, 1.0>` interval.
///
/// `1.0` equals to zero sampling (all instrumentation is sent), `0.0` equals to all session being sampled, `0.5` equals to 50% sampling. Defaults to `1.0`.
public var sessionSamplingRate: Double = ConfigurationDefaults.sessionSamplingRate

/// Sets global attributes, which are sent with all signals.
///
/// Defaults to an empty MutableAttributes object.
Expand All @@ -69,6 +64,12 @@ public struct AgentConfiguration: AgentConfigurationProtocol, Codable, Equatable
/// or discarded by returning `nil` in the callback. Spans can also be modified by the callback.
public var spanInterceptor: ((SpanData) -> SpanData?)?

/// Sets the `UserConfiguration` object.
public var user: UserConfiguration = UserConfiguration()

/// Sets the `SessionConfiguration` object.
public var session: SessionConfiguration = SessionConfiguration()


// MARK: - Private

Expand Down Expand Up @@ -125,16 +126,28 @@ public struct AgentConfiguration: AgentConfigurationProtocol, Codable, Equatable
return updated
}

/// Sets the sampling rate with which sessions will be sampled.
/// Sets the `UserConfiguration` object.
///
/// - Parameter userConfiguration: A configuration object representing properties of the Agent's `User`.
///
/// - Returns: The updated configuration structure.
@discardableResult
public func userConfiguration(_ userConfiguration: UserConfiguration) -> Self {
var updated = self
updated.user = userConfiguration

return updated
}

/// Sets the `SessionConfiguration` object.
///
/// - Parameter sessionSamplingRate: A sampling rate in the `<0.0, 1.0>` interval.
/// `1.0` equals to zero sampling (all instrumentation is sent), `0.0` equals to all session being sampled, `0.5` equals to 50% sampling.
/// - Parameter sessionConfiguration: A configuration object representing properties of the Agent's `Session`.
///
/// - Returns: The updated configuration structure.
@discardableResult
public func sessionSamplingRate(_ sessionSamplingRate: Double) -> Self {
public func sessionConfiguration(_ sessionConfiguration: SessionConfiguration) -> Self {
var updated = self
updated.sessionSamplingRate = sessionSamplingRate
updated.session = sessionConfiguration

return updated
}
Expand Down Expand Up @@ -180,8 +193,11 @@ public struct AgentConfiguration: AgentConfigurationProtocol, Codable, Equatable
// Optional public properties except span filter
case appVersion
case enableDebugLogging
case sessionSamplingRate
case globalAttributes

// Optional public configuration objects
case user
case session
}


Expand All @@ -195,8 +211,9 @@ public struct AgentConfiguration: AgentConfigurationProtocol, Codable, Equatable

lhs.appVersion == rhs.appVersion &&
lhs.enableDebugLogging == rhs.enableDebugLogging &&
lhs.sessionSamplingRate == rhs.sessionSamplingRate &&
lhs.globalAttributes == rhs.globalAttributes
lhs.globalAttributes == rhs.globalAttributes &&
lhs.user == rhs.user &&
lhs.session == rhs.session
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,4 @@ public extension RuntimeState {
var isDebugLoggingEnabled: Bool {
owner.agentConfiguration.enableDebugLogging
}

/// A `Double` containing the used sampling rate.
var sessionSamplingRate: Double {
owner.agentConfiguration.sessionSamplingRate
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ public extension SessionState {

/// Value of the currently used session sampling rate.
var samplingRate: Double {
owner.agentConfiguration.sessionSamplingRate
owner.agentConfiguration.session.samplingRate
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
/*
Copyright 2025 Splunk Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/// A configuration object representing properties of the Agent's `Session`.
public struct SessionConfiguration: Codable, Equatable {

// MARK: - Public properties

/// A sampling rate in the `<0.0, 1.0>` interval.
/// `1.0` equals to zero sampling (all instrumentation is sent), `0.0` equals to all session being sampled, `0.5` equals to 50% sampling.
///
/// Defaults to `1.0`.
public var samplingRate = ConfigurationDefaults.sessionSamplingRate


// MARK: - Initialization

/// Default empty constructor.
///
/// Initializes the configuration object's properties with default values.
public init() {}

/// Initializes the configuration object.
///
/// - Parameters:
/// - samplingRate: A sampling rate in the `<0.0, 1.0>` interval.
/// `1.0` equals to zero sampling (all instrumentation is sent),
/// `0.0` equals to all session being sampled, `0.5` equals to 50% sampling.
public init(samplingRate: Double) {
self.samplingRate = samplingRate
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
/*
Copyright 2025 Splunk Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/// A configuration object representing properties of the Agent's `User`.
public struct UserConfiguration: Codable, Equatable {

// MARK: - Public properties

/// Sets the preferred `UserTrackingMode`.
///
/// Defaults to `.noTracking`
public var trackingMode: UserTrackingMode = .default


// MARK: - Initialization

/// Default empty constructor.
///
/// Initializes the configuration object's properties with default values.
public init() {}

/// Initializes the configuration object.
///
/// - Parameters:
/// - trackingMode: The preferred `UserTrackingMode`.
public init(trackingMode: UserTrackingMode) {
self.trackingMode = trackingMode
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ limitations under the License.
/// A mode of user tracking.
///
/// Determines whether and, if necessary, how the user will be tracked.
public enum UserTrackingMode: Equatable {
public enum UserTrackingMode: Codable, Equatable {

/// No tracking. Individual user sessions are not linked in any way.
///
Expand Down
3 changes: 3 additions & 0 deletions SplunkAgent/Sources/SplunkAgent/Public API/SplunkRum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ public class SplunkRum: ObservableObject {
sessionSampler: DefaultAgentSessionSampler()
)

// Set the configured user tracking mode
user.preferences.trackingMode = configuration.user.trackingMode

initializeEvents["agent_instance_initialized"] = Date()

// Links the current session with the agent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ final class DefaultAgentSessionSamplerTests: XCTestCase {
let newSamplingRate = 0.5

var configuration = try ConfigurationTestBuilder.buildDefault()
configuration.sessionSamplingRate = newSamplingRate
configuration.session.samplingRate = newSamplingRate

sampler.configure(with: configuration)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,5 @@ final class StateTests: XCTestCase {

let debugEnabled = state.isDebugLoggingEnabled
XCTAssertEqual(debugEnabled, ConfigurationDefaults.enableDebugLogging)

let sampling = state.sessionSamplingRate
XCTAssertEqual(sampling, ConfigurationDefaults.sessionSamplingRate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ final class API10ConfigurationTests: XCTestCase {
XCTAssertEqual(full.appName, appName)
XCTAssertEqual(full.appVersion, appVersion)
XCTAssertNotNil(full.enableDebugLogging)
XCTAssertNotNil(full.sessionSamplingRate)
XCTAssertNotNil(full.session.samplingRate)
XCTAssertNotNil(full.user.trackingMode)
XCTAssertNotNil(full.globalAttributes)
XCTAssertNotNil(full.spanInterceptor)
XCTAssertNotNil(full.endpoint.traceEndpoint)
Expand All @@ -64,7 +65,7 @@ final class API10ConfigurationTests: XCTestCase {
XCTAssertEqual(minimal.appName, appName)
XCTAssertNotNil(minimal.appVersion)
XCTAssertEqual(minimal.enableDebugLogging, ConfigurationDefaults.enableDebugLogging)
XCTAssertEqual(minimal.sessionSamplingRate, ConfigurationDefaults.sessionSamplingRate)
XCTAssertEqual(minimal.session.samplingRate, ConfigurationDefaults.sessionSamplingRate)

// Properties (WRITE)
full.appVersion = "0.1"
Expand All @@ -79,11 +80,24 @@ final class API10ConfigurationTests: XCTestCase {
full = full.enableDebugLogging(false)
XCTAssertEqual(full.enableDebugLogging, false)

full.sessionSamplingRate = 0.7
XCTAssertEqual(full.sessionSamplingRate, 0.7)
// Session configuration
full.session.samplingRate = 0.7
XCTAssertEqual(full.session.samplingRate, 0.7)

full = full.sessionSamplingRate(0.5)
XCTAssertEqual(full.sessionSamplingRate, 0.5)
var sessionConfiguration = SessionConfiguration()
sessionConfiguration.samplingRate = 0.5

full = full.sessionConfiguration(sessionConfiguration)
XCTAssertEqual(full.session.samplingRate, 0.5)

// User configuration
full.user.trackingMode = .noTracking
XCTAssertEqual(full.user.trackingMode, .noTracking)

var userConfiguration = UserConfiguration()
userConfiguration.trackingMode = .anonymousTracking
full = full.userConfiguration(userConfiguration)
XCTAssertEqual(full.user.trackingMode, .anonymousTracking)

let testAttributes = MutableAttributes(dictionary: ["key_one": .string("value_one")])
full.globalAttributes = testAttributes
Expand Down Expand Up @@ -143,12 +157,20 @@ final class API10ConfigurationTests: XCTestCase {
let debugLogging = true
let sampling = 0.4
let globalAttributes = MutableAttributes(dictionary: ["test": .string("value")])
let userTrackingMode: UserTrackingMode = .anonymousTracking

var sessionConfiguration = SessionConfiguration()
sessionConfiguration.samplingRate = 0.4

var userConfiguration = UserConfiguration()
userConfiguration.trackingMode = .anonymousTracking

// Builder methods
let configuration = try ConfigurationTestBuilder.buildMinimal()
.appVersion(appVersion)
.enableDebugLogging(debugLogging)
.sessionSamplingRate(sampling)
.sessionConfiguration(sessionConfiguration)
.userConfiguration(userConfiguration)
.globalAttributes(globalAttributes)
.spanInterceptor { spanData in
spanData
Expand All @@ -157,7 +179,8 @@ final class API10ConfigurationTests: XCTestCase {
// Check if the data has been written
XCTAssertEqual(configuration.appVersion, appVersion)
XCTAssertEqual(configuration.enableDebugLogging, debugLogging)
XCTAssertEqual(configuration.sessionSamplingRate, sampling)
XCTAssertEqual(configuration.session.samplingRate, sampling)
XCTAssertEqual(configuration.user.trackingMode, userTrackingMode)
XCTAssertEqual(configuration.globalAttributes, globalAttributes)
XCTAssertNotNil(configuration.spanInterceptor)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,5 @@ final class API10StateTests: XCTestCase {
XCTAssertNotNil(state.endpointConfiguration.realm)
XCTAssertNotNil(state.deploymentEnvironment)
XCTAssertNotNil(state.isDebugLoggingEnabled)
XCTAssertNotNil(state.sessionSamplingRate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ final class ConfigurationTestBuilder {
deploymentEnvironment: deploymentEnvironment
)

var sessionConfiguration = SessionConfiguration()
sessionConfiguration.samplingRate = 1.0

configuration.appVersion = appVersion
configuration.enableDebugLogging = true
configuration.sessionSamplingRate = 1
configuration.session = sessionConfiguration
configuration.globalAttributes = MutableAttributes(dictionary: ["attribute": .string("value")])
configuration.spanInterceptor = { spanData in
spanData
Expand All @@ -75,9 +78,12 @@ final class ConfigurationTestBuilder {
deploymentEnvironment: deploymentEnvironment
)

var sessionConfiguration = SessionConfiguration()
sessionConfiguration.samplingRate = 0.0

configuration.appVersion = appVersion
configuration.enableDebugLogging = true
configuration.sessionSamplingRate = 0
configuration.session = sessionConfiguration
configuration.globalAttributes = MutableAttributes(dictionary: ["attribute": .string("value")])
configuration.spanInterceptor = { spanData in
spanData
Expand Down