Skip to content

Commit

Permalink
Merge branch 'main' into core_data_poc
Browse files Browse the repository at this point in the history
# Conflicts:
#	Sources/EmbraceUploadInternal/EmbraceUpload.swift
  • Loading branch information
NachoEmbrace committed Jan 21, 2025
2 parents 2ec3eb1 + 239a70f commit c19cf04
Show file tree
Hide file tree
Showing 90 changed files with 1,907 additions and 459 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,22 @@ jobs:
- name: Push EmbraceIO Podspec
run: |
pod trunk push embrace-apple-sdk/EmbraceIO.podspec --allow-warnings
create_internal_release:
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
- create_github_release
env:
RC_VERSION: ${{ needs.extractor.outputs.rc_version }}
IS_PRODUCTION_READY: ${{ needs.extractor.outputs.is_production_ready }}

steps:
- name: Record SDK Version History
if: env.IS_PRODUCTION_READY == 'true'
run: |
curl -f -X POST ${{ vars.SDK_VERSION_URL }}/ios/version/ -H 'X-Embrace-CI: ${{ secrets.SDK_VERSION_TOKEN }}' -H 'Content-Type: application/json' -d '{"version": "${{ env.RC_VERSION }}"}'
# Note: missing/removed steps from old sdk
# - dsyms work
30 changes: 3 additions & 27 deletions .github/workflows/run-danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,12 @@ on:

jobs:
build:
runs-on: macos-14
runs-on: ubuntu-latest
name: "Run Danger"
steps:
- uses: actions/checkout@v4

- name: Select Xcode
# See https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
run: |
sudo xcode-select -s /Applications/Xcode_15.4.app
xcodebuild -version
- name: Install Danger Swift
run: |
if ! which danger-swift > /dev/null; then
echo "Danger-swift is not installed; We'll try to install it."
if ! which brew > /dev/null; then
echo "Brew is not installed; cannot proceed with Danger installation."
fi
brew bundle --verbose
echo "Danger was installed successfully"
else
echo "Danger-swift is already installed"
fi
danger-swift --version
- name: Run Danger
run: danger-swift ci
- name: Run Danger Swift
uses: danger/swift@3.20.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 6.7.0
*Jan 10th, 2025**
* Features
* Improvements to the Automatic View Capture functionality, allowing attributes to be added to traces (`TTFR` and `TTI`) using the `addAttributesToTrace(_:)` method.
* Fixes
* Fixed an issue causing crashes in views controllers with very short lifecycles (particularly in hosting controllers acting as internal bridges in SwiftUI).
* Fixed a bug causing compilation issues related to the use of `DispatchQueue`.

## 6.6.0
*Dec 12th, 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.6.0"
spec.version = "6.7.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 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.6.0"
public static let sdkVersion = "6.7.0"
}
30 changes: 27 additions & 3 deletions Sources/EmbraceCommonInternal/Protocols/DispatchableQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,37 @@

import Foundation

public protocol DispatchableQueue: AnyObject {
public protocol DispatchableQueue {
func async(_ block: @escaping () -> Void)
func sync(execute block: () -> Void)
}

extension DispatchQueue: DispatchableQueue {
public class DefaultDispatchableQueue: DispatchableQueue {
private let queue: DispatchQueue

init(queue: DispatchQueue) {
self.queue = queue
}

public func async(_ block: @escaping () -> Void) {
async(group: nil, execute: block)
queue.async(group: nil, execute: block)
}

public func sync(execute block: () -> Void) {
queue.sync(execute: block)
}

public static func with(label: String) -> DispatchableQueue {
DefaultDispatchableQueue(queue: .init(label: label))
}
}

public extension DispatchableQueue where Self == DefaultDispatchableQueue {
static func with(
label: String,
qos: DispatchQoS = .unspecified,
attributes: DispatchQueue.Attributes = []
) -> DispatchableQueue {
DefaultDispatchableQueue(queue: .init(label: label, qos: qos, attributes: attributes))
}
}
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}
}
80 changes: 80 additions & 0 deletions Sources/EmbraceCommonInternal/Swizzling/EmbraceSwizzler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright © 2024 Embrace Mobile, Inc. All rights reserved.
//
import ObjectiveC.runtime

