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

RUMM-1765 Add Application Launch events tracking - all PRs combined #699

Merged
merged 33 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
67be79e
RUMM-1765 Make starting "Background" view based on the command's prop…
ncreated Nov 29, 2021
e245f68
RUMM-1765 Mark the very first RUM session in the process as "initial"
ncreated Nov 29, 2021
93b992a
RUMM-1765 Handle application launch events in `RUMSessionScope`
ncreated Nov 30, 2021
0928f3b
RUMM-1765 Start RUM session with the first tracked event
ncreated Nov 30, 2021
972b42b
RUMM-1765 Improve RUM command mocks
ncreated Dec 1, 2021
373a915
RUMM-1765 Test app launch events tracking in `RUMMonitorTests`
ncreated Dec 1, 2021
0e9cfef
RUMM-1765 Add debug utility for testing background events tracking
ncreated Dec 6, 2021
db4ba70
RUMM-1765 CR fix - address comments
ncreated Dec 7, 2021
795e402
RUMM-1765 Add tests for expected App Launch x Background Events Track…
ncreated Dec 10, 2021
b1609bb
RUMM-1765 Adjust naming in `AppStateHistory` to better fit the new us…
ncreated Dec 10, 2021
d3646f7
RUMM-1765 Inject `AppStateListener` to RUM scopes
ncreated Dec 10, 2021
4c9338b
RUMM-1765 Make App Launch x Background Events Tracking tests green
ncreated Dec 10, 2021
591708a
RUMM-1765 Fix `Benchmarks` build
ncreated Dec 10, 2021
a92066e
RUMM-1765 Fix a bug with custom actions not being sent if tracked as …
ncreated Dec 13, 2021
44a15a8
RUMM-1765 Inject SDK init time to RUM application scope
ncreated Dec 13, 2021
9cc6cd4
RUMM-1765 Start 'ApplicationLaunch' view using the time of SDK init
ncreated Dec 13, 2021
64bcce5
RUMM-1765 Fix `Benchmarks` build
ncreated Dec 13, 2021
87f9639
RUMM-1765 Update `CrashContext` with RUM session info and application…
ncreated Dec 14, 2021
c14df6a
RUMM-1765 Add separate unit tests for `RUMOffViewEventsHandlingRule`
ncreated Dec 16, 2021
5f03f4e
RUMM-1765 Implement separate `Core` component for sampling
ncreated Dec 14, 2021
3922189
RUMM-1765 Inject `Sampler` to RUM
ncreated Dec 14, 2021
b43a12e
RUMM-1765 Send application launch crashes in new RUM session
ncreated Dec 17, 2021
83f75a9
Merge pull request #685 from DataDog/ncreated/RUMM-1765-do-not-start-…
ncreated Dec 20, 2021
bb4732b
Merge pull request #690 from DataDog/ncreated/RUMM-1765-collect-app-l…
ncreated Dec 21, 2021
36a5197
RUMM-1765 Handle background crashes which happened in the previous se…
ncreated Dec 21, 2021
332620f
RUMM-1765 Add features for debugging background crashes in Example app
ncreated Dec 17, 2021
7a6548a
RUMM-1765 Update `AppStateListener` to provide more accurate informat…
ncreated Dec 20, 2021
0813c48
RUMM-1765 Apply CR feedback from #685
ncreated Dec 20, 2021
a7b0242
RUMM-1765 CR feedback - fix number of iterations in `AppStateListener…
ncreated Dec 22, 2021
4910a31
Merge pull request #695 from DataDog/ncreated/RUMM-1765-send-app-laun…
ncreated Dec 22, 2021
0b4ae5e
Merge pull request #696 from DataDog/ncreated/RUMM-1765-fix-foregroun…
ncreated Dec 22, 2021
b733c29
Merge pull request #697 from DataDog/ncreated/RUMM-1765-CR-feedback
ncreated Dec 22, 2021
d8c0efc
Merge branch 'master' into ncreated/RUMM-1765-app-launch-events-tracking
ncreated Dec 22, 2021
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
36 changes: 34 additions & 2 deletions Datadog/Datadog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Datadog/Example/AppConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct ExampleAppConfiguration: AppConfiguration {
.enableTracing(true)
.enableRUM(true)
.enableCrashReporting(using: DDCrashReportingPlugin())
.trackBackgroundEvents()
}

return configuration.build()
Expand Down
54 changes: 51 additions & 3 deletions Datadog/Example/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="gra-d4-cht">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="gra-d4-cht">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
Expand All @@ -18,7 +18,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<view key="tableFooterView" contentMode="scaleToFill" id="wle-IX-rUl">
<rect key="frame" x="0.0" y="328" width="414" height="0.0"/>
<rect key="frame" x="0.0" y="371.5" width="414" height="0.0"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
Expand Down Expand Up @@ -145,6 +145,26 @@
<segue destination="RlD-sM-dFb" kind="show" id="QXc-jH-vs4"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="QlD-DZ-9oo" style="IBUITableViewCellStyleDefault" id="TPI-rh-103">
<rect key="frame" x="0.0" y="305.5" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TPI-rh-103" id="UxM-nw-ttW">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Background events" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QlD-DZ-9oo">
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="mzn-Uj-AaH" kind="show" id="WAp-KR-rgX"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
Expand Down Expand Up @@ -1614,6 +1634,34 @@
</objects>
<point key="canvasLocation" x="-2039" y="1175"/>
</scene>
<!--Debug Background Events View Controller-->
<scene sceneID="O0O-3T-Hyb">
<objects>
<viewController id="mzn-Uj-AaH" customClass="DebugBackgroundEventsViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="JlK-N8-DqF">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This screen requires iO13+" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pei-Ww-c4Z">
<rect key="frame" x="105.5" y="437.5" width="203" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="hcp-O1-u1G"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="pei-Ww-c4Z" firstAttribute="centerY" secondItem="JlK-N8-DqF" secondAttribute="centerY" id="s4u-Ay-B5z"/>
<constraint firstItem="pei-Ww-c4Z" firstAttribute="centerX" secondItem="JlK-N8-DqF" secondAttribute="centerX" id="zAY-DU-dDH"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="wz7-7i-0Fh"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="juJ-nb-ezT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1419" y="1175"/>
</scene>
</scenes>
<resources>
<systemColor name="secondaryLabelColor">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* 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-2020 Datadog, Inc.
*/

import Foundation
import Datadog
import CoreLocation
import UIKit.UIApplication

internal var backgroundLocationMonitor: BackgroundLocationMonitor?

/// Location monitor used in "Example" app for debugging and testing iOS SDK features in background.
internal class BackgroundLocationMonitor: NSObject, CLLocationManagerDelegate {
private struct Constants {
static let locationMonitoringUserDefaultsKey = "is-location-monitoring-started"
static let crashOnNextBackgroundEventUserDefaultsKey = "crash-on-next-background-event"
static let crashDuringNextBackgroundLaunchUserDefaultsKey = "crash-during-next-background-launch"
}

private let locationManager = CLLocationManager()

/// Tells if location monitoring is started.
/// This setting is preserved between application launches. Defaults to `false`.
///
/// Note: `BackgroundLocationMonitor` can be started independently from receiving location monitoring authorization status.
/// Even if this value is `true`, location updates might not be delivered due to restricted or denied status.
private(set) var isStarted: Bool {
get { UserDefaults.standard.bool(forKey: Constants.locationMonitoringUserDefaultsKey) }
set { UserDefaults.standard.set(newValue, forKey: Constants.locationMonitoringUserDefaultsKey) }
}

/// If enabled, the Example app will crash on receiving next event in background.
/// This setting is preserved between application launches. Defaults to `false` and is reset to `false` shortly before crash.
private(set) var shouldCrashOnNextBackgroundEvent: Bool {
get { UserDefaults.standard.bool(forKey: Constants.crashOnNextBackgroundEventUserDefaultsKey) }
set { UserDefaults.standard.set(newValue, forKey: Constants.crashOnNextBackgroundEventUserDefaultsKey) }
}

/// If enabled, the Example app will crash during next launch in background.
/// This setting is preserved between application launches. Defaults to `false` and is reset to `false` shortly before crash.
private(set) var shouldCrashDuringNextBackgroundLaunch: Bool {
get { UserDefaults.standard.bool(forKey: Constants.crashDuringNextBackgroundLaunchUserDefaultsKey) }
set { UserDefaults.standard.set(newValue, forKey: Constants.crashDuringNextBackgroundLaunchUserDefaultsKey) }
}

private var isAppInBackground: Bool {
return UIApplication.shared.applicationState == .background
}

/// Current authorization status for location monitoring.
var currentAuthorizationStatus: String { authorizationStatusDescription(for: locationManager) }

/// Notifies change of authorization status for location monitoring.
var onAuthorizationStatusChange: ((String) -> Void)? = nil

override init() {
super.init()
if isStarted {
// If location monitoring was enabled in previous app session, here we start it for current session.
// This will keep location tracking when the app is woken up in background due to significant location change.
startMonitoring()
}

if isAppInBackground && shouldCrashDuringNextBackgroundLaunch {
shouldCrashDuringNextBackgroundLaunch = false
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
fatalError("Crash during application launch in background")
}
}
}

func startMonitoring() {
logger.debug("Starting 'BackgroundLocationMonitor' with authorizationStatus: '\(authorizationStatusDescription(for: locationManager))'")

if CLLocationManager.significantLocationChangeMonitoringAvailable() {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true

locationManager.requestAlwaysAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
isStarted = true
} else {
Global.rum.addError(message: "Significant location changes monitoring is not available")
}
}

func stopMonitoring() {
locationManager.stopMonitoringSignificantLocationChanges()
isStarted = false
}

func setCrashOnNextBackgroundEvent(_ enabled: Bool) {
shouldCrashOnNextBackgroundEvent = enabled
}

func setCrashDuringNextBackgroundLaunch(_ enabled: Bool) {
shouldCrashDuringNextBackgroundLaunch = enabled
}

// MARK: - CLLocationManagerDelegate

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = authorizationStatusDescription(for: locationManager)
logger.debug("Changed 'BackgroundLocationMonitor' authorizationStatus: '\(status)'")
onAuthorizationStatusChange?(status)
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let recentLocation = locations.last else {
Global.rum.addError(message: "Received update with no locations")
return
}

logger.debug(
"Location changed at \(recentLocation.timestamp)",
attributes: [
"latitude": recentLocation.coordinate.latitude,
"longitude": recentLocation.coordinate.longitude,
"speed": recentLocation.speed,
]
)

Global.rum.addUserAction(
type: .custom,
name: "Location changed at \(recentLocation.timestamp)",
attributes: [
"latitude": recentLocation.coordinate.latitude,
"longitude": recentLocation.coordinate.longitude,
"speed": recentLocation.speed,
]
)

if isAppInBackground && shouldCrashOnNextBackgroundEvent {
shouldCrashOnNextBackgroundEvent = false
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
fatalError("Crash on receiving event in background")
}
}
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if let error = error as? CLError {
if error.code == .denied {
manager.stopMonitoringSignificantLocationChanges()
}
logger.error("Location manager failed with CLError (error.code: \(error.code)", error: error)
Global.rum.addError(message: "Location manager failed with CLError (error.code: \(error.code)")
} else {
logger.error("Location manager failed", error: error)
Global.rum.addError(error: error)
}
}

// MARK: - Helpers

private func authorizationStatusDescription(for manager: CLLocationManager) -> String {
if #available(iOS 14.0, *) {
switch locationManager.authorizationStatus {
case .authorizedAlways: return "authorizedAlways"
case .notDetermined: return "notDetermined"
case .restricted: return "restricted"
case .denied: return "denied"
case .authorizedWhenInUse: return "authorizedWhenInUse"
@unknown default: return "unrecognized (sth new)"
}
} else {
return "unavailable prior to iOS 14.0"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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-2020 Datadog, Inc.
*/

import SwiftUI
import Datadog

@available(iOS 13, *)
internal class DebugBackgroundEventsViewController: UIHostingController<DebugBackgroundEventsView> {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder, rootView: DebugBackgroundEventsView())
}
}

@available(iOS 13.0, *)
private class DebugBackgroundEventsViewModel: ObservableObject {
private let locationMonitor: BackgroundLocationMonitor

@Published var isLocationMonitoringON = false
@Published var willCrashDuringNextBackgroundLaunch = false
@Published var willCrashOnNextBackgroundEvent = false
@Published var authorizationStatus = ""

init() {
locationMonitor = backgroundLocationMonitor!
isLocationMonitoringON = locationMonitor.isStarted
authorizationStatus = locationMonitor.currentAuthorizationStatus
locationMonitor.onAuthorizationStatusChange = { [weak self] newStatus in
self?.authorizationStatus = newStatus
}
willCrashDuringNextBackgroundLaunch = locationMonitor.shouldCrashDuringNextBackgroundLaunch
willCrashOnNextBackgroundEvent = locationMonitor.shouldCrashOnNextBackgroundEvent
}

func startLocationMonitoring() {
locationMonitor.startMonitoring()
isLocationMonitoringON = locationMonitor.isStarted
}

func stopLocationMonitoring() {
locationMonitor.stopMonitoring()
isLocationMonitoringON = locationMonitor.isStarted
}

func toggleCrashDuringNextBackgroundLaunch() {
locationMonitor.setCrashDuringNextBackgroundLaunch(!locationMonitor.shouldCrashDuringNextBackgroundLaunch)
willCrashDuringNextBackgroundLaunch = locationMonitor.shouldCrashDuringNextBackgroundLaunch
}

func toggleCrashOnNextBackgroundEvent() {
locationMonitor.setCrashOnNextBackgroundEvent(!locationMonitor.shouldCrashOnNextBackgroundEvent)
willCrashOnNextBackgroundEvent = locationMonitor.shouldCrashOnNextBackgroundEvent
}
}

@available(iOS 13.0, *)
internal struct DebugBackgroundEventsView: View {
@ObservedObject private var viewModel = DebugBackgroundEventsViewModel()

var body: some View {
VStack(spacing: 18) {
Text("CLLocationManager")
.font(.headline)
.padding()
Divider()
HStack {
Text("Authorization Status:")
.font(.body).fontWeight(.light)
Spacer()
Text(viewModel.authorizationStatus)
.font(.body)
}
HStack {
Text("Location Monitoring:")
.font(.body).fontWeight(.light)
Spacer()
if #available(iOS 14.0, *) {
if viewModel.isLocationMonitoringON {
ProgressView().padding(.trailing, 8)
}
}
Button(viewModel.isLocationMonitoringON ? "STOP" : "START") {
if viewModel.isLocationMonitoringON {
viewModel.stopLocationMonitoring()
} else {
viewModel.startLocationMonitoring()
}
}
}
Divider()
HStack {
Text("Crash during next background launch:").font(.footnote).fontWeight(.light)
Spacer()
Button(viewModel.willCrashDuringNextBackgroundLaunch ? "🔥 ENABLED" : "DISABLED") {
viewModel.toggleCrashDuringNextBackgroundLaunch()
}
}
HStack {
Text("Crash on next background event:").font(.footnote).fontWeight(.light)
Spacer()
Button(viewModel.willCrashOnNextBackgroundEvent ? "🔥 ENABLED" : "DISABLED") {
viewModel.toggleCrashOnNextBackgroundEvent()
}
}
Divider()
Text("Above settings are preserved between application launches, so they are also effective when app is launched in the background due to **significant** location change.")
.font(.footnote)
Spacer()
}
.buttonStyle(DatadogButtonStyle())
.padding()
}
}

// MARK - Preview

@available(iOS 13.0, *)
internal struct DebugBackgroundEventsView_Previews: PreviewProvider {
static var previews: some View {
Group {
DebugBackgroundEventsView()
.previewLayout(.fixed(width: 400, height: 500))
.preferredColorScheme(.light)
DebugBackgroundEventsView()
.previewLayout(.fixed(width: 400, height: 500))
.preferredColorScheme(.dark)
}
}
}
Loading