@coralogix/react-native-plugin 0.10.0 → 0.10.2
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 +19 -0
- package/CxSdk.podspec +3 -3
- package/README.md +27 -8
- package/android/src/main/java/com/cxsdk/CxSdkModule.kt +3 -1
- package/index.cjs.js +73 -8
- package/index.esm.js +73 -8
- package/ios/CxSdk.swift +7 -3
- package/package.json +1 -1
- package/src/model/NetworkRequestDetails.d.ts +5 -0
- package/src/model/Types.d.ts +11 -1
- package/src/utils.d.ts +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.10.2 (2026-08-25)
|
|
2
|
+
|
|
3
|
+
### 🩹 Fixes
|
|
4
|
+
|
|
5
|
+
- `sessionSampleRate` is now applied once instead of twice — the configured percentage was being rolled on both the JavaScript and native sides, so a rate of 1% collected roughly 0.01% of sessions
|
|
6
|
+
- A fractional `sessionSampleRate` such as `0.5` is now rounded to a whole percentage instead of being truncated to `0` on iOS, which silently sampled out every session
|
|
7
|
+
- `SessionReplay.init()` now resolves `false` instead of rejecting when the session was sampled out, and resolves a real boolean on both platforms
|
|
8
|
+
|
|
9
|
+
## 0.10.1 (2026-08-11)
|
|
10
|
+
|
|
11
|
+
### 🩹 Fixes
|
|
12
|
+
|
|
13
|
+
- Network request events on iOS now report the real request duration, response body size, and status text — the duration value was previously written into the response-size field and never reached the SDK, and status text was not forwarded at all
|
|
14
|
+
- Network request events no longer carry an error-message field on any platform, matching the event schema — error details are reported through error events only
|
|
15
|
+
|
|
16
|
+
### ⬆️ Dependency Updates
|
|
17
|
+
|
|
18
|
+
- Native iOS SDK bumped to **2.15.1** — network events reported from the JavaScript layer now honor the measured request duration and status text
|
|
19
|
+
|
|
1
20
|
## 0.10.0 (2026-08-10)
|
|
2
21
|
|
|
3
22
|
### ⬆️ Dependency Updates
|
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.
|
|
20
|
-
s.dependency 'CoralogixInternal','2.
|
|
21
|
-
s.dependency 'SessionReplay','2.
|
|
19
|
+
s.dependency 'Coralogix','2.15.1'
|
|
20
|
+
s.dependency 'CoralogixInternal','2.15.1'
|
|
21
|
+
s.dependency 'SessionReplay','2.15.1'
|
|
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
|
@@ -12,7 +12,7 @@ This package replaces the old `@coralogix/react-native-sdk` package.
|
|
|
12
12
|
|
|
13
13
|
To use Coralogix SDK, call `CoralogixRum.init(options)` at the soonest available moment after the app loads.
|
|
14
14
|
This will initialize the SDK based on the options you provided.
|
|
15
|
-
|
|
15
|
+
`init` is an `async` function.
|
|
16
16
|
|
|
17
17
|
```javascript
|
|
18
18
|
import { CoralogixRum } from '@coralogix/react-native-plugin'
|
|
@@ -32,6 +32,11 @@ await CoralogixRum.init({
|
|
|
32
32
|
});
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
`sessionSampleRate` is a whole percentage in `0`-`100` and is applied **once**. A
|
|
36
|
+
fractional value is rounded to an integer before it is forwarded (and a non-zero
|
|
37
|
+
rate is never rounded down to `0`), because the native bridges read the value as
|
|
38
|
+
an integer.
|
|
39
|
+
|
|
35
40
|
To provide contextual information or transmit manual logs, utilize the exported functions of `CoralogixRum`.
|
|
36
41
|
Keep in mind that these functions will remain inactive until you've invoked `CoralogixRum.init()`.
|
|
37
42
|
|
|
@@ -67,6 +72,7 @@ CoralogixRum.log(CoralogixLogSeverity.Error, 'this is a log', { key: 'value' });
|
|
|
67
72
|
CoralogixRum.error('this is a log with error severity', { key: 'value' });
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
<!-- split title="Logs, errors, and views" path="features/logs-errors-and-views.md" -->
|
|
70
76
|
### Custom Logs
|
|
71
77
|
|
|
72
78
|
Send structured logs with optional data and labels.
|
|
@@ -80,9 +86,10 @@ CoralogixRum.debug('this is a debug log', {key: 'value', pi: 3.14});
|
|
|
80
86
|
CoralogixRum.error('this is an error log', {error: 'yes', is_bad: 'no'});
|
|
81
87
|
```
|
|
82
88
|
|
|
83
|
-
>
|
|
84
|
-
>
|
|
85
|
-
>
|
|
89
|
+
> [!NOTE]
|
|
90
|
+
> Due to React Native limitations, not all value types are supported for the log data values.
|
|
91
|
+
> Our testing shows that React Native can't pass some types to the coralogix native layer; the ones we have identified as problematic are `Map`, `Set`, `Date` and `Function`.
|
|
92
|
+
> There could be more. For example:
|
|
86
93
|
> ```javascript
|
|
87
94
|
> // this would pass as an empty object to the coralogix native layer, same for Set and Date:
|
|
88
95
|
> CoralogixRum.debug('this will be a problem', {key: new Map().set(1, 2)});
|
|
@@ -90,7 +97,7 @@ CoralogixRum.error('this is an error log', {error: 'yes', is_bad: 'no'});
|
|
|
90
97
|
> // this would pass as null to the coralogix native layer
|
|
91
98
|
> CoralogixRum.debug('this will be a problem too', {key: () => {}});
|
|
92
99
|
> ```
|
|
93
|
-
>
|
|
100
|
+
> If you need to pass any of these types, stringify them before you pass them to the `log` method.
|
|
94
101
|
|
|
95
102
|
### Error Reporting
|
|
96
103
|
|
|
@@ -193,6 +200,8 @@ subsequent events belong to a fresh session with a new session id.
|
|
|
193
200
|
CoralogixRum.createNewSession();
|
|
194
201
|
```
|
|
195
202
|
|
|
203
|
+
<!-- /split -->
|
|
204
|
+
<!-- split title="Configuration options" path="configuration/options.md" -->
|
|
196
205
|
### Instrumentation's
|
|
197
206
|
|
|
198
207
|
Turn on/off specific instrumentation; defaults to true for all.
|
|
@@ -282,6 +291,8 @@ await CoralogixRum.init({
|
|
|
282
291
|
});
|
|
283
292
|
```
|
|
284
293
|
|
|
294
|
+
<!-- /split -->
|
|
295
|
+
<!-- split title="Custom instrumentation" path="features/custom-instrumentation.md" -->
|
|
285
296
|
### Custom Spans
|
|
286
297
|
|
|
287
298
|
Create manual RUM spans to instrument custom flows in your application. Requires `traceParentInHeader.enabled: true`.
|
|
@@ -331,7 +342,8 @@ Pass instrument names to prevent network requests, errors, or interactions fired
|
|
|
331
342
|
const tracer = CoralogixRum.getCustomTracer(['networkRequests', 'userInteractions', 'errors']);
|
|
332
343
|
```
|
|
333
344
|
|
|
334
|
-
>
|
|
345
|
+
> [!NOTE]
|
|
346
|
+
> Only one global span may be active at a time. `startGlobalSpan` returns `null` if a global span is already open.
|
|
335
347
|
|
|
336
348
|
### Time Measurement
|
|
337
349
|
|
|
@@ -357,6 +369,8 @@ CoralogixRum.endTimeMeasure('checkout');
|
|
|
357
369
|
- Unmatched `endTimeMeasure` calls are dropped silently by the native SDK.
|
|
358
370
|
- Calling `startTimeMeasure` again with an open `name` overwrites the previous start.
|
|
359
371
|
|
|
372
|
+
<!-- /split -->
|
|
373
|
+
<!-- split title="Exporting and networking" path="configuration/exporting-and-networking.md" -->
|
|
360
374
|
### Traces Exporter
|
|
361
375
|
|
|
362
376
|
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.
|
|
@@ -400,7 +414,8 @@ The callback receives a `TraceExporterData` object following the OTLP JSON forma
|
|
|
400
414
|
}
|
|
401
415
|
```
|
|
402
416
|
|
|
403
|
-
>
|
|
417
|
+
> [!NOTE]
|
|
418
|
+
> The callback fires once per native export batch, which typically contains several spans rather than one per span.
|
|
404
419
|
|
|
405
420
|
### beforeSend
|
|
406
421
|
|
|
@@ -454,7 +469,8 @@ await CoralogixRum.init({
|
|
|
454
469
|
});
|
|
455
470
|
```
|
|
456
471
|
|
|
457
|
-
>
|
|
472
|
+
> [!NOTE]
|
|
473
|
+
> Always read it as `isSessionSampledIn ?? true`. The field is absent on
|
|
458
474
|
> native SDKs that predate it, and the SDK never invents a value it was not given.
|
|
459
475
|
|
|
460
476
|
`isSessionSampledIn` is read-only: it reports the SDK's sampling decision for the
|
|
@@ -515,6 +531,8 @@ await CoralogixRum.init({
|
|
|
515
531
|
});
|
|
516
532
|
```
|
|
517
533
|
|
|
534
|
+
<!-- /split -->
|
|
535
|
+
<!-- split title="Session Replay" path="features/session-replay.md" -->
|
|
518
536
|
### Session Replay
|
|
519
537
|
|
|
520
538
|
Session Replay allows you to record and replay user sessions to understand user behavior and debug issues.
|
|
@@ -616,6 +634,7 @@ import { SessionReplay } from '@coralogix/react-native-plugin';
|
|
|
616
634
|
The `SessionReplay.maskView` function accepts a `LayoutChangeEvent` and will mask the view in session replay recordings. It works on both the legacy and the New Architecture (Fabric).
|
|
617
635
|
|
|
618
636
|
|
|
637
|
+
<!-- /split -->
|
|
619
638
|
### Optional - Coralogix Gradle Plugin (Android)
|
|
620
639
|
|
|
621
640
|
The Coralogix Gradle Plugin automatically instruments all OkHttp clients in your app (including third-party SDKs) at build time.
|
|
@@ -856,7 +856,9 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
|
|
|
856
856
|
duration = if (hasKey("duration")) getDouble("duration").toLong() else 0,
|
|
857
857
|
responseContentLength = if (hasKey("responseContentLength"))
|
|
858
858
|
getDouble("responseContentLength").toLong() else 0,
|
|
859
|
-
|
|
859
|
+
// Intentionally not read from the bridge: the network event schema has no
|
|
860
|
+
// error_message field — error details belong to error events only.
|
|
861
|
+
errorMessage = null,
|
|
860
862
|
traceId = getString("customTraceId"),
|
|
861
863
|
spanId = getString("customSpanId"),
|
|
862
864
|
requestHeaders = if (hasKey("request_headers") && !isNull("request_headers"))
|
package/index.cjs.js
CHANGED
|
@@ -577,7 +577,7 @@ function stopJsRefreshRateSampler() {
|
|
|
577
577
|
appStateSub = null;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
-
var version = "0.10.
|
|
580
|
+
var version = "0.10.2";
|
|
581
581
|
var pkg = {
|
|
582
582
|
version: version};
|
|
583
583
|
|
|
@@ -766,9 +766,43 @@ const getUrlFragments = url => {
|
|
|
766
766
|
return url;
|
|
767
767
|
}
|
|
768
768
|
};
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Rolls the session sampling dice.
|
|
772
|
+
*
|
|
773
|
+
* This is the only roll for a config without `excludeFromSampling`: `init` pins the
|
|
774
|
+
* rate it sends to native to 100 in that case, so native does not roll again. See the
|
|
775
|
+
* comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
|
|
776
|
+
*/
|
|
769
777
|
function isSamplingOn(threshold) {
|
|
770
778
|
return threshold > 0 && Math.random() * 100 <= threshold;
|
|
771
779
|
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
|
|
783
|
+
* integer percentage in [0, 100].
|
|
784
|
+
*
|
|
785
|
+
* The bridges read the value as an integer (`NSNumber.intValue` on iOS,
|
|
786
|
+
* `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
|
|
787
|
+
* truncated to `0` on iOS — silently sampling every session out — and can throw
|
|
788
|
+
* on Android. Round instead, and never round a caller-requested non-zero rate
|
|
789
|
+
* down to `0`: `0` is the one value that means "emit nothing".
|
|
790
|
+
*/
|
|
791
|
+
function normalizeSessionSampleRate(rate) {
|
|
792
|
+
if (typeof rate !== 'number' || !Number.isFinite(rate)) {
|
|
793
|
+
logger.warn(`CoralogixRum: \`sessionSampleRate\` must be a finite number; received ${String(rate)}. Falling back to 100.`);
|
|
794
|
+
return 100;
|
|
795
|
+
}
|
|
796
|
+
if (rate <= 0) {
|
|
797
|
+
return 0;
|
|
798
|
+
}
|
|
799
|
+
if (rate >= 100) {
|
|
800
|
+
return 100;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Keep a sub-1% request alive rather than truncating it to "emit nothing".
|
|
804
|
+
return Math.max(1, Math.round(rate));
|
|
805
|
+
}
|
|
772
806
|
function resolveCoralogixOtelWebConfig(config) {
|
|
773
807
|
var _config$sessionSample, _config$view_context;
|
|
774
808
|
const {
|
|
@@ -780,7 +814,7 @@ function resolveCoralogixOtelWebConfig(config) {
|
|
|
780
814
|
OPTIONS_DEFAULTS.ignoreUrls && resolvedIgnoreUrls.push(...OPTIONS_DEFAULTS.ignoreUrls);
|
|
781
815
|
return _extends({}, OPTIONS_DEFAULTS, config, {
|
|
782
816
|
ignoreUrls: resolvedIgnoreUrls,
|
|
783
|
-
sessionSampleRate: (_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100,
|
|
817
|
+
sessionSampleRate: normalizeSessionSampleRate((_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100),
|
|
784
818
|
debug,
|
|
785
819
|
view_context: (_config$view_context = config.view_context) != null ? _config$view_context : OPTIONS_DEFAULTS.view_context
|
|
786
820
|
});
|
|
@@ -870,7 +904,6 @@ class CoralogixFetchInstrumentation extends instrumentationFetch.FetchInstrument
|
|
|
870
904
|
const responseContentLength = attrs[OtelNetworkAttrs.RESPONSE_CONTENT_LENGTH] || 0;
|
|
871
905
|
const duration = hrTimeToMilliseconds(readableSpan.duration);
|
|
872
906
|
const fragments = getUrlFragments(url);
|
|
873
|
-
const errorMessage = result.message;
|
|
874
907
|
const customTraceId = readableSpan.spanContext().traceId;
|
|
875
908
|
const customSpanId = readableSpan.spanContext().spanId;
|
|
876
909
|
const parsed = new URL(url);
|
|
@@ -887,7 +920,6 @@ class CoralogixFetchInstrumentation extends instrumentationFetch.FetchInstrument
|
|
|
887
920
|
statusText,
|
|
888
921
|
duration,
|
|
889
922
|
responseContentLength,
|
|
890
|
-
errorMessage,
|
|
891
923
|
customTraceId,
|
|
892
924
|
customSpanId
|
|
893
925
|
};
|
|
@@ -1035,6 +1067,9 @@ function createErrorProxy(errorMessage) {
|
|
|
1035
1067
|
});
|
|
1036
1068
|
}
|
|
1037
1069
|
let isInited = false;
|
|
1070
|
+
// Whether native, rather than JS, rolled the sampling for this init. Only in that mode can
|
|
1071
|
+
// `CoralogixRum.isInited` be true while native has no RUM behind it.
|
|
1072
|
+
let nativeOwnsSamplingRoll = false;
|
|
1038
1073
|
let fetchInstrumentation;
|
|
1039
1074
|
function toSafeRecord(name, value) {
|
|
1040
1075
|
if (value == null) {
|
|
@@ -1051,7 +1086,7 @@ const CoralogixRum = {
|
|
|
1051
1086
|
return isInited;
|
|
1052
1087
|
},
|
|
1053
1088
|
init: async function (options) {
|
|
1054
|
-
var
|
|
1089
|
+
var _options$debug, _resolvedOptions$excl, _resolvedOptions$excl2;
|
|
1055
1090
|
if (isInited) {
|
|
1056
1091
|
console.warn('[Coralogix React Native SDK] - already initialized');
|
|
1057
1092
|
return;
|
|
@@ -1060,13 +1095,28 @@ const CoralogixRum = {
|
|
|
1060
1095
|
console.warn('[Coralogix React Native SDK] - `public_key` is required and must be a non-empty string; initialization aborted.');
|
|
1061
1096
|
return;
|
|
1062
1097
|
}
|
|
1098
|
+
|
|
1099
|
+
// Configure the logger first: resolving the options normalizes `sessionSampleRate`,
|
|
1100
|
+
// which warns about an invalid rate. Doing it the other way round swallowed that
|
|
1101
|
+
// warning on the first init — exactly when the caller needs to see it.
|
|
1102
|
+
logger.shouldLog = (_options$debug = options.debug) != null ? _options$debug : false;
|
|
1063
1103
|
const resolvedOptions = resolveCoralogixOtelWebConfig(options);
|
|
1064
|
-
|
|
1104
|
+
|
|
1105
|
+
// The rate has to be applied exactly *once*. Rolling it on both sides squared it:
|
|
1106
|
+
// `sessionSampleRate: 1` kept ~0.01% of sessions instead of 1%.
|
|
1107
|
+
//
|
|
1108
|
+
// Without `excludeFromSampling`, JS rolls and the rate sent to native is pinned to 100
|
|
1109
|
+
// so native does not roll again; a losing roll returns here, which also keeps JS
|
|
1110
|
+
// instrumentation off a session that would emit nothing. With `excludeFromSampling`,
|
|
1111
|
+
// only native can honour "sampled out, but still emit these categories", so JS does not
|
|
1112
|
+
// roll and forwards the real rate — pinning to 100 there would sample every session in.
|
|
1065
1113
|
const hasExcludeOverrides = ((_resolvedOptions$excl = (_resolvedOptions$excl2 = resolvedOptions.excludeFromSampling) == null ? void 0 : _resolvedOptions$excl2.length) != null ? _resolvedOptions$excl : 0) > 0;
|
|
1066
|
-
if (!isSamplingOn(resolvedOptions.sessionSampleRate)
|
|
1114
|
+
if (!hasExcludeOverrides && !isSamplingOn(resolvedOptions.sessionSampleRate)) {
|
|
1067
1115
|
logger.debug('CoralogixRum: Session tracking is disabled');
|
|
1068
1116
|
return;
|
|
1069
1117
|
}
|
|
1118
|
+
nativeOwnsSamplingRoll = hasExcludeOverrides;
|
|
1119
|
+
const nativeSessionSampleRate = hasExcludeOverrides ? resolvedOptions.sessionSampleRate : 100;
|
|
1070
1120
|
await registerCoralogixInstrumentations(resolvedOptions);
|
|
1071
1121
|
let finalOptions;
|
|
1072
1122
|
const lastNavRoute = getLastNavigationRouteDetected();
|
|
@@ -1097,6 +1147,7 @@ const CoralogixRum = {
|
|
|
1097
1147
|
});
|
|
1098
1148
|
}
|
|
1099
1149
|
await CxSdk.initialize(_extends({}, finalOptions, {
|
|
1150
|
+
sessionSampleRate: nativeSessionSampleRate,
|
|
1100
1151
|
frameworkVersion: pkg.version,
|
|
1101
1152
|
networkExtraConfig: finalOptions.networkExtraConfig ? serializeNetworkCaptureRules(finalOptions.networkExtraConfig) : undefined,
|
|
1102
1153
|
hasBeforeSend: !!finalOptions.beforeSend,
|
|
@@ -1282,7 +1333,21 @@ const SessionReplay = {
|
|
|
1282
1333
|
logger.warn("invalid options in SessionReplay.init: ", options);
|
|
1283
1334
|
return false;
|
|
1284
1335
|
}
|
|
1285
|
-
|
|
1336
|
+
try {
|
|
1337
|
+
// iOS resolves the string "initializeSessionReplay success" rather than a boolean,
|
|
1338
|
+
// so coerce instead of handing a truthy string back through a `Promise<boolean>`.
|
|
1339
|
+
return !!(await CxSdk.initializeSessionReplay(options));
|
|
1340
|
+
} catch (e) {
|
|
1341
|
+
// When native owns the sampling roll, a sampled-out session leaves no native RUM
|
|
1342
|
+
// behind and iOS rejects rather than resolving. That is an expected outcome, not a
|
|
1343
|
+
// failure, so report it as `false` like the guards above rather than surfacing an
|
|
1344
|
+
// unhandled rejection. Every other rejection is a real error and stays the caller's.
|
|
1345
|
+
if (!nativeOwnsSamplingRoll) {
|
|
1346
|
+
throw e;
|
|
1347
|
+
}
|
|
1348
|
+
logger.warn('SessionReplay.init: no native RUM for this session (sampled out); replay not started.', e);
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1286
1351
|
},
|
|
1287
1352
|
shutdown: async () => {
|
|
1288
1353
|
logger.debug("session replay: shutdown called");
|
package/index.esm.js
CHANGED
|
@@ -575,7 +575,7 @@ function stopJsRefreshRateSampler() {
|
|
|
575
575
|
appStateSub = null;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
var version = "0.10.
|
|
578
|
+
var version = "0.10.2";
|
|
579
579
|
var pkg = {
|
|
580
580
|
version: version};
|
|
581
581
|
|
|
@@ -764,9 +764,43 @@ const getUrlFragments = url => {
|
|
|
764
764
|
return url;
|
|
765
765
|
}
|
|
766
766
|
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Rolls the session sampling dice.
|
|
770
|
+
*
|
|
771
|
+
* This is the only roll for a config without `excludeFromSampling`: `init` pins the
|
|
772
|
+
* rate it sends to native to 100 in that case, so native does not roll again. See the
|
|
773
|
+
* comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
|
|
774
|
+
*/
|
|
767
775
|
function isSamplingOn(threshold) {
|
|
768
776
|
return threshold > 0 && Math.random() * 100 <= threshold;
|
|
769
777
|
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
|
|
781
|
+
* integer percentage in [0, 100].
|
|
782
|
+
*
|
|
783
|
+
* The bridges read the value as an integer (`NSNumber.intValue` on iOS,
|
|
784
|
+
* `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
|
|
785
|
+
* truncated to `0` on iOS — silently sampling every session out — and can throw
|
|
786
|
+
* on Android. Round instead, and never round a caller-requested non-zero rate
|
|
787
|
+
* down to `0`: `0` is the one value that means "emit nothing".
|
|
788
|
+
*/
|
|
789
|
+
function normalizeSessionSampleRate(rate) {
|
|
790
|
+
if (typeof rate !== 'number' || !Number.isFinite(rate)) {
|
|
791
|
+
logger.warn(`CoralogixRum: \`sessionSampleRate\` must be a finite number; received ${String(rate)}. Falling back to 100.`);
|
|
792
|
+
return 100;
|
|
793
|
+
}
|
|
794
|
+
if (rate <= 0) {
|
|
795
|
+
return 0;
|
|
796
|
+
}
|
|
797
|
+
if (rate >= 100) {
|
|
798
|
+
return 100;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Keep a sub-1% request alive rather than truncating it to "emit nothing".
|
|
802
|
+
return Math.max(1, Math.round(rate));
|
|
803
|
+
}
|
|
770
804
|
function resolveCoralogixOtelWebConfig(config) {
|
|
771
805
|
var _config$sessionSample, _config$view_context;
|
|
772
806
|
const {
|
|
@@ -778,7 +812,7 @@ function resolveCoralogixOtelWebConfig(config) {
|
|
|
778
812
|
OPTIONS_DEFAULTS.ignoreUrls && resolvedIgnoreUrls.push(...OPTIONS_DEFAULTS.ignoreUrls);
|
|
779
813
|
return _extends({}, OPTIONS_DEFAULTS, config, {
|
|
780
814
|
ignoreUrls: resolvedIgnoreUrls,
|
|
781
|
-
sessionSampleRate: (_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100,
|
|
815
|
+
sessionSampleRate: normalizeSessionSampleRate((_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100),
|
|
782
816
|
debug,
|
|
783
817
|
view_context: (_config$view_context = config.view_context) != null ? _config$view_context : OPTIONS_DEFAULTS.view_context
|
|
784
818
|
});
|
|
@@ -868,7 +902,6 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
|
|
|
868
902
|
const responseContentLength = attrs[OtelNetworkAttrs.RESPONSE_CONTENT_LENGTH] || 0;
|
|
869
903
|
const duration = hrTimeToMilliseconds(readableSpan.duration);
|
|
870
904
|
const fragments = getUrlFragments(url);
|
|
871
|
-
const errorMessage = result.message;
|
|
872
905
|
const customTraceId = readableSpan.spanContext().traceId;
|
|
873
906
|
const customSpanId = readableSpan.spanContext().spanId;
|
|
874
907
|
const parsed = new URL(url);
|
|
@@ -885,7 +918,6 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
|
|
|
885
918
|
statusText,
|
|
886
919
|
duration,
|
|
887
920
|
responseContentLength,
|
|
888
|
-
errorMessage,
|
|
889
921
|
customTraceId,
|
|
890
922
|
customSpanId
|
|
891
923
|
};
|
|
@@ -1033,6 +1065,9 @@ function createErrorProxy(errorMessage) {
|
|
|
1033
1065
|
});
|
|
1034
1066
|
}
|
|
1035
1067
|
let isInited = false;
|
|
1068
|
+
// Whether native, rather than JS, rolled the sampling for this init. Only in that mode can
|
|
1069
|
+
// `CoralogixRum.isInited` be true while native has no RUM behind it.
|
|
1070
|
+
let nativeOwnsSamplingRoll = false;
|
|
1036
1071
|
let fetchInstrumentation;
|
|
1037
1072
|
function toSafeRecord(name, value) {
|
|
1038
1073
|
if (value == null) {
|
|
@@ -1049,7 +1084,7 @@ const CoralogixRum = {
|
|
|
1049
1084
|
return isInited;
|
|
1050
1085
|
},
|
|
1051
1086
|
init: async function (options) {
|
|
1052
|
-
var
|
|
1087
|
+
var _options$debug, _resolvedOptions$excl, _resolvedOptions$excl2;
|
|
1053
1088
|
if (isInited) {
|
|
1054
1089
|
console.warn('[Coralogix React Native SDK] - already initialized');
|
|
1055
1090
|
return;
|
|
@@ -1058,13 +1093,28 @@ const CoralogixRum = {
|
|
|
1058
1093
|
console.warn('[Coralogix React Native SDK] - `public_key` is required and must be a non-empty string; initialization aborted.');
|
|
1059
1094
|
return;
|
|
1060
1095
|
}
|
|
1096
|
+
|
|
1097
|
+
// Configure the logger first: resolving the options normalizes `sessionSampleRate`,
|
|
1098
|
+
// which warns about an invalid rate. Doing it the other way round swallowed that
|
|
1099
|
+
// warning on the first init — exactly when the caller needs to see it.
|
|
1100
|
+
logger.shouldLog = (_options$debug = options.debug) != null ? _options$debug : false;
|
|
1061
1101
|
const resolvedOptions = resolveCoralogixOtelWebConfig(options);
|
|
1062
|
-
|
|
1102
|
+
|
|
1103
|
+
// The rate has to be applied exactly *once*. Rolling it on both sides squared it:
|
|
1104
|
+
// `sessionSampleRate: 1` kept ~0.01% of sessions instead of 1%.
|
|
1105
|
+
//
|
|
1106
|
+
// Without `excludeFromSampling`, JS rolls and the rate sent to native is pinned to 100
|
|
1107
|
+
// so native does not roll again; a losing roll returns here, which also keeps JS
|
|
1108
|
+
// instrumentation off a session that would emit nothing. With `excludeFromSampling`,
|
|
1109
|
+
// only native can honour "sampled out, but still emit these categories", so JS does not
|
|
1110
|
+
// roll and forwards the real rate — pinning to 100 there would sample every session in.
|
|
1063
1111
|
const hasExcludeOverrides = ((_resolvedOptions$excl = (_resolvedOptions$excl2 = resolvedOptions.excludeFromSampling) == null ? void 0 : _resolvedOptions$excl2.length) != null ? _resolvedOptions$excl : 0) > 0;
|
|
1064
|
-
if (!isSamplingOn(resolvedOptions.sessionSampleRate)
|
|
1112
|
+
if (!hasExcludeOverrides && !isSamplingOn(resolvedOptions.sessionSampleRate)) {
|
|
1065
1113
|
logger.debug('CoralogixRum: Session tracking is disabled');
|
|
1066
1114
|
return;
|
|
1067
1115
|
}
|
|
1116
|
+
nativeOwnsSamplingRoll = hasExcludeOverrides;
|
|
1117
|
+
const nativeSessionSampleRate = hasExcludeOverrides ? resolvedOptions.sessionSampleRate : 100;
|
|
1068
1118
|
await registerCoralogixInstrumentations(resolvedOptions);
|
|
1069
1119
|
let finalOptions;
|
|
1070
1120
|
const lastNavRoute = getLastNavigationRouteDetected();
|
|
@@ -1095,6 +1145,7 @@ const CoralogixRum = {
|
|
|
1095
1145
|
});
|
|
1096
1146
|
}
|
|
1097
1147
|
await CxSdk.initialize(_extends({}, finalOptions, {
|
|
1148
|
+
sessionSampleRate: nativeSessionSampleRate,
|
|
1098
1149
|
frameworkVersion: pkg.version,
|
|
1099
1150
|
networkExtraConfig: finalOptions.networkExtraConfig ? serializeNetworkCaptureRules(finalOptions.networkExtraConfig) : undefined,
|
|
1100
1151
|
hasBeforeSend: !!finalOptions.beforeSend,
|
|
@@ -1280,7 +1331,21 @@ const SessionReplay = {
|
|
|
1280
1331
|
logger.warn("invalid options in SessionReplay.init: ", options);
|
|
1281
1332
|
return false;
|
|
1282
1333
|
}
|
|
1283
|
-
|
|
1334
|
+
try {
|
|
1335
|
+
// iOS resolves the string "initializeSessionReplay success" rather than a boolean,
|
|
1336
|
+
// so coerce instead of handing a truthy string back through a `Promise<boolean>`.
|
|
1337
|
+
return !!(await CxSdk.initializeSessionReplay(options));
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
// When native owns the sampling roll, a sampled-out session leaves no native RUM
|
|
1340
|
+
// behind and iOS rejects rather than resolving. That is an expected outcome, not a
|
|
1341
|
+
// failure, so report it as `false` like the guards above rather than surfacing an
|
|
1342
|
+
// unhandled rejection. Every other rejection is a real error and stays the caller's.
|
|
1343
|
+
if (!nativeOwnsSamplingRoll) {
|
|
1344
|
+
throw e;
|
|
1345
|
+
}
|
|
1346
|
+
logger.warn('SessionReplay.init: no native RUM for this session (sampled out); replay not started.', e);
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1284
1349
|
},
|
|
1285
1350
|
shutdown: async () => {
|
|
1286
1351
|
logger.debug("session replay: shutdown called");
|
package/ios/CxSdk.swift
CHANGED
|
@@ -175,7 +175,6 @@ class CxSdk: RCTEventEmitter {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
let cxNetworkDict = toCoralogixNetwork(dictionary: dictionary)
|
|
178
|
-
print("[CX DEBUG] cxNetworkDict response_payload: \( cxNetworkDict)")
|
|
179
178
|
coralogixRum?.setNetworkRequestContext(dictionary: cxNetworkDict)
|
|
180
179
|
resolve("reportNetworkRequest success")
|
|
181
180
|
}
|
|
@@ -471,12 +470,17 @@ class CxSdk: RCTEventEmitter {
|
|
|
471
470
|
result["url"] = dictionary["url"] as? String ?? ""
|
|
472
471
|
result["host"] = dictionary["host"] as? String ?? ""
|
|
473
472
|
result["method"] = dictionary["method"] as? String ?? ""
|
|
474
|
-
|
|
475
|
-
result["
|
|
473
|
+
// JS numbers cross the bridge as NSNumber doubles; coerce instead of an exact-Int cast.
|
|
474
|
+
result["status_code"] = (dictionary["statusCode"] as? NSNumber)?.intValue ?? 0
|
|
475
|
+
result["status_text"] = dictionary["statusText"] as? String ?? ""
|
|
476
|
+
result["duration"] = (dictionary["duration"] as? NSNumber)?.doubleValue ?? 0
|
|
477
|
+
result["http_response_body_size"] = (dictionary["responseContentLength"] as? NSNumber)?.intValue ?? 0
|
|
476
478
|
result["fragments"] = dictionary["fragments"] as? String ?? ""
|
|
477
479
|
result["schema"] = dictionary["schema"] as? String ?? ""
|
|
478
480
|
result["customTraceId"] = dictionary["customTraceId"] as? String ?? ""
|
|
479
481
|
result["customSpanId"] = dictionary["customSpanId"] as? String ?? ""
|
|
482
|
+
// errorMessage is intentionally not forwarded: the network event schema has no
|
|
483
|
+
// error_message field — error details belong to error events only.
|
|
480
484
|
if let v = dictionary["request_headers"] as? [String: String] { result["request_headers"] = v }
|
|
481
485
|
if let v = dictionary["response_headers"] as? [String: String] { result["response_headers"] = v }
|
|
482
486
|
if let v = dictionary["request_payload"] as? String { result["request_payload"] = v }
|
package/package.json
CHANGED
|
@@ -8,6 +8,11 @@ export type NetworkRequestDetails = {
|
|
|
8
8
|
statusText: string;
|
|
9
9
|
duration: number;
|
|
10
10
|
responseContentLength: number;
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Ignored. The network event schema has no error field — error
|
|
13
|
+
* details are reported through error events only. Kept for source
|
|
14
|
+
* compatibility; neither platform forwards it.
|
|
15
|
+
*/
|
|
11
16
|
errorMessage?: string | null;
|
|
12
17
|
customTraceId: string;
|
|
13
18
|
customSpanId: string;
|
package/src/model/Types.d.ts
CHANGED
|
@@ -260,7 +260,17 @@ export interface CoralogixBrowserSdkConfig {
|
|
|
260
260
|
fpsSamplingSeconds?: number;
|
|
261
261
|
/** Sets a value for the `environment` attribute */
|
|
262
262
|
environment?: string;
|
|
263
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Percentage of overall sessions being tracked, defaults to 100%.
|
|
265
|
+
*
|
|
266
|
+
* The rate is applied exactly once. Without `excludeFromSampling` the plugin
|
|
267
|
+
* rolls it and the native SDK is told 100; with `excludeFromSampling` set the
|
|
268
|
+
* native SDK rolls it, since only native can emit the excluded categories for a
|
|
269
|
+
* sampled-out session.
|
|
270
|
+
*
|
|
271
|
+
* Expressed as a whole percentage in [0, 100]; a fractional value is rounded
|
|
272
|
+
* (never down to 0) because the native bridges read it as an integer.
|
|
273
|
+
*/
|
|
264
274
|
sessionSampleRate?: number;
|
|
265
275
|
/**
|
|
266
276
|
* Event categories that bypass session sampling. Events of these categories are
|
package/src/utils.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { CoralogixBrowserSdkConfig } from './model/Types';
|
|
2
2
|
export declare const hrTimeToMilliseconds: ([seconds, nanoseconds]: [number, number]) => number;
|
|
3
3
|
export declare const getUrlFragments: (url: string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Rolls the session sampling dice.
|
|
6
|
+
*
|
|
7
|
+
* This is the only roll for a config without `excludeFromSampling`: `init` pins the
|
|
8
|
+
* rate it sends to native to 100 in that case, so native does not roll again. See the
|
|
9
|
+
* comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
|
|
10
|
+
*/
|
|
4
11
|
export declare function isSamplingOn(threshold: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
|
|
14
|
+
* integer percentage in [0, 100].
|
|
15
|
+
*
|
|
16
|
+
* The bridges read the value as an integer (`NSNumber.intValue` on iOS,
|
|
17
|
+
* `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
|
|
18
|
+
* truncated to `0` on iOS — silently sampling every session out — and can throw
|
|
19
|
+
* on Android. Round instead, and never round a caller-requested non-zero rate
|
|
20
|
+
* down to `0`: `0` is the one value that means "emit nothing".
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeSessionSampleRate(rate: unknown): number;
|
|
5
23
|
export declare function resolveCoralogixOtelWebConfig(config: CoralogixBrowserSdkConfig): CoralogixBrowserSdkConfig;
|