@@ -35,4 +35,260 @@ npm install --save \
35
35
36
36
6 . See examples in [ app/(tabs)/demo.tsx] ( app/(tabs)/demo.tsx ) for how to create manual telemetry.
37
37
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
+ ```
0 commit comments