Skip to content

Commit e1db3c4

Browse files
authored
docs: update the README and code to demonstrate integration with native sdks (#4)
* docs: update the README with info about using native sdks * docs: add example code for native integration
1 parent 223919a commit e1db3c4

File tree

79 files changed

+4942
-7
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4942
-7
lines changed

README.md

Lines changed: 257 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,260 @@ npm install --save \
3535

3636
6. See examples in [app/(tabs)/demo.tsx](app/(tabs)/demo.tsx) for how to create manual telemetry.
3737

38-
For more information, including how to configure auto-instrumentation for `fetch` and other libraries, see the [OpenTelemetry Demo documentation for React Native](https://opentelemetry.io/docs/demo/services/react-native-app/).
38+
For more information, including how to configure auto-instrumentation for `fetch` and other libraries, see the [OpenTelemetry Demo documentation for React Native](https://opentelemetry.io/docs/demo/services/react-native-app/).
39+
40+
## Installing a native SDK
41+
42+
To get Core Mobile Vitals and other auto-instrumentation, or to instrument native code, you can
43+
install a native Honeycomb SDK alongside the React Native SDK.
44+
45+
Note: It is recommended to use different `service.name`s for iOS, Android, and React Native, e.g. `“my-app-ios”`, `“my-app-android”`, and `“my-app-react-native”`, so that each app will appear as a separate dataset.
46+
47+
## Installing the Swift SDK
48+
49+
To get started, install the
50+
[Honeycomb Swift SDK](https://github.com/honeycombio/honeycomb-opentelemetry-swift) following
51+
the instructions in its README.
52+
53+
React Native native modules are usually written in Objective C, while the Honeycomb SDK is written
54+
in Swift, so you will want to make an `@objc` wrapper for Honeycomb and use it to call through to
55+
Swift methods.
56+
57+
```swift
58+
@objc public class HoneycombWrapper : NSObject {
59+
@objc public func configure() {
60+
do {
61+
let options = try HoneycombOptions.Builder()
62+
.setAPIKey("YOUR-API-KEY-HERE")
63+
.setServiceName("YOUR-SERVICE-NAME-HERE")
64+
.build()
65+
try Honeycomb.configure(options: options)
66+
} catch {
67+
NSException(name: NSExceptionName("HoneycombOptionsError"), reason: "\(error)").raise()
68+
}
69+
}
70+
}
71+
```
72+
73+
You can initialize the wrapper from your module's `init` method.
74+
75+
```objectivec
76+
- (instancetype)init {
77+
self = [super init];
78+
if (self) {
79+
_honeycomb = [[HoneycombWrapper alloc] init];
80+
[_honeycomb configure];
81+
}
82+
return self;
83+
}
84+
```
85+
86+
## Installing the native Android SDK
87+
88+
To get started, install the
89+
[Honeycomb Android SDK](https://github.com/honeycombio/honeycomb-opentelemetry-android) following
90+
the instructions in its README.
91+
92+
Add an `OpenTelemetryRum` member to your `Application` subclass, and initialize it in `onCreate`.
93+
94+
```kotlin
95+
class MainApplication : Application(), ReactApplication {
96+
var otelRum: OpenTelemetryRum? = null
97+
98+
// ...
99+
100+
override fun onCreate() {
101+
super.onCreate()
102+
103+
val options = HoneycombOptions.builder(this)
104+
.setApiKey("YOUR-API-KEY-HERE")
105+
.setServiceName("YOUR-SERVICE-NAME-HERE")
106+
.build()
107+
108+
otelRum = Honeycomb.configure(this, options)
109+
}
110+
}
111+
```
112+
113+
Add a `val otel: () -> OpenTelemetryRum?` member to your native module, and pass in
114+
`{ otelRum }` when constructing it.
115+
116+
## Trace Propagation
117+
118+
When calling into native code from JavaScript, you can manually pass through a parent ID and trace
119+
ID, to have the telemetry show up as a single trace in Honeycomb.
120+
121+
In Android:
122+
123+
```kotlin
124+
@ReactMethod
125+
fun sendNativeSpanWithParent(parentId: String?, traceId: String?) {
126+
otel()?.let { otel ->
127+
var spanBuilder = otel
128+
.openTelemetry
129+
.getTracer("android sdk")
130+
.spanBuilder("child span")
131+
132+
if (parentId != null && traceId != null) {
133+
val remoteContext = SpanContext.createFromRemoteParent(
134+
traceId,
135+
parentId,
136+
TraceFlags.getDefault(),
137+
TraceState.getDefault(),
138+
)
139+
140+
val context = Context.current().with(Span.wrap(remoteContext))
141+
142+
spanBuilder = spanBuilder.setParent(context)
143+
}
144+
145+
val span = spanBuilder.startSpan()
146+
span.end()
147+
}
148+
}
149+
```
150+
151+
Or iOS:
152+
153+
```swift
154+
@objc public func sendSpan(parentId: String?, traceId: String?) {
155+
let tracerProvider = OpenTelemetry.instance.tracerProvider.get(
156+
instrumentationName: "swift sdk",
157+
instrumentationVersion: nil
158+
)
159+
160+
var spanBuilder = tracerProvider
161+
.spanBuilder(spanName: "child span")
162+
163+
if let traceId = traceId, let parentId = parentId {
164+
spanBuilder = spanBuilder.setParent(SpanContext.createFromRemoteParent(
165+
traceId: TraceId(fromHexString: traceId),
166+
spanId: SpanId(fromHexString: parentId),
167+
traceFlags: TraceFlags(),
168+
traceState: TraceState()))
169+
}
170+
171+
let span = spanBuilder.startSpan()
172+
span.end()
173+
}
174+
```
175+
176+
```objectivec
177+
RCT_EXPORT_METHOD(sendNativeSpanWithParent:(NSString *)parentID trace:(NSString *)traceID) {
178+
[_honeycomb sendSpanWithParentId:parentID traceId:traceID];
179+
}
180+
```
181+
182+
To obtain the IDs from JavaScript:
183+
184+
```typescript
185+
function sendNativeSpan(parentId: string, traceId: string) {
186+
const {YourModule} = NativeModules;
187+
YourModule.sendNativeSpanWithParent(parentId, traceId);
188+
}
189+
190+
trace
191+
.getTracer("javascript sdk")
192+
.startActiveSpan("parent span", span => {
193+
let parentId = span.spanContext().spanId;
194+
let traceId = span.spanContext().traceId;
195+
sendNativeSpan(parentId, traceId);
196+
span.end();
197+
});
198+
```
199+
200+
## Session Syncing
201+
202+
Sometimes, it's not possible or desirable to manually pass through trace IDs.
203+
But it's still useful to be able to have a consistent `session.id` to identify
204+
all the traces within a single session, regardless of whether the telemetry was
205+
sent from JavaScript, Swift, or Kotlin. To do that, you can add a `SpanProcessor`
206+
to the JavaScript layer to let it use the `session.id` generated by the native
207+
SDK.
208+
209+
In Android, it's easy to add a function to your native module to get the session ID.
210+
```kotlin
211+
@ReactMethod(isBlockingSynchronousMethod = true)
212+
fun getSessionId(): String? = otel()?.rumSessionId
213+
```
214+
215+
In iOS, it's a little more complicated. Start by adding a property to your Honeycomb wrapper to
216+
keep track of the ID.
217+
```swift
218+
private let sessionLock = NSLock()
219+
220+
private var _sessionId: String? = nil
221+
222+
@objc public var sessionId: String? {
223+
get {
224+
return sessionLock.withLock {
225+
_sessionId
226+
}
227+
}
228+
set(value) {
229+
sessionLock.withLock {
230+
_sessionId = value
231+
}
232+
}
233+
}
234+
```
235+
236+
Then, when configuring the SDK, add a listener to keep the member up to date.
237+
238+
```swift
239+
NotificationCenter.default.addObserver(
240+
forName: .sessionStarted,
241+
object: nil,
242+
queue: .main
243+
) { notification in
244+
guard let session = notification.userInfo?["session"] as? HoneycombSession else {
245+
self.sessionId = nil
246+
return
247+
}
248+
self.sessionId = session.id
249+
}
250+
```
251+
252+
Next, add a method to the Objective C native module code to call the wrapper.
253+
254+
```objectivec
255+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getSessionId) {
256+
return [_honeycomb sessionId];
257+
}
258+
```
259+
260+
In your JavaScript layer, create the `SpanProcessor`:
261+
262+
```typescript
263+
function getSessionId(): string | null {
264+
const {YourModule} = NativeModules;
265+
return YourModule.getSessionId();
266+
}
267+
268+
class SessionIdSpanProcessor implements SpanProcessor {
269+
onStart(span: Span, parentContext: Context): void {
270+
let sessionId = getSessionId();
271+
if (sessionId) {
272+
span.setAttribute('session.id', sessionId);
273+
}
274+
}
275+
onEnd(span: ReadableSpan): void {}
276+
forceFlush(): Promise<void> { return Promise.resolve(); }
277+
shutdown(): Promise<void> { return Promise.resolve(); }
278+
}
279+
```
280+
281+
Finally, update the configuration code to pass in the new processor:
282+
283+
```typescript
284+
const traceProvider = new WebTracerProvider({
285+
resource,
286+
spanProcessors: [
287+
new SessionIdSpanProcessor(),
288+
new SimpleSpanProcessor(
289+
new OTLPTraceExporter({ headers, url: `${honeycombURL}/v1/traces` })
290+
),
291+
new SimpleSpanProcessor(new ConsoleSpanExporter()),
292+
],
293+
});
294+
```

android/.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# OSX
2+
#
3+
.DS_Store
4+
5+
# Android/IntelliJ
6+
#
7+
build/
8+
.idea
9+
.gradle
10+
local.properties
11+
*.iml
12+
*.hprof
13+
.cxx/
14+
15+
# Bundle artifacts
16+
*.jsbundle

0 commit comments

Comments
 (0)