Project: Embrace (Observability and Analysis / Observability)
File: GETTING_STARTED.md

This document will walk you through the Embrace BrandGame example app and what this app does to integrate with the EmbraceIO package. The BrandGame app is a simple SwiftUI app that contains various examples of an app using the Embrace SDK. [!NOTE] Triple tap the background to bring up the main menu! Otherwise the app will seem pretty empty... The BrandGame Xcode project can be found at . This is a great project to read through to get an idea on how the Embrace team would go about instrumenting an app. The BrandGame example app has the luxury of being within the same repo as the EmbraceIO package. So in its Xcode project settings it defines an SPM package dependency locally using a relative path. For your project, you'll want to add a package dependency in Xcode. Select `File > Add Package Dependencies...` and enter the repository URL `https://github.com/embrace-io/embrace-apple-sdk.git` into the search bar. Be sure to set the Dependency Rule to a recent release. When "Choose Package Products for embrace-apple-sdk" appears, Xcode will add all libraries to your application by default. For the most straightforward installation, select either `EmbraceIO` or `EmbraceIO-Dynamic` to add to your application target. For all other package products, do not add these to your target by selecting `None`. Explanation of Package Products Here's a quick summary of the 3 products this package contains: EmbraceIO - This is the recommend product to install for quick integration. It provides a convenience layer over `EmbraceCore` to simplify the setup interface. EmbraceCore - This is the implementation of the Embrace SDK. If you'd like to customize your integration, this product allows you to. EmbraceCrash - This contains the Embrace Crash Reporter. We keep this as a separate target for those apps that may not want crash reporting enabled, or may want to use a separate crash reporter. It is included as a dependency of `EmbraceIO`, but not `EmbraceCore`. We also distribute each product statically and dynamically. The `*-Dynamic` products should be used if you prefer dynamic libraries. The BrandGame example app is a straightforward SwiftUI application. When deciding where to initialize Embrace, we chose to override the App's `init` method. We'll import `EmbraceIO` and call `Embrace.setup` with an `Embrace.Options` object. Here is the logic in : ```swift import EmbraceIO import SwiftUI struct BrandGameApp: App { init() { do { try Embrace .setup(options: embraceOptions) .start() } catch let e { print("Error starting Embrace \(e.localizedDescription)") } } } ``` Once the Embrace client had been started, it is ready to be used to observe performance in your App. In the example above we refer to a variable `embraceOptions`. This is an instance of `Embrace.Options` that is used to customize the behavior of the SDK. The BrandGame app defines this as a computed property in an extension in . Separating the this computed property into its own extension allows us to use a conditional compilation block to change our options between our build configurations (Debug/Release). ```swift var embraceOptions: Embrace.Options { return .init( appId: "d3buG", appGroupId: nil, logLevel: .debug ) } var embraceOptions:

{ return .init( appId: "myApp", appGroupId: nil ) } ``` The `setup` and `start` methods both return a non-optional instance of the `Embrace` class. Feel free to store a reference to this object: ```swift let embrace = try Embrace .setup(options: embraceOptions) .start() // later on embrace.buildSpan(name: "my-operation") .markAsKeySpan() .startSpan() ``` Once `setup` has succeeded, you can access the same instance via the static `Embrace.client` property. It is important to note that this is an optional property, and will be `nil` if called before `setup` or if `setup` fails. ```swift try Embrace .setup(options: embraceOptions) .start() // later on Embrace.client?.buildSpan(name: "my-operation") .markAsKeySpan() .startSpan() ``` An example of this in the BrandGame app is in . In the `buildSpan` method we record a span to measure a user's reflex time: ```swift extension ReflexGameModel { private func buildSpan(startTime: Date) -> Span? { return Embrace.client?.buildSpan(name: "reflex-measure", type: .ux) .setStartTime(time: startTime) .markAsKeySpan() .startSpan() } } ``` For convenience, we use the `Embrace.client` static accessor to build this span. To export your app's logs and traces using the Embrace iOS SDK to the or a backend of your choice, initialize your `Embrace.Options` with an object. This object takes optional SpanExporters and LogExporters that point to your ingest points. Here are some example Exporter objects, initialized using HTTP endpoints that are specific to the OTel Collector or your chosen backend: ```swift private var otelSpanExporter: OtlpHttpTraceExporter { let configuration = URLSessionConfiguration.default let headerValue = // THIS VALUE IS COLLECTOR/BACKEND SPECIFIC configuration.httpAdditionalHeaders = ["Authorization": "Basic \(headerValue)"] let session = URLSession(configuration: configuration) return OtlpHttpTraceExporter( endpoint: URL(string: "https://otlp-gateway-prod-us-west-0.grafana.net/otlp/v1/traces")!, useSession: session ) } private var otelLogExporter: OtlpHttpLogExporter { let configuration = URLSessionConfiguration.default let headerValue = //THIS VALUE IS COLLECTOR/BACKEND SPECIFIC configuration.httpAdditionalHeaders = ["Authorization": "Basic \(headerValue)"] let session = URLSession(configuration: configuration) return OtlpHttpLogExporter( endpoint: URL(string: "https://otlp-gateway-prod-us-west-0.grafana.net/otlp/v1/logs")!, useSession: session ) } ``` Once these objects have been created, you can initialize the `Embrace.Options` with a `OpenTelemetryExport` that contains one, both, or neither of these Exporters: ```swift var embraceOptions: Embrace.Options { return .init( appId: APP_ID, appGroupId: nil, platform: .default, endpoints: Embrace.Endpoints.fromInfoPlist(), export: OpenTelemetryExport( spanExporter: otelSpanExporter, logExporter: otelLogExporter ) ) } ``` In order to have stack traces in error logs or crashes be as readable as possible, its important to upload your app's desymbolication files to the Embrace backend. The best way to do this is to add a build step to your project. First, download . This is an archive that contains the Embrace `upload` binary and also a shell script `run.sh`. These should be copied into your project at a known location like `embrace_support/upload` and `embrace_support/run.sh`. Then, in your app target's Xcode Build Phases pane, add a new 'Run Script Phase' at the end of the list of Build Phases. This run script phase should invoke the `run.sh` script with the envvars `EMBRACE_ID` and `EMBRACE_TOKEN`: ```sh EMBRACEID='USEYOURKEY' EMBRACETOKEN='USEYOURTOKEN' "path/to/run.sh" ``` These envvars are retrieved from your Embrace dashboard and are: EMBRACE_ID The 'appId' value you have configured in `Embrace.Options` EMBRACE_TOKEN A secret token that is an API key to authorize the upload request For more information on uploading dSYMs, check out our existing .