@coralogix/react-native-plugin 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/CxSdk.podspec +3 -3
- package/README.md +139 -0
- package/android/build.gradle +13 -1
- package/android/src/main/java/com/cxsdk/CxSdkModule.kt +151 -4
- package/android/src/main/java/com/cxsdk/RUMClient.kt +7 -0
- package/android/src/test/kotlin/com/cxsdk/ExtractOtelIdsTest.kt +49 -0
- package/index.cjs.js +195 -4
- package/index.esm.js +193 -5
- package/ios/CxSdk.mm +21 -0
- package/ios/CxSdk.swift +123 -1
- package/package.json +1 -1
- package/src/custom-spans/CoralogixCustomSpan.d.ts +9 -0
- package/src/custom-spans/CoralogixCustomTracer.d.ts +18 -0
- package/src/custom-spans/CoralogixGlobalSpan.d.ts +20 -0
- package/src/custom-spans/CustomSpanBridge.d.ts +11 -0
- package/src/custom-spans/CustomSpanRegistry.d.ts +12 -0
- package/src/index.d.ts +16 -1
- package/src/model/CoralogixDomain.d.ts +1 -0
- package/src/model/CoralogixOtelWebType.d.ts +20 -1
- package/src/model/Types.d.ts +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
## 0.5.0 (2026-05-17)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- bridge startTimeMeasure / endTimeMeasure to native (CX-40525)
|
|
6
|
+
- expose excludeFromSampling on JS config + native bridges
|
|
7
|
+
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
### 🚀 Features
|
|
11
|
+
|
|
12
|
+
- Bridge `CoralogixRum.startTimeMeasure(name, labels?)` / `CoralogixRum.endTimeMeasure(name)` to the native iOS and Android SDKs as a fire-and-forget pass-through ([CX-40525](https://coralogix.atlassian.net/browse/CX-40525)). The JS side keeps no state — native owns the in-flight registry.
|
|
13
|
+
|
|
14
|
+
## 0.4.0 (2026-05-03)
|
|
15
|
+
|
|
16
|
+
### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- Custom Spans & Traces Exporter (CX-36055)
|
|
19
|
+
|
|
20
|
+
### 🩹 Fixes
|
|
21
|
+
|
|
22
|
+
- import CoralogixIgnoredInstrument in index.ts for rollup build
|
|
23
|
+
|
|
1
24
|
## 0.3.3 (2026-04-05)
|
|
2
25
|
|
|
3
26
|
### 🩹 Fixes
|
package/CxSdk.podspec
CHANGED
|
@@ -16,9 +16,9 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
|
-
s.dependency 'Coralogix','2.4
|
|
20
|
-
s.dependency 'CoralogixInternal','2.4
|
|
21
|
-
s.dependency 'SessionReplay','2.4
|
|
19
|
+
s.dependency 'Coralogix','2.6.4'
|
|
20
|
+
s.dependency 'CoralogixInternal','2.6.4'
|
|
21
|
+
s.dependency 'SessionReplay','2.6.4'
|
|
22
22
|
|
|
23
23
|
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
24
24
|
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
package/README.md
CHANGED
|
@@ -156,6 +156,25 @@ await CoralogixRum.init({
|
|
|
156
156
|
});
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
### Exclude From Sampling
|
|
160
|
+
|
|
161
|
+
By default, when a session is sampled out (via `sessionSampleRate`), nothing is
|
|
162
|
+
emitted for that session. Use `excludeFromSampling` to keep emitting specific
|
|
163
|
+
event categories even when the session is sampled out.
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
import { CoralogixRum } from '@coralogix/react-native-plugin';
|
|
167
|
+
|
|
168
|
+
await CoralogixRum.init({
|
|
169
|
+
// ...
|
|
170
|
+
sessionSampleRate: 10,
|
|
171
|
+
excludeFromSampling: ['errors', 'logs'], // always emitted, even for sampled-out sessions
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Allowed values: `'errors'`, `'logs'`, `'network'`, `'userInteractions'`,
|
|
176
|
+
`'mobileVitals'`, `'customSpan'`, `'customMeasurement'`.
|
|
177
|
+
|
|
159
178
|
### Ignore Errors
|
|
160
179
|
|
|
161
180
|
The ignoreErrors option allows you to exclude errors that meet specific criteria.
|
|
@@ -184,6 +203,126 @@ await CoralogixRum.init({
|
|
|
184
203
|
});
|
|
185
204
|
```
|
|
186
205
|
|
|
206
|
+
### Custom Spans
|
|
207
|
+
|
|
208
|
+
Create manual RUM spans to instrument custom flows in your application. Requires `traceParentInHeader.enabled: true`.
|
|
209
|
+
|
|
210
|
+
**Prerequisite:**
|
|
211
|
+
```javascript
|
|
212
|
+
await CoralogixRum.init({
|
|
213
|
+
// ...
|
|
214
|
+
traceParentInHeader: { enabled: true },
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Basic usage — global span with a child:**
|
|
219
|
+
```javascript
|
|
220
|
+
import { CoralogixRum } from '@coralogix/react-native-plugin';
|
|
221
|
+
|
|
222
|
+
const tracer = CoralogixRum.getCustomTracer();
|
|
223
|
+
|
|
224
|
+
const globalSpan = await tracer.startGlobalSpan('checkout', { step: 'start' });
|
|
225
|
+
if (!globalSpan) return; // another global span is already active
|
|
226
|
+
|
|
227
|
+
const childSpan = await globalSpan.startCustomSpan('validate-cart');
|
|
228
|
+
await childSpan?.endSpan();
|
|
229
|
+
|
|
230
|
+
await globalSpan.endSpan();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Linking network requests with `withContext`:**
|
|
234
|
+
|
|
235
|
+
When you call `fetch` inside `withContext`, the network request is automatically linked to the active global span's trace.
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
const globalSpan = await tracer.startGlobalSpan('checkout');
|
|
239
|
+
|
|
240
|
+
await globalSpan.withContext(async () => {
|
|
241
|
+
await fetch('https://api.example.com/cart'); // linked to globalSpan's traceId
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
await globalSpan.endSpan();
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**`ignoredInstruments` — exclude auto-instrumentation from the trace:**
|
|
248
|
+
|
|
249
|
+
Pass instrument names to prevent network requests, errors, or interactions fired during this tracer's spans from being linked to your custom trace.
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
const tracer = CoralogixRum.getCustomTracer(['networkRequests', 'userInteractions', 'errors']);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> **Note:** Only one global span may be active at a time. `startGlobalSpan` returns `null` if a global span is already open.
|
|
256
|
+
|
|
257
|
+
### Time Measurement
|
|
258
|
+
|
|
259
|
+
Measure the duration of arbitrary flows with a pair of `startTimeMeasure(name, labels?)` / `endTimeMeasure(name)` calls. The native SDK records start/end timestamps and reports the delta as a custom-measurement span (milliseconds).
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
import { CoralogixRum } from '@coralogix/react-native-plugin';
|
|
263
|
+
|
|
264
|
+
CoralogixRum.startTimeMeasure('checkout', { flow: 'checkout' });
|
|
265
|
+
|
|
266
|
+
await validateCart();
|
|
267
|
+
await charge();
|
|
268
|
+
await confirm();
|
|
269
|
+
|
|
270
|
+
CoralogixRum.endTimeMeasure('checkout');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Behaviour:**
|
|
274
|
+
|
|
275
|
+
- Both calls are fire-and-forget — they cross the bridge directly into native and return immediately.
|
|
276
|
+
- The JS side keeps no state; the native SDK owns the in-flight registry.
|
|
277
|
+
- **You are responsible for pairing every `startTimeMeasure(name, labels?)` with exactly one `endTimeMeasure(name)`.** Leaked starts persist in memory until `CoralogixRum.shutdown()`.
|
|
278
|
+
- Unmatched `endTimeMeasure` calls are dropped silently by the native SDK.
|
|
279
|
+
- Calling `startTimeMeasure` again with an open `name` overwrites the previous start.
|
|
280
|
+
|
|
281
|
+
### Traces Exporter
|
|
282
|
+
|
|
283
|
+
Receive OTLP-formatted trace batches from the native SDK in your JavaScript code. Use this to forward spans to an OTLP-compatible backend (e.g. Jaeger, custom collector) alongside Coralogix.
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
await CoralogixRum.init({
|
|
287
|
+
// ...
|
|
288
|
+
tracesExporter: (data) => {
|
|
289
|
+
// data.resource_spans contains OTLP JSON-format span data
|
|
290
|
+
sendToMyOtlpBackend(JSON.stringify(data));
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The callback receives a `TraceExporterData` object following the OTLP JSON format:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
{
|
|
299
|
+
resource_spans: [
|
|
300
|
+
{
|
|
301
|
+
resource: { attributes: [{ key: string, value: { string_value: string } }] },
|
|
302
|
+
scope_spans: [
|
|
303
|
+
{
|
|
304
|
+
scope: { name: string, version?: string },
|
|
305
|
+
spans: [
|
|
306
|
+
{
|
|
307
|
+
trace_id: string,
|
|
308
|
+
span_id: string,
|
|
309
|
+
parent_span_id?: string,
|
|
310
|
+
name: string,
|
|
311
|
+
start_time_unix_nano: string,
|
|
312
|
+
end_time_unix_nano: string,
|
|
313
|
+
attributes: [{ key: string, value: {...} }],
|
|
314
|
+
status: { code: string },
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
> **Note:** The callback fires once per native export batch, which typically contains several spans rather than one per span.
|
|
325
|
+
|
|
187
326
|
### beforeSend
|
|
188
327
|
|
|
189
328
|
Enable event access and modification before sending to Coralogix, supporting content modification, and event discarding.
|
package/android/build.gradle
CHANGED
|
@@ -61,6 +61,15 @@ android {
|
|
|
61
61
|
"generated/jni"
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
|
+
test {
|
|
65
|
+
java.srcDirs += ["src/test/kotlin"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
testOptions {
|
|
70
|
+
unitTests.all {
|
|
71
|
+
useJUnitPlatform()
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
@@ -75,7 +84,10 @@ dependencies {
|
|
|
75
84
|
implementation "com.facebook.react:react-android"
|
|
76
85
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
77
86
|
|
|
78
|
-
implementation "com.coralogix:android-sdk:2.
|
|
87
|
+
implementation "com.coralogix:android-sdk:2.12.0"
|
|
88
|
+
|
|
89
|
+
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
|
90
|
+
testImplementation "org.mockito:mockito-core:5.12.0"
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
react {
|
|
@@ -5,6 +5,11 @@ import android.os.Handler
|
|
|
5
5
|
import android.os.Looper
|
|
6
6
|
import android.util.Log
|
|
7
7
|
import com.coralogix.android.sdk.CoralogixRum
|
|
8
|
+
import com.coralogix.android.sdk.customspans.CoralogixCustomSpan
|
|
9
|
+
import com.coralogix.android.sdk.customspans.CoralogixGlobalSpan
|
|
10
|
+
import com.coralogix.android.sdk.customspans.CoralogixIgnoredInstrument
|
|
11
|
+
import com.coralogix.android.sdk.model.ExcludableInstrumentation
|
|
12
|
+
import com.coralogix.android.sdk.traceexporter.CoralogixTraceExporterData
|
|
8
13
|
import com.coralogix.android.sdk.internal.features.instrumentations.error.CoralogixErrorDecorator
|
|
9
14
|
import com.coralogix.android.sdk.internal.features.instrumentations.network.NetworkRequestDetails
|
|
10
15
|
import com.coralogix.android.sdk.internal.infrastructure.threaddump.CoralogixJsStackFrame
|
|
@@ -39,10 +44,14 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
39
44
|
import org.json.JSONArray
|
|
40
45
|
import org.json.JSONObject
|
|
41
46
|
import com.facebook.react.uimanager.UIManagerModule
|
|
47
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
42
48
|
|
|
43
49
|
class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
44
50
|
ReactContextBaseJavaModule(reactContext), RUMClient, SessionReplayClient {
|
|
45
51
|
|
|
52
|
+
private val globalSpans = ConcurrentHashMap<String, CoralogixGlobalSpan>()
|
|
53
|
+
private val customSpans = ConcurrentHashMap<String, CoralogixCustomSpan>()
|
|
54
|
+
|
|
46
55
|
override fun getName(): String {
|
|
47
56
|
return NAME
|
|
48
57
|
}
|
|
@@ -134,6 +143,16 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
134
143
|
CoralogixRum.sendCustomMeasurement(name, value)
|
|
135
144
|
}
|
|
136
145
|
|
|
146
|
+
@ReactMethod
|
|
147
|
+
override fun startTimeMeasure(name: String, labels: ReadableMap?) {
|
|
148
|
+
CoralogixRum.startTimeMeasure(name, labels?.toStringAnyMap() ?: emptyMap())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@ReactMethod
|
|
152
|
+
override fun endTimeMeasure(name: String) {
|
|
153
|
+
CoralogixRum.endTimeMeasure(name)
|
|
154
|
+
}
|
|
155
|
+
|
|
137
156
|
@ReactMethod
|
|
138
157
|
override fun reportError(details: ReadableMap) {
|
|
139
158
|
val errorType = details.getString("error_type").orEmpty()
|
|
@@ -290,10 +309,73 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
290
309
|
}
|
|
291
310
|
// endregion
|
|
292
311
|
|
|
312
|
+
// region - Custom Spans
|
|
313
|
+
|
|
314
|
+
@ReactMethod
|
|
315
|
+
override fun startGlobalSpan(name: String, labels: ReadableMap?, ignoredInstruments: ReadableArray?, promise: Promise) {
|
|
316
|
+
val ignoredSet = ignoredInstruments?.toIgnoredInstrumentSet() ?: emptySet()
|
|
317
|
+
val tracer = CoralogixRum.getCustomTracer(ignoredSet)
|
|
318
|
+
if (tracer == null) {
|
|
319
|
+
promise.resolve(null)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
val globalSpan = tracer.startGlobalSpan(name, labels?.toStringAnyMap())
|
|
323
|
+
if (globalSpan == null) {
|
|
324
|
+
promise.resolve(null)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
globalSpans[globalSpan.spanId] = globalSpan
|
|
328
|
+
val result = Arguments.createMap()
|
|
329
|
+
result.putString("spanId", globalSpan.spanId)
|
|
330
|
+
result.putString("traceId", globalSpan.traceId)
|
|
331
|
+
promise.resolve(result)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@ReactMethod
|
|
335
|
+
override fun startCustomSpan(parentSpanId: String, name: String, labels: ReadableMap?, promise: Promise) {
|
|
336
|
+
val globalSpan = globalSpans[parentSpanId]
|
|
337
|
+
if (globalSpan == null) {
|
|
338
|
+
promise.resolve(null)
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
val customSpan = globalSpan.startCustomSpan(name, labels?.toStringAnyMap())
|
|
342
|
+
val otelIds = extractOtelIds(customSpan)
|
|
343
|
+
if (otelIds == null) {
|
|
344
|
+
Log.e(NAME, "startCustomSpan: failed to extract OTel IDs via reflection — resolving null")
|
|
345
|
+
promise.resolve(null)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
customSpans[otelIds.first] = customSpan
|
|
349
|
+
val result = Arguments.createMap()
|
|
350
|
+
result.putString("spanId", otelIds.first)
|
|
351
|
+
result.putString("traceId", otelIds.second)
|
|
352
|
+
promise.resolve(result)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@ReactMethod
|
|
357
|
+
override fun endSpan(spanId: String, promise: Promise) {
|
|
358
|
+
globalSpans.remove(spanId)?.let {
|
|
359
|
+
it.endSpan()
|
|
360
|
+
promise.resolve(null)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
customSpans.remove(spanId)?.let {
|
|
364
|
+
it.endSpan()
|
|
365
|
+
promise.resolve(null)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
Log.w(NAME, "endSpan: unknown spanId $spanId")
|
|
369
|
+
promise.resolve(null)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// endregion
|
|
373
|
+
|
|
293
374
|
// region - utils
|
|
294
375
|
override fun invalidate() {
|
|
295
376
|
super.invalidate()
|
|
296
|
-
|
|
377
|
+
globalSpans.clear()
|
|
378
|
+
customSpans.clear()
|
|
297
379
|
Handler(Looper.getMainLooper()).post {
|
|
298
380
|
Log.d("CxSdkModule", "Bridge destroyed — shutting down CoralogixRum")
|
|
299
381
|
CoralogixRum.shutdown()
|
|
@@ -354,6 +436,13 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
354
436
|
getArray("networkExtraConfig")?.toNetworkCaptureRuleList() ?: emptyList()
|
|
355
437
|
else emptyList()
|
|
356
438
|
|
|
439
|
+
val excludeFromSampling = if (hasKey("excludeFromSampling") && !isNull("excludeFromSampling"))
|
|
440
|
+
getArray("excludeFromSampling")?.toExcludeFromSamplingList() ?: emptyList()
|
|
441
|
+
else emptyList()
|
|
442
|
+
|
|
443
|
+
val sessionSampleRate =
|
|
444
|
+
if (hasKey("sessionSampleRate") && !isNull("sessionSampleRate")) getInt("sessionSampleRate") else 100
|
|
445
|
+
|
|
357
446
|
return CoralogixOptions(
|
|
358
447
|
applicationName = applicationName,
|
|
359
448
|
coralogixDomain = coralogixDomain,
|
|
@@ -367,13 +456,23 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
367
456
|
mobileVitalsOptions = getMap("mobileVitals")?.toMobileVitalsOptions() ?: mapOf(),
|
|
368
457
|
ignoreUrls = getArray("ignoreUrls")?.handleStringOrRegexList() ?: listOf(),
|
|
369
458
|
ignoreErrors = getArray("ignoreErrors")?.handleStringOrRegexList() ?: listOf(),
|
|
370
|
-
sessionSampleRate =
|
|
459
|
+
sessionSampleRate = sessionSampleRate,
|
|
460
|
+
excludeFromSampling = excludeFromSampling,
|
|
371
461
|
traceParentInHeader = traceParentInHeaderConfig,
|
|
372
462
|
debug = if (hasKey("debug")) getBoolean("debug") else false,
|
|
373
463
|
proxyUrl = getString("proxyUrl"),
|
|
374
464
|
beforeSendCallback = if (hasKey("hasBeforeSend") && getBoolean("hasBeforeSend")) ::beforeSendCallback else null,
|
|
375
465
|
collectIPData = collectIpData,
|
|
376
|
-
networkCaptureConfig = networkExtraConfig
|
|
466
|
+
networkCaptureConfig = networkExtraConfig,
|
|
467
|
+
tracesExporter = if (hasKey("hasTracesExporter") && getBoolean("hasTracesExporter")) {
|
|
468
|
+
{ data: CoralogixTraceExporterData ->
|
|
469
|
+
if (reactApplicationContext.hasActiveReactInstance()) {
|
|
470
|
+
reactApplicationContext
|
|
471
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
472
|
+
.emit("onTracesExport", data.toJson())
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} else null
|
|
377
476
|
)
|
|
378
477
|
}
|
|
379
478
|
|
|
@@ -523,8 +622,9 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
523
622
|
"AP1" -> CoralogixDomain.AP1
|
|
524
623
|
"AP2" -> CoralogixDomain.AP2
|
|
525
624
|
"AP3" -> CoralogixDomain.AP3
|
|
625
|
+
"US3" -> CoralogixDomain.US3
|
|
526
626
|
"STAGING" -> CoralogixDomain.STAGING
|
|
527
|
-
else -> throw IllegalArgumentException("Invalid coralogixDomain: $this. Must be one of [EU1, EU2, US1, US2, AP1, AP2, AP3, STAGING]")
|
|
627
|
+
else -> throw IllegalArgumentException("Invalid coralogixDomain: $this. Must be one of [EU1, EU2, US1, US2, US3, AP1, AP2, AP3, STAGING]")
|
|
528
628
|
}
|
|
529
629
|
}
|
|
530
630
|
|
|
@@ -758,6 +858,36 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
758
858
|
return result
|
|
759
859
|
}
|
|
760
860
|
|
|
861
|
+
private fun ReadableArray.toIgnoredInstrumentSet(): Set<CoralogixIgnoredInstrument> {
|
|
862
|
+
val set = mutableSetOf<CoralogixIgnoredInstrument>()
|
|
863
|
+
for (i in 0 until size()) {
|
|
864
|
+
when (getString(i)) {
|
|
865
|
+
"networkRequests" -> set.add(CoralogixIgnoredInstrument.NETWORK_REQUESTS)
|
|
866
|
+
"userInteractions" -> set.add(CoralogixIgnoredInstrument.USER_INTERACTIONS)
|
|
867
|
+
"errors" -> set.add(CoralogixIgnoredInstrument.ERRORS)
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return set
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private fun ReadableArray.toExcludeFromSamplingList(): List<ExcludableInstrumentation> {
|
|
874
|
+
val set = LinkedHashSet<ExcludableInstrumentation>()
|
|
875
|
+
for (i in 0 until size()) {
|
|
876
|
+
val value = getString(i)
|
|
877
|
+
when (value) {
|
|
878
|
+
"errors" -> set.add(ExcludableInstrumentation.Errors)
|
|
879
|
+
"logs" -> set.add(ExcludableInstrumentation.Logs)
|
|
880
|
+
"network" -> set.add(ExcludableInstrumentation.Network)
|
|
881
|
+
"userInteractions" -> set.add(ExcludableInstrumentation.UserInteractions)
|
|
882
|
+
"mobileVitals" -> set.add(ExcludableInstrumentation.MobileVitals)
|
|
883
|
+
"customSpan" -> set.add(ExcludableInstrumentation.CustomSpan)
|
|
884
|
+
"customMeasurement" -> set.add(ExcludableInstrumentation.CustomMeasurement)
|
|
885
|
+
else -> Log.w("CxSdkModule", "excludeFromSampling: unrecognized value '$value' — native enum may have drifted")
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return set.toList()
|
|
889
|
+
}
|
|
890
|
+
|
|
761
891
|
private fun ReadableArray.toHybridMetricList(): List<HybridMetric> {
|
|
762
892
|
val out = ArrayList<HybridMetric>(size())
|
|
763
893
|
for (i in 0 until size()) {
|
|
@@ -778,5 +908,22 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
778
908
|
|
|
779
909
|
companion object {
|
|
780
910
|
const val NAME = "CxSdk"
|
|
911
|
+
|
|
912
|
+
// TODO: replace reflection with a public accessor once the Coralogix Android SDK exposes one —
|
|
913
|
+
// 'getSpan$library_release' is a Kotlin-internal name that may be renamed in a future SDK version.
|
|
914
|
+
internal fun extractOtelIds(child: CoralogixCustomSpan): Pair<String, String>? =
|
|
915
|
+
extractOtelIds(child, "getSpan\$library_release")
|
|
916
|
+
|
|
917
|
+
internal fun extractOtelIds(child: CoralogixCustomSpan, accessorName: String): Pair<String, String>? = try {
|
|
918
|
+
val span = child.javaClass.getMethod(accessorName).invoke(child) ?: return null
|
|
919
|
+
val ctx = span.javaClass.getMethod("getSpanContext").invoke(span) ?: return null
|
|
920
|
+
val spanId = ctx.javaClass.getMethod("getSpanId").invoke(ctx) as? String
|
|
921
|
+
val traceId = ctx.javaClass.getMethod("getTraceId").invoke(ctx) as? String
|
|
922
|
+
if (spanId.isNullOrEmpty() || traceId.isNullOrEmpty()) null
|
|
923
|
+
else Pair(spanId, traceId)
|
|
924
|
+
} catch (e: Throwable) {
|
|
925
|
+
Log.w(NAME, "extractOtelIds: reflection failed — $e")
|
|
926
|
+
null
|
|
927
|
+
}
|
|
781
928
|
}
|
|
782
929
|
}
|
|
@@ -16,10 +16,17 @@ interface RUMClient {
|
|
|
16
16
|
fun log(severity: Int, message: String, data: ReadableMap, labels: ReadableMap)
|
|
17
17
|
fun reportNetworkRequest(requestDetails: ReadableMap)
|
|
18
18
|
fun sendCustomMeasurement(measurement: ReadableMap)
|
|
19
|
+
fun startTimeMeasure(name: String, labels: ReadableMap?)
|
|
20
|
+
fun endTimeMeasure(name: String)
|
|
19
21
|
fun reportError(details: ReadableMap)
|
|
20
22
|
fun sendCxSpanData(results: ReadableArray)
|
|
21
23
|
fun reportMobileVitalsMeasurement(type: String, value: Double, units: String)
|
|
22
24
|
fun reportMobileVitalsMeasurementSet(type: String, metrics: ReadableArray)
|
|
23
25
|
fun isCoralogixGradlePluginApplied(promise: Promise)
|
|
24
26
|
fun shutdown(promise: Promise)
|
|
27
|
+
|
|
28
|
+
// custom spans
|
|
29
|
+
fun startGlobalSpan(name: String, labels: ReadableMap?, ignoredInstruments: ReadableArray?, promise: Promise)
|
|
30
|
+
fun startCustomSpan(parentSpanId: String, name: String, labels: ReadableMap?, promise: Promise)
|
|
31
|
+
fun endSpan(spanId: String, promise: Promise)
|
|
25
32
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package com.cxsdk
|
|
2
|
+
|
|
3
|
+
import com.coralogix.android.sdk.customspans.CoralogixCustomSpan
|
|
4
|
+
import io.opentelemetry.api.trace.Span
|
|
5
|
+
import io.opentelemetry.api.trace.SpanContext
|
|
6
|
+
import io.opentelemetry.api.trace.TraceFlags
|
|
7
|
+
import io.opentelemetry.api.trace.TraceState
|
|
8
|
+
import org.mockito.Mockito.mock
|
|
9
|
+
import org.mockito.Mockito.`when`
|
|
10
|
+
import kotlin.test.Test
|
|
11
|
+
import kotlin.test.assertEquals
|
|
12
|
+
import kotlin.test.assertNull
|
|
13
|
+
|
|
14
|
+
class ExtractOtelIdsTest {
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun `extractOtelIds returns correct spanId and traceId via reflection`() {
|
|
18
|
+
val spanId = "1234567890abcdef"
|
|
19
|
+
val traceId = "abcdef1234567890abcdef1234567890"
|
|
20
|
+
|
|
21
|
+
val mockSpan = mock(Span::class.java)
|
|
22
|
+
`when`(mockSpan.spanContext).thenReturn(
|
|
23
|
+
SpanContext.create(traceId, spanId, TraceFlags.getDefault(), TraceState.getDefault())
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
val result = CxSdkModule.extractOtelIds(CoralogixCustomSpan(mockSpan))
|
|
27
|
+
|
|
28
|
+
assertEquals(Pair(spanId, traceId), result)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun `extractOtelIds returns null when spanContext returns null`() {
|
|
33
|
+
val mockSpan = mock(Span::class.java)
|
|
34
|
+
`when`(mockSpan.spanContext).thenReturn(null)
|
|
35
|
+
|
|
36
|
+
val result = CxSdkModule.extractOtelIds(CoralogixCustomSpan(mockSpan))
|
|
37
|
+
|
|
38
|
+
assertNull(result)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test
|
|
42
|
+
fun `extractOtelIds returns null when accessor method does not exist`() {
|
|
43
|
+
val mockSpan = mock(Span::class.java)
|
|
44
|
+
|
|
45
|
+
val result = CxSdkModule.extractOtelIds(CoralogixCustomSpan(mockSpan), "nonExistentMethod")
|
|
46
|
+
|
|
47
|
+
assertNull(result)
|
|
48
|
+
}
|
|
49
|
+
}
|