@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/ios/CxSdk.swift CHANGED
@@ -1,5 +1,6 @@
1
1
  import Foundation
2
2
  import Coralogix
3
+ import CoralogixInternal
3
4
  import SessionReplay
4
5
  import React
5
6
 
@@ -11,6 +12,9 @@ enum CxSdkError: Error {
11
12
  @objc(CxSdk)
12
13
  class CxSdk: RCTEventEmitter {
13
14
  var coralogixRum: CoralogixRum?
15
+ // Accessed only from the RN methodQueue (serial); no explicit locking required.
16
+ private var globalSpans: [String: CoralogixGlobalSpan] = [:]
17
+ private var customSpans: [String: CoralogixCustomSpan] = [:]
14
18
 
15
19
  override init() {
16
20
  super.init()
@@ -21,7 +25,7 @@ class CxSdk: RCTEventEmitter {
21
25
  }
22
26
 
23
27
  override func supportedEvents() -> [String]! {
24
- return ["onBeforeSend"]
28
+ return ["onBeforeSend", "onTracesExport"]
25
29
  }
26
30
 
27
31
  @objc(initialize:withResolver:withRejecter:)
@@ -39,6 +43,15 @@ class CxSdk: RCTEventEmitter {
39
43
 
40
44
  var options = try self.toCoralogixOptions(parameter: parameter)
41
45
  options.beforeSendCallBack = beforeSendCallBack
46
+
47
+ let hasTracesExporter = parameter["hasTracesExporter"] as? Bool ?? false
48
+ if hasTracesExporter {
49
+ options.tracesExporter = { [weak self] data in
50
+ guard let jsonString = data.jsonString else { return }
51
+ self?.sendEvent(withName: "onTracesExport", body: jsonString)
52
+ }
53
+ }
54
+
42
55
  let rnPluginVersion = parameter["frameworkVersion"] as? String ?? ""
43
56
 
44
57
  self.coralogixRum = CoralogixRum(options: options, sdkFramework: .reactNative(version: rnPluginVersion))
@@ -195,6 +208,8 @@ class CxSdk: RCTEventEmitter {
195
208
  @objc(shutdown:withRejecter:)
196
209
  func shutdown(resolve:RCTPromiseResolveBlock,
197
210
  reject:RCTPromiseRejectBlock) -> Void {
211
+ globalSpans.removeAll()
212
+ customSpans.removeAll()
198
213
  coralogixRum?.shutdown()
199
214
  resolve("shutdown success")
200
215
  }
@@ -353,6 +368,17 @@ class CxSdk: RCTEventEmitter {
353
368
  resolve("sendCustomMeasurement success")
354
369
  }
355
370
 
371
+ @objc(startTimeMeasure:labels:)
372
+ func startTimeMeasure(name: String, labels: NSDictionary) {
373
+ let labelsDict = labels as? [String: Any]
374
+ coralogixRum?.startTimeMeasure(name: name, labels: labelsDict)
375
+ }
376
+
377
+ @objc(endTimeMeasure:)
378
+ func endTimeMeasure(name: String) {
379
+ coralogixRum?.endTimeMeasure(name: name)
380
+ }
381
+
356
382
  private func toHybridMetricList(metrics: NSArray) -> [HybridMetric] {
357
383
  let array = metrics as? [[String: Any]] ?? []
358
384
 
@@ -457,6 +483,9 @@ class CxSdk: RCTEventEmitter {
457
483
  let ignoreError = (parameter["ignoreErrors"] as? [Any])?.compactMap { $0 as? String } ?? []
458
484
  let rawNetworkExtraConfig = parameter["networkExtraConfig"] as? [[String: Any]]
459
485
  let networkExtraConfig = toNetworkCaptureRuleList(rawNetworkExtraConfig)
486
+ let excludeFromSampling = toExcludeFromSampling(parameter["excludeFromSampling"] as? [Any])
487
+
488
+ let sessionSampleRate = (parameter["sessionSampleRate"] as? NSNumber)?.intValue ?? 100
460
489
 
461
490
  let options = CoralogixExporterOptions(
462
491
  coralogixDomain: coralogixDomain,
@@ -468,6 +497,8 @@ class CxSdk: RCTEventEmitter {
468
497
  ignoreUrls: ignoreUrls,
469
498
  ignoreErrors: ignoreError,
470
499
  labels: labels,
500
+ sessionSampleRate: sessionSampleRate,
501
+ excludeFromSampling: excludeFromSampling,
471
502
  instrumentations: instrumentationDict,
472
503
  collectIPData: parameter["collectIPData"] as? Bool ?? true,
473
504
  proxyUrl: parameter["proxyUrl"] as? String ?? nil,
@@ -579,6 +610,23 @@ class CxSdk: RCTEventEmitter {
579
610
  }
580
611
  }
581
612
 
613
+ private func toExcludeFromSampling(_ raw: [Any]?) -> Set<ExcludableInstrumentation> {
614
+ guard let raw = raw else { return [] }
615
+ var result: Set<ExcludableInstrumentation> = []
616
+ for value in raw {
617
+ guard let key = value as? String else {
618
+ debugPrint("[CxSdk] excludeFromSampling: ignoring non-string value \(value)")
619
+ continue
620
+ }
621
+ guard let instrumentation = ExcludableInstrumentation(rawValue: key) else {
622
+ debugPrint("[CxSdk] excludeFromSampling: unrecognized value '\(key)' — native enum may have drifted")
623
+ continue
624
+ }
625
+ result.insert(instrumentation)
626
+ }
627
+ return result
628
+ }
629
+
582
630
  private func instrumentationType(from string: String) -> CoralogixExporterOptions.InstrumentationType? {
583
631
  switch string {
584
632
  case "mobile_vitals":
@@ -600,6 +648,78 @@ class CxSdk: RCTEventEmitter {
600
648
  }
601
649
  }
602
650
 
651
+ // MARK: - Custom Spans
652
+
653
+ @objc(startGlobalSpan:labels:ignoredInstruments:withResolver:withRejecter:)
654
+ func startGlobalSpan(name: String,
655
+ labels: NSDictionary?,
656
+ ignoredInstruments: NSArray?,
657
+ resolve: RCTPromiseResolveBlock,
658
+ reject: RCTPromiseRejectBlock) {
659
+ guard let rum = coralogixRum else { resolve(nil); return }
660
+ let ignored = toIgnoredInstrumentSet(ignoredInstruments)
661
+ guard let tracer = rum.getCustomTracer(ignoredInstruments: ignored) else { resolve(nil); return }
662
+ let labelsDict = toAnyDict(labels)
663
+ guard let globalSpan = tracer.startGlobalSpan(name: name, labels: labelsDict) else { resolve(nil); return }
664
+ let spanId = globalSpan.spanId
665
+ let traceId = globalSpan.traceId
666
+ globalSpans[spanId] = globalSpan
667
+ resolve(["spanId": spanId, "traceId": traceId])
668
+ }
669
+
670
+ @objc(startCustomSpan:name:labels:withResolver:withRejecter:)
671
+ func startCustomSpan(parentSpanId: String,
672
+ name: String,
673
+ labels: NSDictionary?,
674
+ resolve: RCTPromiseResolveBlock,
675
+ reject: RCTPromiseRejectBlock) {
676
+ guard let globalSpan = globalSpans[parentSpanId] else { resolve(nil); return }
677
+ let labelsDict = toAnyDict(labels)
678
+ let customSpan = globalSpan.startCustomSpan(name: name, labels: labelsDict)
679
+ let spanId = customSpan.span.context.spanId.hexString
680
+ let traceId = globalSpan.traceId
681
+ guard !spanId.isEmpty else { resolve(nil); return }
682
+ customSpans[spanId] = customSpan
683
+ resolve(["spanId": spanId, "traceId": traceId])
684
+ }
685
+
686
+ @objc(endSpan:withResolver:withRejecter:)
687
+ func endSpan(spanId: String,
688
+ resolve: RCTPromiseResolveBlock,
689
+ reject: RCTPromiseRejectBlock) {
690
+ if let globalSpan = globalSpans.removeValue(forKey: spanId) {
691
+ globalSpan.endSpan()
692
+ } else if let customSpan = customSpans.removeValue(forKey: spanId) {
693
+ customSpan.endSpan()
694
+ }
695
+ resolve(nil)
696
+ }
697
+
698
+ private func toIgnoredInstrumentSet(_ array: NSArray?) -> Set<CoralogixIgnoredInstrument> {
699
+ guard let array = array else { return [] }
700
+ var set = Set<CoralogixIgnoredInstrument>()
701
+ for item in array {
702
+ switch item as? String {
703
+ case "networkRequests": set.insert(.networkRequests)
704
+ case "userInteractions": set.insert(.userInteractions)
705
+ case "errors": set.insert(.errors)
706
+ default: break
707
+ }
708
+ }
709
+ return set
710
+ }
711
+
712
+ private func toAnyDict(_ dict: NSDictionary?) -> [String: Any]? {
713
+ guard let dict = dict, dict.count > 0 else { return nil }
714
+ var result = [String: Any]()
715
+ for (key, value) in dict {
716
+ if let k = key as? String {
717
+ result[k] = value
718
+ }
719
+ }
720
+ return result.isEmpty ? nil : result
721
+ }
722
+
603
723
  private func getCoralogixDomain(domain: String) -> CoralogixDomain {
604
724
  switch domain.uppercased() {
605
725
  case "EU1":
@@ -616,6 +736,8 @@ class CxSdk: RCTEventEmitter {
616
736
  return .AP2
617
737
  case "AP3":
618
738
  return .AP3
739
+ case "US3":
740
+ return .US3
619
741
  case "STAGING":
620
742
  return .STG
621
743
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralogix/react-native-plugin",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "Official Coralogix React Native plugin",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Coralogix",
@@ -0,0 +1,9 @@
1
+ import type { CustomSpanBridge } from './CustomSpanBridge';
2
+ /** A child span nested under a {@link CoralogixGlobalSpan}. */
3
+ export declare class CoralogixCustomSpan {
4
+ readonly spanId: string;
5
+ readonly traceId: string;
6
+ private readonly bridge;
7
+ constructor(spanId: string, traceId: string, bridge: CustomSpanBridge);
8
+ endSpan(): Promise<void>;
9
+ }
@@ -0,0 +1,18 @@
1
+ import type { CoralogixIgnoredInstrument } from '../model/Types';
2
+ import { CoralogixGlobalSpan } from './CoralogixGlobalSpan';
3
+ import type { CustomSpanBridge } from './CustomSpanBridge';
4
+ /**
5
+ * Entry point for manual RUM tracing. Obtain an instance via `CoralogixRum.getCustomTracer()`.
6
+ * The `ignoredInstruments` list controls which auto-instrumented events are NOT linked to spans
7
+ * created by this tracer (e.g. passing `['networkRequests']` keeps network spans independent).
8
+ */
9
+ export declare class CoralogixCustomTracer {
10
+ private readonly ignoredInstruments;
11
+ private readonly bridge;
12
+ constructor(ignoredInstruments: CoralogixIgnoredInstrument[], bridge: CustomSpanBridge);
13
+ /**
14
+ * Starts a new global (root) span. Returns `null` if a global span is already active —
15
+ * only one global span may exist at a time across the entire app.
16
+ */
17
+ startGlobalSpan(name: string, labels?: Record<string, any>): Promise<CoralogixGlobalSpan | null>;
18
+ }
@@ -0,0 +1,20 @@
1
+ import { CoralogixCustomSpan } from './CoralogixCustomSpan';
2
+ import type { CustomSpanBridge } from './CustomSpanBridge';
3
+ /** A root-level custom span. All child spans and `withContext` network calls share its traceId. */
4
+ export declare class CoralogixGlobalSpan {
5
+ readonly spanId: string;
6
+ readonly traceId: string;
7
+ private readonly bridge;
8
+ constructor(spanId: string, traceId: string, bridge: CustomSpanBridge);
9
+ /** Creates a child span nested under this global span. */
10
+ startCustomSpan(name: string, labels?: Record<string, any>): Promise<CoralogixCustomSpan | null>;
11
+ /**
12
+ * Runs fn() with this span set as the active global span for the duration of
13
+ * the returned promise. The active span ID remains set throughout the full
14
+ * async chain — including code after intermediate awaits — because we await
15
+ * fn() before clearing. Only detached callbacks (e.g. setTimeout) that fire
16
+ * after withContext resolves won't be linked.
17
+ */
18
+ withContext<T>(fn: () => Promise<T>): Promise<T>;
19
+ endSpan(): Promise<void>;
20
+ }
@@ -0,0 +1,11 @@
1
+ export interface CustomSpanBridge {
2
+ startGlobalSpan(name: string, labels: Record<string, any> | null, ignoredInstruments: string[]): Promise<{
3
+ spanId: string;
4
+ traceId: string;
5
+ } | null>;
6
+ startCustomSpan(parentSpanId: string, name: string, labels: Record<string, any> | null): Promise<{
7
+ spanId: string;
8
+ traceId: string;
9
+ } | null>;
10
+ endSpan(spanId: string): Promise<void>;
11
+ }
@@ -0,0 +1,12 @@
1
+ import type { CoralogixIgnoredInstrument } from '../model/Types';
2
+ export declare const CustomSpanRegistry: {
3
+ getActive: () => string | null;
4
+ setActive: (id: string) => void;
5
+ clearActive: () => void;
6
+ registerSpan: (spanId: string, traceId: string) => void;
7
+ unregisterSpan: (spanId: string) => void;
8
+ getTraceId: (spanId: string) => string | undefined;
9
+ setIgnoredInstruments: (set: Set<CoralogixIgnoredInstrument>) => void;
10
+ getIgnoredInstruments: () => Set<CoralogixIgnoredInstrument>;
11
+ clear: () => void;
12
+ };
package/src/index.d.ts CHANGED
@@ -10,6 +10,7 @@ interface CxSdkClient {
10
10
  initialize(options: CoralogixBrowserSdkConfig & {
11
11
  frameworkVersion?: string;
12
12
  hasBeforeSend?: boolean;
13
+ hasTracesExporter?: boolean;
13
14
  }): Promise<boolean>;
14
15
  setUserContext(userContext: UserContextConfig): void;
15
16
  getUserContext(): Promise<UserContextConfig>;
@@ -20,6 +21,8 @@ interface CxSdkClient {
20
21
  setViewContext(viewContext: string): void;
21
22
  log(severity: CoralogixLogSeverity, message: string, data: Record<string, any> | undefined | null, labels: Record<string, any> | undefined | null): void;
22
23
  sendCustomMeasurement(measurement: CustomMeasurement): void;
24
+ startTimeMeasure(name: string, labels: Record<string, unknown>): void;
25
+ endTimeMeasure(name: string): void;
23
26
  reportError(details: Record<string, string>): void;
24
27
  reportNetworkRequest(details: NetworkRequestDetails): void;
25
28
  shutdown(): Promise<boolean>;
@@ -38,6 +41,15 @@ interface CxSdkClient {
38
41
  captureScreenshot(): void;
39
42
  maskViewByTag(viewTag: number): void;
40
43
  reportUserInteraction(interaction: InteractionContext): void;
44
+ startGlobalSpan(name: string, labels: Record<string, any> | null, ignoredInstruments: string[]): Promise<{
45
+ spanId: string;
46
+ traceId: string;
47
+ } | null>;
48
+ startCustomSpan(parentSpanId: string, name: string, labels: Record<string, any> | null): Promise<{
49
+ spanId: string;
50
+ traceId: string;
51
+ } | null>;
52
+ endSpan(spanId: string): Promise<void>;
41
53
  }
42
54
  export declare const CxSdk: CxSdkClient;
43
55
  export declare const CoralogixRum: CoralogixOtelWebType;
@@ -47,9 +59,12 @@ export { type ApplicationContextConfig } from './model/ApplicationContextConfig'
47
59
  export { type ViewContextConfig } from './model/ViewContextConfig';
48
60
  export { CoralogixDomain } from './model/CoralogixDomain';
49
61
  export { type UserContextConfig } from './model/UserContextConfig';
50
- export { type CoralogixBrowserSdkConfig, type NetworkCaptureRule, CoralogixLogSeverity, } from './model/Types';
62
+ export { type CoralogixBrowserSdkConfig, type CoralogixIgnoredInstrument, type ExcludableInstrumentation, type NetworkCaptureRule, type TraceExporterData, CoralogixLogSeverity, } from './model/Types';
51
63
  export { type CoralogixOtelWebOptionsInstrumentations } from './model/CoralogixOtelWebOptionsInstrumentations';
52
64
  export { type CustomMeasurement } from './model/CustomMeasurement';
53
65
  export { type NetworkRequestDetails } from './model/NetworkRequestDetails';
54
66
  export { type CoralogixMobileVitals } from './model/CoralogixMobileVitals';
55
67
  export { attachReactNavigationObserver } from './instrumentations/navigation/NavigationInstrumentation';
68
+ export { CoralogixCustomTracer } from './custom-spans/CoralogixCustomTracer';
69
+ export { CoralogixGlobalSpan } from './custom-spans/CoralogixGlobalSpan';
70
+ export { CoralogixCustomSpan } from './custom-spans/CoralogixCustomSpan';
@@ -6,5 +6,6 @@ export declare enum CoralogixDomain {
6
6
  AP1 = "AP1",
7
7
  AP2 = "AP2",
8
8
  AP3 = "AP3",
9
+ US3 = "US3",
9
10
  STAGING = "staging"
10
11
  }
@@ -1,11 +1,12 @@
1
1
  import type { SendLog } from './SendLog';
2
2
  import type { CoralogixRumLabels } from './CoralogixRumLabels';
3
- import { CoralogixBrowserSdkConfig } from './Types';
3
+ import { CoralogixBrowserSdkConfig, CoralogixIgnoredInstrument } from './Types';
4
4
  import { UserContextConfig } from './UserContextConfig';
5
5
  import { ViewContextConfig } from './ViewContextConfig';
6
6
  import { ApplicationContextConfig } from './ApplicationContextConfig';
7
7
  import { CustomMeasurement } from './CustomMeasurement';
8
8
  import { NetworkRequestDetails } from './NetworkRequestDetails';
9
+ import { CoralogixCustomTracer } from '../custom-spans/CoralogixCustomTracer';
9
10
  export interface CoralogixOtelWebType extends SendLog {
10
11
  /**
11
12
  * Init CoralogixRum.
@@ -40,11 +41,29 @@ export interface CoralogixOtelWebType extends SendLog {
40
41
  * Send custom key value pair
41
42
  */
42
43
  sendCustomMeasurement: (measurement: CustomMeasurement) => void;
44
+ /**
45
+ * Start a time measurement for `name`. The native SDK records the start
46
+ * timestamp; the call is fire-and-forget. Pair every `startTimeMeasure`
47
+ * with exactly one `endTimeMeasure(name)` — leaked starts persist in
48
+ * memory until shutdown.
49
+ */
50
+ startTimeMeasure: (name: string, labels?: Record<string, unknown>) => void;
51
+ /**
52
+ * End a previously started time measurement. The native SDK computes the
53
+ * duration and reports it as a custom-measurement span (milliseconds).
54
+ * Unknown names are dropped silently by the native SDK.
55
+ */
56
+ endTimeMeasure: (name: string) => void;
43
57
  reportError: (error: Error, isCrash: boolean) => void;
44
58
  /**
45
59
  * Report a network request to the underlying Coralogix SDK
46
60
  * @param details
47
61
  */
48
62
  reportNetworkRequest: (details: NetworkRequestDetails) => void;
63
+ /**
64
+ * Returns a tracer for creating custom spans. Instruments listed in
65
+ * ignoredInstruments will not be auto-parented to the active global span.
66
+ */
67
+ getCustomTracer: (ignoredInstruments?: CoralogixIgnoredInstrument[]) => CoralogixCustomTracer;
49
68
  readonly isInited: boolean;
50
69
  }
@@ -200,6 +200,11 @@ export interface CoralogixBrowserSdkConfig {
200
200
  environment?: string;
201
201
  /** Percentage of overall sessions being tracked, defaults to 100% */
202
202
  sessionSampleRate?: number;
203
+ /**
204
+ * Event categories that bypass session sampling. Events of these categories are
205
+ * emitted even when the session is sampled out. Defaults to `[]`.
206
+ */
207
+ excludeFromSampling?: ExcludableInstrumentation[];
203
208
  /** Collect IP data */
204
209
  collectIPData?: boolean;
205
210
  /** Enable event access and modification before sending to Coralogix, supporting content modification, and event discarding. */
@@ -218,12 +223,30 @@ export interface CoralogixBrowserSdkConfig {
218
223
  sampleIntervalMs?: number;
219
224
  };
220
225
  mobileVitals?: CoralogixMobileVitals;
226
+ /** Callback invoked with each OTLP trace batch exported by the native SDK. */
227
+ tracesExporter?: (data: TraceExporterData) => void;
221
228
  }
222
229
  export type HybridMetric = {
223
230
  name: string;
224
231
  value: number;
225
232
  units: string;
226
233
  };
234
+ /**
235
+ * Instrumentation names that can be excluded from a custom tracer's span context.
236
+ * Pass one or more values to `getCustomTracer()` to prevent those event types from
237
+ * being linked to spans created by that tracer.
238
+ */
239
+ export type CoralogixIgnoredInstrument = 'networkRequests' | 'userInteractions' | 'errors';
240
+ /**
241
+ * Event categories that can opt out of session sampling. When the session is
242
+ * sampled out, events whose category is listed in `excludeFromSampling` are still
243
+ * emitted; everything else is dropped. Mirrors the iOS/Android native enums.
244
+ */
245
+ export type ExcludableInstrumentation = 'errors' | 'logs' | 'network' | 'userInteractions' | 'mobileVitals' | 'customSpan' | 'customMeasurement';
246
+ /** OTLP JSON-format trace data delivered to the `tracesExporter` callback. */
247
+ export interface TraceExporterData {
248
+ resource_spans: Record<string, unknown>[];
249
+ }
227
250
  export interface InteractionContext {
228
251
  type: 'click' | 'scroll' | 'swipe';
229
252
  /** Required. Element identifier: accessibilityLabel ?? componentName for clicks; 'ScrollView' for scroll/swipe. */