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

Release 6.6.0 #148

Merged
merged 15 commits into from
Dec 11, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
run: |
eval "$(~/.local/bin/mise activate bash)" >> ~/.bashrc
echo "$PATH"
mise doctor
# mise doctor
./bin/build_xcframeworks.sh

# TODO: Finish this step
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 6.6.0
*Dec 12th, 2024*
* Features
* Added new instrumentation for the `ViewCaptureService`. Can be enabled through `ViewCaptureService.Options.instrumentFirstRender`.
* Added url blacklist for the `URLSessionCaptureService`. Can be configured through `URLSessionCaptureService.Options.ignoredURLs`.
* Added the ability to auto terminate spans if the session ends while the span is still open.
* Updated the OpenTelemetry dependency to v1.12.1 which fixes some concurrency related crashes.
* Improved logic around Embrace data uploads and retries.
* Deprecated `Span.markAsKeySpan()`.
* Fixes
* Fixed the remote config parse sometimes failing.
* Fixed the remote config cache not working properly.
* Fixed crash logs sometimes not containing the session properties.
* Fixed keychain related crash/hang during startup.
* Fixed issues with the `WebViewCaptureService` that could lead to a crash.
* Fixed issue with the `URLSessionCaptureService` when dealing with `URLSessionDelegate` objects in Objective-C responding to methods without conforming to specific protocols.

