diff --git a/Datadog/IntegrationUnitTests/AppRunner/AppRunStep+Fixtures.swift b/Datadog/IntegrationUnitTests/AppRunner/AppRunStep+Fixtures.swift index ca4fdb1e38..19fe7cb4f0 100644 --- a/Datadog/IntegrationUnitTests/AppRunner/AppRunStep+Fixtures.swift +++ b/Datadog/IntegrationUnitTests/AppRunner/AppRunStep+Fixtures.swift @@ -56,7 +56,6 @@ extension AppRunStep { app.initializeSDK(sdkSetup ?? { _ in }) app.enableRUM { rumConfig in rumSetup?(&rumConfig) - rumConfig.sessionEndedSampleRate = 0 // TODO: RUM-9335 Enable "Session Ended" telemetry after fixing `application.id` value for session stop rumConfig.telemetrySampleRate = 0 } }) diff --git a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift index bb8a290681..ee5ce36f16 100644 --- a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift @@ -46,7 +46,9 @@ class RUMSessionEndedMetricIntegrationTests: XCTestCase { monitor.stopSession() // Then - let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + let event = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()) + XCTAssertEqual(event.application?.id, rumConfig.applicationID, "It must report `application.id` even though the session no longer exists") + let metricAttributes = try XCTUnwrap(event.attributes) XCTAssertTrue(metricAttributes.wasStopped) } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index fa3cd95614..ac0841dafc 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -273,6 +273,7 @@ internal final class RUMFeature: DatadogRemoteFeature, RUMSessionSamplerProvider TelemetryInterceptor(sessionEndedMetric: sessionEndedMetric), TelemetryReceiver( featureScope: featureScope, + applicationID: configuration.applicationID, dateProvider: configuration.dateProvider, sampler: Sampler(samplingRate: configuration.telemetrySampleRate), configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate) diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index a89e85b833..b44bd4ce52 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -18,6 +18,9 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// RUM feature scope. let featureScope: FeatureScope + /// The RUM application ID. It is known upfront, so telemetry can be attributed to the application + /// even when no RUM session exists (e.g. after the session expired or was stopped). + let applicationID: String let dateProvider: DateProvider /// Sampler for all telemetry events. @@ -41,16 +44,19 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// /// - Parameters: /// - featureScope: RUM feature scope. + /// - applicationID: The RUM application ID. /// - dateProvider: Current device time provider. /// - sampler: Telemetry events sampler. /// - configurationExtraSampler: Extra sampler for configuration events (applied on top of `sampler`). init( featureScope: FeatureScope, + applicationID: String, dateProvider: DateProvider, sampler: Sampler, configurationExtraSampler: Sampler ) { self.featureScope = featureScope + self.applicationID = applicationID self.dateProvider = dateProvider self.sampler = sampler self.configurationExtraSampler = configurationExtraSampler @@ -285,7 +291,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { let event = TelemetryDebugEvent( dd: .init(), action: rum?.userActionID.map { .init(id: .string(value: $0)) }, - application: rum.map { .init(id: $0.applicationID) }, + application: .init(id: self.applicationID), date: date.addingTimeInterval(context.serverTimeOffset).timeIntervalSince1970.dd.toInt64Milliseconds, effectiveSampleRate: Double(effectiveSampleRate), experimentalFeatures: nil, diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 0dbdacd5a5..e230d796d1 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -498,7 +498,7 @@ class TelemetryReceiverTests: XCTestCase { let osMock: OperatingSystem = .mockRandom() featureScope.contextMock = .mockWith(device: deviceMock, os: osMock) featureScope.contextMock.set(additionalContext: rumContext) - let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, applicationID: rumContext.applicationID) // When TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: mockRandomAttributes(), sampleRate: 100) @@ -524,7 +524,7 @@ class TelemetryReceiverTests: XCTestCase { // Given let rumContext: RUMCoreContext = .mockRandom() featureScope.contextMock.set(additionalContext: rumContext) - let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, applicationID: rumContext.applicationID) let sessionIDOverride = "session-id-override" // When @@ -541,6 +541,20 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertNil(event?.telemetry.telemetryInfo[SDKMetricFields.sessionIDOverrideKey], "It should delete `sessionIDOverrideKey` from metric attributes") } + func testSendTelemetryMetricWithNoRUMContext() { + // Given + let applicationID: String = .mockRandom() + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope, applicationID: applicationID) + + // When: no RUM context exists, e.g. the session expired or was stopped + TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: mockRandomAttributes(), sampleRate: 100) + + // Then + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first + XCTAssertEqual(event?.application?.id, applicationID, "It should attribute the metric to the RUM application even with no session") + XCTAssertNil(event?.session?.id, "It should not report a session ID when no session exists") + } + func testMethodCallTelemetryPropagatesAllData() throws { // Given let deviceMock: DeviceInfo = .mockRandom() diff --git a/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift b/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift index 4f81c21b91..b63761a4c4 100644 --- a/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift +++ b/TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift @@ -1594,12 +1594,14 @@ extension TelemetryReceiver: AnyMockable { public static func mockWith( featureScope: FeatureScope = NOPFeatureScope(), + applicationID: String = .mockAny(), dateProvider: DateProvider = SystemDateProvider(), sampler: Sampler = .mockKeepAll(), configurationExtraSampler: Sampler = .mockKeepAll() ) -> Self { .init( featureScope: featureScope, + applicationID: applicationID, dateProvider: dateProvider, sampler: sampler, configurationExtraSampler: configurationExtraSampler