public class EmbraceSwizzler {
public init() {
}

/// Swizzles a specific instance method of a given class.
///
/// This method allows you to replace the implementation of an instance method in the specified class (`type`)
/// with a custom implementation provided as a block. Only the implementation of the specified class is swizzled,
/// and parent class methods with the same selector are not affected.
///
/// - Parameters:
/// - type: The class in which the method resides. The method to be swizzled **must belong to this class specifically**.
/// - selector: The selector for the instance method to be swizzled.
/// - implementationType: The expected function signature of the original method (use `@convention(c)`)
/// - blockImplementationType: The expected function signature of the new implementation block (use `@convention(block)`)
/// - block: A closure that accepts the original implementation as input (`implementationType.Type`) and provides the
/// new implementation (`blockImplementationType.Type`)
///
/// - Important: This method only operates on methods explicitly declared in the specified class. If the method is inherited from a parent class, **it wont be swizzled**.
public func swizzleDeclaredInstanceMethod<T, F>(
in type: AnyClass,
selector: Selector,
implementationType: T.Type,
blockImplementationType: F.Type,
_ block: @escaping (T) -> F
) throws {

// Find the method in the specified class
var methodToSwizzle: Method?
var methodCount: UInt32 = 0

// We use `class_copyMethodList` and search for the method instead of using `class_getInstanceMethod`
// because we don't want to modify the `superclass` implementation.
let methods = class_copyMethodList(type, &methodCount)
if let methods = methods {
for index in 0..<Int(methodCount) {
let method = methods[index]
if sel_isEqual(method_getName(method), selector) {
methodToSwizzle = method
break
}
}

}

free(methods)

// If the method is not found, we exit early. This is not a real problem, that's why we don't throw as in `Swizzlable`
guard let method = methodToSwizzle else {
return
}

// Retrieve the original implementation of the method
let originalImplementation = method_getImplementation(method)
saveInCache(originalImplementation: originalImplementation, forMethod: method, associatedToClass: type)

// Create a block implementation by invoking the provided closure, passing the original implementation as input.
let originalTypifiedImplementation = unsafeBitCast(originalImplementation, to: implementationType)
let newImplementationBlock: F = block(originalTypifiedImplementation)
let newImplementation = imp_implementationWithBlock(newImplementationBlock)

// Do the actual IMP replacement
method_setImplementation(method, newImplementation)
}

private func saveInCache(originalImplementation: IMP, forMethod method: Method, associatedToClass: AnyClass) {
#if DEBUG
let swizzlerClassName = String(describing: type(of: self))
SwizzleCache.shared.addMethodImplementation(originalImplementation,
forMethod: method,
inClass: associatedToClass,
swizzler: swizzlerClassName)
#endif
}
}
8 changes: 6 additions & 2 deletions Sources/EmbraceConfigInternal/EmbraceConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class EmbraceConfig {

@ThreadSafe private var lastUpdateTime: TimeInterval = Date(timeIntervalSince1970: 0).timeIntervalSince1970

let configurable: EmbraceConfigurable
public let configurable: EmbraceConfigurable

let queue: DispatchableQueue

Expand All @@ -27,7 +27,7 @@ public class EmbraceConfig {
options: Options,
notificationCenter: NotificationCenter,
logger: InternalLogger,
queue: DispatchableQueue = DispatchQueue(label: "com.embrace.config", attributes: .concurrent)
queue: DispatchableQueue = .with(label: "com.embrace.config", attributes: .concurrent)
) {
self.options = options
self.notificationCenter = notificationCenter
Expand Down Expand Up @@ -99,6 +99,10 @@ extension EmbraceConfig /* EmbraceConfigurable delegation */ {
return configurable.isNetworkSpansForwardingEnabled
}

public var isUiLoadInstrumentationEnabled: Bool {
return configurable.isUiLoadInstrumentationEnabled
}

public var internalLogLimits: InternalLogLimits {
return configurable.internalLogLimits
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ extension RemoteConfig: EmbraceConfigurable {

public var isNetworkSpansForwardingEnabled: Bool { isEnabled(threshold: payload.networkSpansForwardingThreshold) }

public var isUiLoadInstrumentationEnabled: Bool { payload.uiLoadInstrumentationEnabled }

public var networkPayloadCaptureRules: [NetworkPayloadCaptureRule] { payload.networkPayloadCaptureRules }

public var internalLogLimits: InternalLogLimits {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct RemoteConfigPayload: Decodable, Equatable {
var sdkEnabledThreshold: Float
var backgroundSessionThreshold: Float
var networkSpansForwardingThreshold: Float
var uiLoadInstrumentationEnabled: Bool

var internalLogsTraceLimit: Int
var internalLogsDebugLimit: Int
Expand All @@ -33,6 +34,8 @@ public struct RemoteConfigPayload: Decodable, Equatable {
case threshold = "pct_enabled"
}

case uiLoadInstrumentationEnabled = "ui_load_instrumentation_enabled"

case internalLogLimits = "internal_log_limits"
enum InternalLogLimitsCodingKeys: String, CodingKey {
case trace
Expand Down Expand Up @@ -83,6 +86,12 @@ public struct RemoteConfigPayload: Decodable, Equatable {
networkSpansForwardingThreshold = defaultPayload.networkSpansForwardingThreshold
}

// ui load instrumentation
uiLoadInstrumentationEnabled = try rootContainer.decodeIfPresent(
Bool.self,
forKey: .uiLoadInstrumentationEnabled
) ?? defaultPayload.uiLoadInstrumentationEnabled

// internal logs limit
if rootContainer.contains(.internalLogLimits) {
let internalLogsLimitsContainer = try rootContainer.nestedContainer(
Expand Down Expand Up @@ -135,6 +144,7 @@ public struct RemoteConfigPayload: Decodable, Equatable {
sdkEnabledThreshold = 100.0
backgroundSessionThreshold = 0.0
networkSpansForwardingThreshold = 0.0
uiLoadInstrumentationEnabled = false

internalLogsTraceLimit = 0
internalLogsDebugLimit = 0
Expand Down
2 changes: 2 additions & 0 deletions Sources/EmbraceConfiguration/EmbraceConfigurable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import Foundation

var isNetworkSpansForwardingEnabled: Bool { get }

var isUiLoadInstrumentationEnabled: Bool { get }

var internalLogLimits: InternalLogLimits { get }

var networkPayloadCaptureRules: [NetworkPayloadCaptureRule] { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class DefaultConfig: EmbraceConfigurable {

public let isNetworkSpansForwardingEnabled: Bool = false

public let isUiLoadInstrumentationEnabled: Bool = false

public let internalLogLimits = InternalLogLimits()

public let networkPayloadCaptureRules = [NetworkPayloadCaptureRule]()
Expand Down
10 changes: 8 additions & 2 deletions Sources/EmbraceCore/Capture/CaptureServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EmbraceCaptureService
import EmbraceCommonInternal
import EmbraceStorageInternal
import EmbraceUploadInternal
import EmbraceConfiguration

final class CaptureServices {

Expand All @@ -16,7 +17,11 @@ final class CaptureServices {
var context: CrashReporterContext
weak var crashReporter: CrashReporter?

init(options: Embrace.Options, storage: EmbraceStorage?, upload: EmbraceUpload?) throws {
weak var config: EmbraceConfigurable?

init(options: Embrace.Options, config: EmbraceConfigurable?, storage: EmbraceStorage?, upload: EmbraceUpload?) throws {
self.config = config

// add required capture services
// and remove duplicates
services = CaptureServiceFactory.addRequiredServices(to: options.services.unique)
Expand Down Expand Up @@ -67,7 +72,8 @@ final class CaptureServices {
}

// for testing
init(services: [CaptureService], context: CrashReporterContext) {
init(config: EmbraceConfigurable?, services: [CaptureService], context: CrashReporterContext) {
self.config = config
self.services = services
self.context = context
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,6 @@ final class DefaultURLSessionTaskHandler: URLSessionTaskHandler {

private extension DefaultURLSessionTaskHandler {
static func queue() -> DispatchableQueue {
DispatchQueue(label: "com.embrace.URLSessionTaskHandler", qos: .utility)
.with(label: "com.embrace.URLSessionTaskHandler", qos: .utility)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ extension CaptureServices {
throw serviceNotFoundError
}

guard viewCaptureService.options.instrumentFirstRender else {
guard viewCaptureService.options.instrumentFirstRender,
config?.isUiLoadInstrumentationEnabled == true else {
throw firstRenderInstrumentationDisabledError
}

Expand Down Expand Up @@ -64,7 +65,7 @@ extension CaptureServices {
}

guard let builder = viewCaptureService.otel?.buildSpan(
name: name,
name: name,
type: type,
attributes: attributes,
autoTerminationCode: nil
Expand Down Expand Up @@ -108,6 +109,21 @@ extension CaptureServices {
let span = builder.startSpan()
span.end(time: endTime)
}

func addAttributesToTrace(
for viewController: UIViewController,
attributes: [String: String]
) throws {
guard let viewCaptureService = try validateCaptureService() else {
return
}

guard let parentSpan = viewCaptureService.parentSpan(for: viewController) else {
throw parentSpanNotFoundError
}

attributes.forEach { parentSpan.setAttribute(key: $0.key, value: .string($0.value)) }
}
}

#endif
Loading

0 comments on commit c19cf04

Please sign in to comment.