Skip to content

Commit

Permalink
Adding state enum
Browse files Browse the repository at this point in the history
Stopping span processor
  • Loading branch information
NachoEmbrace committed Jan 17, 2025
1 parent e08a55e commit 3fd237a
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class DefaultDispatchableQueue: DispatchableQueue {
public func sync(execute block: () -> Void) {
queue.sync(execute: block)
}

public static func with(label: String) -> DispatchableQueue {
DefaultDispatchableQueue(queue: .init(label: label))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// Copyright © 2025 Embrace Mobile, Inc. All rights reserved.
//

public protocol EmbraceSDKStateProvider: AnyObject {
var isEnabled: Bool { get}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public extension InstrumentableViewController {
startTime: startTime,
endTime: endTime,
attributes: attributes
)
)
}

/// Method used to add attributes to the active trace associated with the render process of a `UIViewController`.
Expand Down
18 changes: 6 additions & 12 deletions Sources/EmbraceCore/Capture/UX/View/ViewCaptureService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ private extension ViewCaptureService {
selector: selector,
implementationType: (@convention(c) (UIViewController, Selector) -> Void).self,
blockImplementationType: (@convention(block) (UIViewController) -> Void).self
) { originalImplementation in
{ viewController in
) { originalImplementation in { viewController in
// If the state was already fulfilled, then call the original implementation.
if let state = viewController.emb_instrumentation_state, state.viewDidLoadSpanCreated {
originalImplementation(viewController, selector)
Expand All @@ -133,8 +132,7 @@ private extension ViewCaptureService {
selector: selector,
implementationType: (@convention(c) (UIViewController, Selector, Bool) -> Void).self,
blockImplementationType: (@convention(block) (UIViewController, Bool) -> Void).self
) { originalImplementation in
{ viewController, animated in
) { originalImplementation in { viewController, animated in
// If by this time (`viewWillAppear` being called) there's no `emb_instrumentation_state` associated
// to the viewController, then we don't swizzle as the "instrument render" feature might be disabled.
if let state = viewController.emb_instrumentation_state {
Expand Down Expand Up @@ -173,8 +171,7 @@ private extension ViewCaptureService {
selector: selector,
implementationType: (@convention(c) (UIViewController, Selector, Bool) -> Void).self,
blockImplementationType: (@convention(block) (UIViewController, Bool) -> Void).self
) { originalImplementation in
{ viewController, animated in
) { originalImplementation in { viewController, animated in
// If the state was already fulfilled, then call the original implementation.
if let state = viewController.emb_instrumentation_state, state.viewDidAppearSpanCreated {
originalImplementation(viewController, selector, animated)
Expand Down Expand Up @@ -206,8 +203,7 @@ private extension ViewCaptureService {
selector: selector,
implementationType: (@convention(c) (UIViewController, Selector, Bool) -> Void).self,
blockImplementationType: (@convention(block) (UIViewController, Bool) -> Void).self
) { originalImplementation in
{ viewController, animated in
) { originalImplementation in { viewController, animated in
self.handler.onViewDidDisappear(viewController)
originalImplementation(viewController, selector, animated)
}
Expand All @@ -229,8 +225,7 @@ private extension ViewCaptureService {
blockImplementationType: (
@convention(block) (UIViewController, NSCoder) -> UIViewController?
).self
) { originalImplementation in
{ viewController, coder in
) { originalImplementation in { viewController, coder in
// Get the class and bundle path of the view controller being initialized and check
// if the view controller belongs to the main bundle (this excludes, for eaxmple, UIKit classes)
let viewControllerClass = type(of: viewController)
Expand Down Expand Up @@ -266,8 +261,7 @@ private extension ViewCaptureService {
blockImplementationType: (
@convention(block) (UIViewController, String?, Bundle?) -> UIViewController
).self
) { originalImplementation in
{ viewController, nibName, bundle in
) { originalImplementation in { viewController, nibName, bundle in
// Get the class and bundle path of the view controller being initialized and check
// if the view controller belongs to the main bundle (this excludes, for eaxmple, UIKit classes)
let viewControllerClass = type(of: viewController)
Expand Down
84 changes: 38 additions & 46 deletions Sources/EmbraceCore/Embrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import EmbraceOTelInternal
import EmbraceStorageInternal
import EmbraceUploadInternal
import EmbraceObjCUtilsInternal
import OpenTelemetryApi

/**
Main class used to interact with the Embrace SDK.
Expand Down Expand Up @@ -40,8 +39,14 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
/// The `Embrace.Options` that were used to configure the SDK.
@objc public private(set) var options: Embrace.Options

/// Returns the current state of the SDK.
@objc public private(set) var state: EmbraceSDKState = .notInitialized

/// Returns whether the SDK was started.
@objc public private(set) var started: Bool
@available(*, deprecated, message: "Use `state` instead.")
@objc public var started: Bool {
return state == .started
}

/// Returns the `DeviceIdentifier` used by Embrace for the current device.
public private(set) var deviceId: DeviceIdentifier
Expand All @@ -56,7 +61,7 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
/// Returns true if the SDK is started and was not disabled through remote configurations.
@objc public var isSDKEnabled: Bool {
let remoteConfigEnabled = config?.isSDKEnabled ?? true
return started && remoteConfigEnabled
return state == .started && remoteConfigEnabled
}

/// Returns the version of the Embrace SDK.
Expand All @@ -77,8 +82,6 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
let sessionController: SessionController
let sessionLifecycle: SessionLifecycle

var isFirstStart: Bool = true

private let processingQueue = DispatchQueue(
label: "com.embrace.processing",
qos: .background,
Expand Down Expand Up @@ -122,6 +125,8 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
try options.validate()

client = try Embrace(options: options)
client?.state = .initialized

if let client = client {
client.recordSetupSpan(startTime: startTime)
return client
Expand All @@ -142,9 +147,8 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
init(options: Embrace.Options,
logControllable: LogControllable? = nil,
embraceStorage: EmbraceStorage? = nil) throws {
self.started = false
self.options = options

self.options = options
self.logLevel = options.logLevel

self.storage = try embraceStorage ?? Embrace.createStorage(options: options)
Expand Down Expand Up @@ -180,7 +184,7 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
logController?.sdkStateProvider = self

// setup otel
EmbraceOTel.setup(spanProcessors: .processors(for: storage, export: options.export))
EmbraceOTel.setup(spanProcessors: .processors(for: storage, export: options.export, sdkStateProvider: self))
let logSharedState = DefaultEmbraceLogSharedState.create(
storage: self.storage,
controller: self.logController,
Expand Down Expand Up @@ -212,8 +216,8 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
sessionLifecycle.setup()

Embrace.synchronizationQueue.sync {
guard started == false else {
Embrace.logger.warning("Embrace was already started!")
guard state == .initialized else {
Embrace.logger.warning("The Embrace SDK can only be started once!")
return
}

Expand All @@ -222,52 +226,34 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
return
}

let first = isFirstStart
isFirstStart = false

let processStartSpan: Span? = first ? createProcessStartSpan() : nil
defer { processStartSpan?.end() }
let processStartSpan = createProcessStartSpan()
defer { processStartSpan.end() }

let block: ()->Void = {
self.started = true
recordSpan(name: "emb-sdk-start", parent: processStartSpan, type: .performance) { _ in
state = .started

self.sessionLifecycle.startSession()

if first {
self.captureServices.install()
}
sessionLifecycle.startSession()
captureServices.install()

self.processingQueue.async { [weak self] in

self?.captureServices.start()

if first {
// fetch crash reports and link them to sessions
// then upload them
UnsentDataHandler.sendUnsentData(
storage: self?.storage,
upload: self?.upload,
otel: self,
logController: self?.logController,
currentSessionId: self?.sessionController.currentSession?.id,
crashReporter: self?.captureServices.crashReporter
)
}
// fetch crash reports and link them to sessions
// then upload them
UnsentDataHandler.sendUnsentData(
storage: self?.storage,
upload: self?.upload,
otel: self,
logController: self?.logController,
currentSessionId: self?.sessionController.currentSession?.id,
crashReporter: self?.captureServices.crashReporter
)

// retry any remaining cached upload data
self?.upload?.retryCachedData()
}
}

if first {
recordSpan(name: "emb-sdk-start", parent: processStartSpan, type: .performance) { _ in
block()
}
} else {
block()
}

isFirstStart = false
}

return self
Expand All @@ -276,6 +262,7 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
/// Method used to stop the Embrace SDK from capturing and generating data.
/// - Throws: `EmbraceSetupError.invalidThread` if not called from the main thread.
/// - Note: This method won't do anything if the Embrace SDK was already stopped.
/// - Note: The SDK can't be started again once stopped.
/// - Returns: The `Embrace` client instance.
@discardableResult
@objc public func stop() throws -> Embrace {
Expand All @@ -284,12 +271,17 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
}

Embrace.synchronizationQueue.sync {
guard started == true else {
guard state != .stopped else {
Embrace.logger.warning("Embrace was already stopped!")
return
}

started = false
guard state == .started else {
Embrace.logger.warning("Embrace was not started so it can't be stopped!")
return
}

state = .stopped

sessionLifecycle.stop()
sessionController.clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@
// Copyright © 2025 Embrace Mobile, Inc. All rights reserved.
//

import Foundation

protocol EmbraceSDKStateProvider: AnyObject {
var isEnabled: Bool { get}
}
import EmbraceCommonInternal

extension Embrace: EmbraceSDKStateProvider {
var isEnabled: Bool {
public var isEnabled: Bool {
return isSDKEnabled
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
import Foundation
import EmbraceOTelInternal
import EmbraceStorageInternal
import EmbraceCommonInternal
import OpenTelemetrySdk

extension Collection where Element == SpanProcessor {
static func processors(for storage: EmbraceStorage, export: OpenTelemetryExport?) -> [SpanProcessor] {
static func processors(
for storage: EmbraceStorage,
export: OpenTelemetryExport?,
sdkStateProvider: EmbraceSDKStateProvider
) -> [SpanProcessor] {
var processors: [SpanProcessor] = [
SingleSpanProcessor(
spanExporter: StorageSpanExporter(
options: .init(storage: storage),
logger: Embrace.logger
)
),
sdkStateProvider: sdkStateProvider
)
]

Expand Down
20 changes: 20 additions & 0 deletions Sources/EmbraceCore/Public/EmbraceSDKState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright © 2025 Embrace Mobile, Inc. All rights reserved.
//

import Foundation

/// Enum used to represent the current state of the Embrace SDK
@objc public enum EmbraceSDKState: Int {
/// The SDK was not setup yet
case notInitialized

/// The SDK was setup but hasn't started yet
case initialized

/// The SDK was started
case started

/// The SDK was stopped
case stopped
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ManualSessionLifecycle: SessionLifecycle {
active = true
}

func stop() {
func stop() {
active = false
}

Expand Down
Loading

0 comments on commit 3fd237a

Please sign in to comment.