## 6.5.2
*Nov 14th, 2024*
* Features
Expand Down
2 changes: 1 addition & 1 deletion EmbraceIO.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "EmbraceIO"
spec.version = "6.5.2"
spec.version = "6.6.0"
spec.summary = "Visibility into your users that you didn't have before."
spec.description = <<-DESC
Embrace is the only performance monitoring platform focused solely on mobile. We are built
Expand Down
2 changes: 1 addition & 1 deletion Examples/BrandGame/BrandGame/Embrace/App+Embrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension BrandGameApp {

private var otelExport: OpenTelemetryExport {
OpenTelemetryExport(
spanExporter: StdoutExporter(isDebug: true),
spanExporter: StdoutSpanExporter(isDebug: true),
logExporter: StdoutLogExporter(isDebug: true)
)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/EmbraceCommonInternal/EmbraceMeta.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
// Do not edit this file manually

public class EmbraceMeta {
public static let sdkVersion = "6.5.2"
public static let sdkVersion = "6.6.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import EmbraceConfiguration
/// Remote config uses the Embrace Config Service to request config values
public class RemoteConfig {

let logger: InternalLogger

// config requests
@ThreadSafe var payload: RemoteConfigPayload
let fetcher: RemoteConfigFetcher
Expand All @@ -19,6 +21,8 @@ public class RemoteConfig {

@ThreadSafe private(set) var updating = false

let cacheURL: URL?

public convenience init(
options: RemoteConfig.Options,
payload: RemoteConfigPayload = RemoteConfigPayload(),
Expand All @@ -38,6 +42,42 @@ public class RemoteConfig {
self.payload = payload
self.fetcher = fetcher
self.deviceIdHexValue = options.deviceId.intValue(digitCount: Self.deviceIdUsedDigits)
self.logger = logger

if let url = options.cacheLocation {
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
self.cacheURL = options.cacheLocation?.appendingPathComponent("cache")
loadFromCache()
} else {
self.cacheURL = nil
}
}

func loadFromCache() {
guard let url = cacheURL,
FileManager.default.fileExists(atPath: url.path) else {
return
}

do {
let data = try Data(contentsOf: url)
payload = try JSONDecoder().decode(RemoteConfigPayload.self, from: data)
} catch {
logger.error("Error loading cached remote config!")
}
}

func saveToCache(_ data: Data?) {
guard let url = cacheURL,
let data = data else {
return
}

do {
try data.write(to: url, options: .atomic)
} catch {
logger.warning("Error saving remote config cache!")
}
}
}

Expand Down Expand Up @@ -67,7 +107,7 @@ extension RemoteConfig: EmbraceConfigurable {
}

updating = true
fetcher.fetch { [weak self] newPayload in
fetcher.fetch { [weak self] newPayload, data in
defer { self?.updating = false }
guard let strongSelf = self else {
completion(false, nil)
Expand All @@ -82,6 +122,8 @@ extension RemoteConfig: EmbraceConfigurable {
let didUpdate = strongSelf.payload != newPayload
strongSelf.payload = newPayload

strongSelf.saveToCache(data)

completion(didUpdate, nil)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public extension RemoteConfig {
let appVersion: String
let userAgent: String

let cacheLocation: URL?

let urlSessionConfiguration: URLSessionConfiguration

public init(
Expand All @@ -28,6 +30,7 @@ public extension RemoteConfig {
sdkVersion: String,
appVersion: String,
userAgent: String,
cacheLocation: URL?,
urlSessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default
) {
self.apiBaseUrl = apiBaseUrl
Expand All @@ -38,6 +41,7 @@ public extension RemoteConfig {
self.sdkVersion = sdkVersion
self.appVersion = appVersion
self.userAgent = userAgent
self.cacheLocation = cacheLocation
self.urlSessionConfiguration = urlSessionConfiguration
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class RemoteConfigFetcher {
)
}

func fetch(completion: @escaping (RemoteConfigPayload?) -> Void) {
func fetch(completion: @escaping (RemoteConfigPayload?, Data?) -> Void) {
guard let request = newRequest() else {
completion(nil)
completion(nil, nil)
return
}

Expand All @@ -38,19 +38,19 @@ class RemoteConfigFetcher {

guard let data = data, error == nil else {
self?.logger.error("Error fetching remote config:\n\(String(describing: error?.localizedDescription))")
completion(nil)
completion(nil, nil)
return
}

guard let httpResponse = response as? HTTPURLResponse else {
self?.logger.error("Error fetching remote config - Invalid response:\n\(String(describing: response?.description))")
completion(nil)
completion(nil, nil)
return
}

guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else {
self?.logger.error("Error fetching remote config - Invalid response:\n\(httpResponse.description))")
completion(nil)
completion(nil, nil)
return
}

Expand All @@ -59,11 +59,11 @@ class RemoteConfigFetcher {
let payload = try JSONDecoder().decode(RemoteConfigPayload.self, from: data)

self?.logger.info("Successfully fetched remote config")
completion(payload)
completion(payload, data)
} catch {
self?.logger.error("Error decoding remote config:\n\(error.localizedDescription)")
// if a decoding issue happens, instead of returning `nil`, we provide a default `RemoteConfigPayload`
completion(RemoteConfigPayload())
completion(RemoteConfigPayload(), nil)
}
}

Expand Down
51 changes: 29 additions & 22 deletions Sources/EmbraceCore/Capture/UX/View/UIViewControllerHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ class UIViewControllerHandler {
@ThreadSafe var alreadyFinishedUiReadyIds: Set<String> = []

init() {
NotificationCenter.default.addObserver(
Embrace.notificationCenter.addObserver(
self,
selector: #selector(appDidEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
selector: #selector(foregroundSessionDidEnd),
name: .embraceForegroundSessionDidEnd,
object: nil
)
}

deinit {
NotificationCenter.default.removeObserver(self)
Embrace.notificationCenter.removeObserver(self)
}

func parentSpan(for vc: UIViewController) -> Span? {
Expand All @@ -53,16 +53,18 @@ class UIViewControllerHandler {
return parentSpans[id]
}

@objc func appDidEnterBackground() {
@objc func foregroundSessionDidEnd(_ notification: Notification? = nil) {
let now = notification?.object as? Date ?? Date()

// end all parent spans and visibility spans if the app enters the background
// also clear all the cached spans
queue.async {
for span in self.visibilitySpans.values {
span.end()
span.end(time: now)
}

for id in self.parentSpans.keys {
self.forcefullyEndSpans(id: id)
self.forcefullyEndSpans(id: id, time: now)
}

self.parentSpans.removeAll()
Expand Down Expand Up @@ -206,8 +208,10 @@ class UIViewControllerHandler {
}

// end view did appear span
let now = Date()

if let span = self.viewDidAppearSpans.removeValue(forKey: id) {
span.end()
span.end(time: now)
}

guard let parentSpan = self.parentSpans[id] else {
Expand All @@ -216,7 +220,7 @@ class UIViewControllerHandler {

// end time to first render span
if parentSpan.isTimeToFirstRender {
parentSpan.end()
parentSpan.end(time: now)
self.clear(id: id, vc: vc)

// generate ui ready span
Expand All @@ -231,8 +235,8 @@ class UIViewControllerHandler {
// if the view controller was already flagged as ready to interact
// we end the spans right away
if self.alreadyFinishedUiReadyIds.contains(id) {
span.end()
parentSpan.end()
span.end(time: now)
parentSpan.end(time: now)

self.clear(id: id, vc: vc)

Expand All @@ -250,13 +254,16 @@ class UIViewControllerHandler {
return
}

let now = Date()

// end visibility span
if let span = self.visibilitySpans[id] {
span.end()
span.end(time: now)
self.visibilitySpans[id] = nil
}

// force end all spans
self.forcefullyEndSpans(id: id)
self.forcefullyEndSpans(id: id, time: now)
}
}

Expand All @@ -271,8 +278,9 @@ class UIViewControllerHandler {
// if we have a ui ready span it means that viewDidAppear already happened
// in this case we close the spans
if let span = self.uiReadySpans[id] {
span.end()
parentSpan.end()
let now = Date()
span.end(time: now)
parentSpan.end(time: now)
self.clear(id: id, vc: vc)

// otherwise it means the view is still loading, in this case we flag
Expand All @@ -284,26 +292,26 @@ class UIViewControllerHandler {
}
}

private func forcefullyEndSpans(id: String) {
private func forcefullyEndSpans(id: String, time: Date) {

if let viewDidLoadSpan = self.viewDidLoadSpans[id] {
viewDidLoadSpan.end(errorCode: .userAbandon)
viewDidLoadSpan.end(errorCode: .userAbandon, time: time)
}

if let viewWillAppearSpan = self.viewWillAppearSpans[id] {
viewWillAppearSpan.end(errorCode: .userAbandon)
viewWillAppearSpan.end(errorCode: .userAbandon, time: time)
}

if let viewDidAppearSpan = self.viewDidAppearSpans[id] {
viewDidAppearSpan.end(errorCode: .userAbandon)
viewDidAppearSpan.end(errorCode: .userAbandon, time: time)
}

if let uiReadySpan = self.uiReadySpans[id] {
uiReadySpan.end(errorCode: .userAbandon)
uiReadySpan.end(errorCode: .userAbandon, time: time)
}

if let parentSpan = self.parentSpans[id] {
parentSpan.end(errorCode: .userAbandon)
parentSpan.end(errorCode: .userAbandon, time: time)
}

self.clear(id: id)
Expand Down Expand Up @@ -338,7 +346,6 @@ class UIViewControllerHandler {
self.viewDidLoadSpans[id] = nil
self.viewWillAppearSpans[id] = nil
self.viewDidAppearSpans[id] = nil
self.visibilitySpans[id] = nil
self.uiReadySpans[id] = nil
self.alreadyFinishedUiReadyIds.remove(id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension ViewCaptureService {
}

@objc public convenience override init() {
self.init(instrumentVisibility: true, instrumentFirstRender: true)
self.init(instrumentVisibility: true, instrumentFirstRender: false)
}
}
}
14 changes: 13 additions & 1 deletion Sources/EmbraceCore/Embrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
/// Returns the current `MetadataHandler` used to store resources and session properties.
@objc public let metadata: MetadataHandler

var isSDKEnabled: Bool {
if let config = config {
return config.isSDKEnabled
}
return true
}

let config: EmbraceConfig?
let storage: EmbraceStorage
let upload: EmbraceUpload?
Expand Down Expand Up @@ -149,7 +156,8 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
self.logController = logControllable ?? LogController(
storage: storage,
upload: upload,
controller: sessionController
controller: sessionController,
config: config
)
super.init()

Expand Down Expand Up @@ -258,6 +266,10 @@ To start the SDK you first need to configure it using an `Embrace.Options` insta
@objc private func onConfigUpdated() {
if let config = config {
Embrace.logger.limits = config.internalLogLimits
if !config.isSDKEnabled {
Embrace.logger.debug("SDK was disabled")
captureServices.stop()
}
}
}
}
Loading
Loading