@coralogix/browser 2.11.1 → 2.12.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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## 2.12.0 (2025-11-30)
2
+
3
+ ### 🚀 Features
4
+
5
+ - Introduced the tracesExporter callback, enabling custom processing or exporting of collected trace events.
6
+
7
+
1
8
  ## 2.11.1 (2025-11-24)
2
9
 
3
10
  ### 🩹 Fixes
package/README.md CHANGED
@@ -901,6 +901,28 @@ const customTracer = CoralogixRum.getCustomTracer({
901
901
  // ... your code
902
902
  ```
903
903
 
904
+ ### Traces Exporter
905
+
906
+ The `tracesExporter` callback gives you full control over how collected trace events are handled.
907
+ It receives a `TracesExporterPayload` object containing all trace data that the SDK has collected.
908
+
909
+ ```ts
910
+ CoralogixRum.init({
911
+ tracesExporter: (data: TraceExporterData) => {
912
+ // Example: forward traces to your own backend endpoint
913
+ fetch('https://api.mycompany.com/rum-traces', {
914
+ method: 'POST',
915
+ headers: {
916
+ 'Content-Type': 'application/json',
917
+ },
918
+ body: JSON.stringify(data),
919
+ });
920
+
921
+ },
922
+ });
923
+ ```
924
+
925
+
904
926
  ### Soft Navigations — Experimental
905
927
 
906
928
  Soft navigations are navigations that do not trigger a full page reload, such as SPA navigations. Defaults to false.
package/index.esm2.js CHANGED
@@ -3381,6 +3381,179 @@ function isProcessorShouldStop(isActive) {
3381
3381
  return !isActive || !!((_a = getSessionManager()) === null || _a === void 0 ? void 0 : _a.isIdleActive);
3382
3382
  }
3383
3383
 
3384
+ var NANOS_IN_SECOND = 1000000000;
3385
+ var NANOS_PER_MILLISECOND = 1000000;
3386
+ var MAX_FUTURE_NANOS = 60 * 60 * NANOS_IN_SECOND;
3387
+ var MAX_PAST_NANOS = 24 * 60 * 60 * NANOS_IN_SECOND;
3388
+
3389
+ function toOtlpKeyValue(key, value) {
3390
+ if (!value) {
3391
+ return { key: key, value: { string_value: '' } };
3392
+ }
3393
+ switch (typeof value) {
3394
+ case 'string':
3395
+ return { key: key, value: { string_value: value } };
3396
+ case 'number':
3397
+ if (!Number.isFinite(value)) {
3398
+ return {
3399
+ key: key,
3400
+ value: { string_value: String(value) },
3401
+ };
3402
+ }
3403
+ return Number.isInteger(value)
3404
+ ? { key: key, value: { int_value: value } }
3405
+ : { key: key, value: { double_value: value } };
3406
+ case 'boolean':
3407
+ return { key: key, value: { bool_value: value } };
3408
+ default:
3409
+ return { key: key, value: { string_value: '' } };
3410
+ }
3411
+ }
3412
+ function toOtlpAttributes(attributes) {
3413
+ if (!attributes)
3414
+ return [];
3415
+ return Object.entries(attributes).map(function (_a) {
3416
+ var _b = __read$1(_a, 2), k = _b[0], v = _b[1];
3417
+ return toOtlpKeyValue(k, v);
3418
+ });
3419
+ }
3420
+ function timestampToNanosNumber(timestamp) {
3421
+ var _a = __read$1(timestamp, 2), seconds = _a[0], nanos = _a[1];
3422
+ return seconds * NANOS_IN_SECOND + nanos;
3423
+ }
3424
+ function timestampToNanosString(timestamp) {
3425
+ var _a = __read$1(timestamp, 2), seconds = _a[0], nanos = _a[1];
3426
+ var secondsPart = String(seconds);
3427
+ var nanosPart = String(nanos).padStart(9, '0');
3428
+ return secondsPart + nanosPart;
3429
+ }
3430
+ function hexToBase64(hex) {
3431
+ if (!hex)
3432
+ return '';
3433
+ var binary = '';
3434
+ for (var i = 0; i < hex.length; i += 2) {
3435
+ var byte = Number.parseInt(hex.slice(i, i + 2), 16);
3436
+ binary += String.fromCharCode(byte);
3437
+ }
3438
+ return btoa(binary);
3439
+ }
3440
+ function mapStatusCodeToOtlp(code) {
3441
+ var _a;
3442
+ if (code == null)
3443
+ return undefined;
3444
+ var STATUS_CODE_MAP = {
3445
+ 0: 'STATUS_CODE_UNSET',
3446
+ 1: 'STATUS_CODE_OK',
3447
+ 2: 'STATUS_CODE_ERROR',
3448
+ };
3449
+ return (_a = STATUS_CODE_MAP[code]) !== null && _a !== void 0 ? _a : 'STATUS_CODE_UNSET';
3450
+ }
3451
+ function toOtlpStatus(spanStatus) {
3452
+ if (!spanStatus)
3453
+ return undefined;
3454
+ var code = mapStatusCodeToOtlp(spanStatus.code);
3455
+ var message = spanStatus.message;
3456
+ if (!code && !message)
3457
+ return undefined;
3458
+ return __assign({ code: code !== null && code !== void 0 ? code : 'STATUS_CODE_UNSET' }, (message ? { message: message } : {}));
3459
+ }
3460
+ function isWithinAllowedWindow(start, now) {
3461
+ return start <= now + MAX_FUTURE_NANOS && start >= now - MAX_PAST_NANOS;
3462
+ }
3463
+
3464
+ function buildExporterPayload(logs) {
3465
+ var e_1, _a;
3466
+ var _b;
3467
+ var resourceSpans = [];
3468
+ var nowUnixNano = Date.now() * NANOS_PER_MILLISECOND;
3469
+ try {
3470
+ for (var logs_1 = __values$1(logs), logs_1_1 = logs_1.next(); !logs_1_1.done; logs_1_1 = logs_1.next()) {
3471
+ var entry = logs_1_1.value;
3472
+ var instrumentationData = entry.instrumentation_data;
3473
+ if (!instrumentationData)
3474
+ continue;
3475
+ var spanJson = mapCxSpanToOtlpSpan(instrumentationData.otelSpan);
3476
+ if (!spanJson)
3477
+ continue;
3478
+ var startNanos = timestampToNanosNumber(instrumentationData.otelSpan.startTime);
3479
+ if (!isWithinAllowedWindow(startNanos, nowUnixNano))
3480
+ continue;
3481
+ var resource = mapEntryToOtlpResource(entry, (_b = instrumentationData.otelResource) === null || _b === void 0 ? void 0 : _b.attributes);
3482
+ var scopeSpans = {
3483
+ spans: [spanJson],
3484
+ };
3485
+ resourceSpans.push({ resource: resource, scope_spans: [scopeSpans] });
3486
+ }
3487
+ }
3488
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
3489
+ finally {
3490
+ try {
3491
+ if (logs_1_1 && !logs_1_1.done && (_a = logs_1.return)) _a.call(logs_1);
3492
+ }
3493
+ finally { if (e_1) throw e_1.error; }
3494
+ }
3495
+ return { resource_spans: resourceSpans };
3496
+ }
3497
+ function stripOtelFieldsFromLogs(logs) {
3498
+ return logs.map(function (_a) {
3499
+ _a.instrumentation_data; var rest = __rest(_a, ["instrumentation_data"]);
3500
+ return rest;
3501
+ });
3502
+ }
3503
+ function mapCxSpanToOtlpSpan(span) {
3504
+ var _a;
3505
+ if (!span.traceId || !span.spanId || !span.name) {
3506
+ if (getSdkConfig().debug) {
3507
+ console.debug('Missing required span fields: traceId, spanId, name', span);
3508
+ return null;
3509
+ }
3510
+ }
3511
+ if (!span.startTime || !span.endTime) {
3512
+ if (getSdkConfig().debug) {
3513
+ console.debug('Missing span timestamps', span);
3514
+ }
3515
+ return null;
3516
+ }
3517
+ var ids = encodeSpanIds(span);
3518
+ if (!ids)
3519
+ return null;
3520
+ var startUnixNano = timestampToNanosString(span.startTime);
3521
+ var endUnixNano = timestampToNanosString(span.endTime);
3522
+ return __assign(__assign(__assign({ trace_id: ids.traceIdB64, span_id: ids.spanIdB64 }, (ids.parentSpanIdB64 ? { parentSpanId: ids.parentSpanIdB64 } : {})), { name: (_a = span.name) !== null && _a !== void 0 ? _a : '', kind: 'SPAN_KIND_CLIENT', start_time_unix_nano: startUnixNano, end_time_unix_nano: endUnixNano, attributes: toOtlpAttributes(span.attributes) }), (span.status ? { status: toOtlpStatus(span.status) } : {}));
3523
+ }
3524
+ function mapEntryToOtlpResource(entry, extra) {
3525
+ var _a;
3526
+ var base = {
3527
+ 'cx.application.name': entry.applicationName,
3528
+ 'cx.subsystem.name': entry.subsystemName,
3529
+ };
3530
+ var merged = __assign(__assign({}, base), (extra !== null && extra !== void 0 ? extra : {}));
3531
+ if (!('service.name' in merged)) {
3532
+ merged['service.name'] = entry.applicationName;
3533
+ }
3534
+ var version = (_a = entry.version_metadata) === null || _a === void 0 ? void 0 : _a.app_version;
3535
+ if (version && !('service.version' in merged)) {
3536
+ merged['service.version'] = version;
3537
+ }
3538
+ var attributes = toOtlpAttributes(merged);
3539
+ return attributes.length ? { attributes: attributes } : {};
3540
+ }
3541
+ function encodeSpanIds(span) {
3542
+ return {
3543
+ traceIdB64: hexToBase64(span.traceId),
3544
+ spanIdB64: hexToBase64(span.spanId),
3545
+ parentSpanIdB64: span.parentSpanId
3546
+ ? hexToBase64(span.parentSpanId)
3547
+ : undefined,
3548
+ };
3549
+ }
3550
+
3551
+ function buildCustomTraces(logs) {
3552
+ var tracesPayload = buildExporterPayload(logs);
3553
+ var strippedLogs = stripOtelFieldsFromLogs(logs);
3554
+ return { tracesPayload: tracesPayload, strippedLogs: strippedLogs };
3555
+ }
3556
+
3384
3557
  var CoralogixExporter = (function () {
3385
3558
  function CoralogixExporter() {
3386
3559
  var _this = this;
@@ -3393,6 +3566,9 @@ var CoralogixExporter = (function () {
3393
3566
  this.batchTimeDelay = BATCH_TIME_DELAY;
3394
3567
  this.invokeLogRequest = function (logs, markCachedLogsSent) {
3395
3568
  if (logs === null || logs === void 0 ? void 0 : logs.length) {
3569
+ if (_this.sdkConfig.tracesExporter) {
3570
+ logs = _this.buildAndExportOtelTraces(logs);
3571
+ }
3396
3572
  _this.request
3397
3573
  .send(JSON.stringify({
3398
3574
  logs: logs,
@@ -3463,6 +3639,14 @@ var CoralogixExporter = (function () {
3463
3639
  }
3464
3640
  }
3465
3641
  };
3642
+ CoralogixExporter.prototype.buildAndExportOtelTraces = function (logs) {
3643
+ var hasOtelSpanData = logs.some(function (log) { return !!log.instrumentation_data; });
3644
+ if (!hasOtelSpanData)
3645
+ return logs;
3646
+ var _a = buildCustomTraces(logs), tracesPayload = _a.tracesPayload, strippedLogs = _a.strippedLogs;
3647
+ this.sdkConfig.tracesExporter(tracesPayload);
3648
+ return strippedLogs;
3649
+ };
3466
3650
  CoralogixExporter.prototype.clearCachedDataForSessionWithError = function () {
3467
3651
  CxGlobal.cachedLogs = [];
3468
3652
  this.batchTimeDelay = BATCH_TIME_DELAY;
@@ -3968,7 +4152,7 @@ function resolveUrlBlueprinters(urlBlueprinters) {
3968
4152
  return resolvedUrlBlueprinters;
3969
4153
  }
3970
4154
 
3971
- var SDK_VERSION = '2.11.1';
4155
+ var SDK_VERSION = '2.12.0';
3972
4156
 
3973
4157
  function shouldDropEvent(cxRumEvent, options) {
3974
4158
  if (isDocumentErrorWithoutMessage(cxRumEvent)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralogix/browser",
3
- "version": "2.11.1",
3
+ "version": "2.12.0",
4
4
  "description": "Official Coralogix SDK for browsers",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Coralogix",
@@ -14,6 +14,7 @@ export declare class CoralogixExporter implements SpanExporter, ProcessorBaseMod
14
14
  export(spans: CxReadableSpan[], resultCallback: (result: ExportResult) => void): void;
15
15
  private handleSessionWithError;
16
16
  private invokeLogRequest;
17
+ private buildAndExportOtelTraces;
17
18
  clearCachedDataForSessionWithError(): void;
18
19
  shutdown(): Promise<void>;
19
20
  }
@@ -0,0 +1,4 @@
1
+ export declare const NANOS_IN_SECOND = 1000000000;
2
+ export declare const NANOS_PER_MILLISECOND = 1000000;
3
+ export declare const MAX_FUTURE_NANOS: number;
4
+ export declare const MAX_PAST_NANOS: number;
@@ -0,0 +1,3 @@
1
+ import { CxSpan } from '../types';
2
+ import { BuildCustomTracesResult } from './traces-exporter.types';
3
+ export declare function buildCustomTraces(logs: CxSpan[]): BuildCustomTracesResult;
@@ -0,0 +1,4 @@
1
+ import { TraceExporterData } from './traces-exporter.types';
2
+ import { CxSpan } from '../types';
3
+ export declare function buildExporterPayload(logs: CxSpan[]): TraceExporterData;
4
+ export declare function stripOtelFieldsFromLogs(logs: CxSpan[]): Omit<CxSpan, 'instrumentation_data'>[];
@@ -0,0 +1,71 @@
1
+ import type { HrTime, SpanStatusCode } from '@opentelemetry/api';
2
+ import { CxSpan } from '../types';
3
+ export interface OtlpAnyValueJson {
4
+ string_value?: string;
5
+ bool_value?: boolean;
6
+ int_value?: number | string;
7
+ double_value?: number | string;
8
+ }
9
+ export interface OtlpKeyValueJson {
10
+ key: string;
11
+ value?: OtlpAnyValueJson;
12
+ }
13
+ export type OtlpStatusCodeJson = 'STATUS_CODE_UNSET' | 'STATUS_CODE_OK' | 'STATUS_CODE_ERROR';
14
+ export interface OtlpStatusJson {
15
+ code?: OtlpStatusCodeJson;
16
+ message?: string;
17
+ }
18
+ export type OtlpSpanKindJson = 'SPAN_KIND_INTERNAL' | 'SPAN_KIND_SERVER' | 'SPAN_KIND_CLIENT' | 'SPAN_KIND_PRODUCER' | 'SPAN_KIND_CONSUMER';
19
+ export interface OtlpSpanJson {
20
+ trace_id: string;
21
+ span_id: string;
22
+ parent_span_id?: string;
23
+ name: string;
24
+ kind: OtlpSpanKindJson;
25
+ start_time_unix_nano: string;
26
+ end_time_unix_nano: string;
27
+ attributes?: OtlpKeyValueJson[];
28
+ status?: OtlpStatusJson;
29
+ }
30
+ export interface OtlpScopeJson {
31
+ name?: string;
32
+ version?: string;
33
+ attributes?: OtlpKeyValueJson[];
34
+ }
35
+ export interface OtlpScopeSpansJson {
36
+ scope?: OtlpScopeJson;
37
+ spans: OtlpSpanJson[];
38
+ }
39
+ export interface OtlpResourceJson {
40
+ attributes?: OtlpKeyValueJson[];
41
+ }
42
+ export interface OtlpResourceSpansJson {
43
+ resource?: OtlpResourceJson;
44
+ scope_spans: OtlpScopeSpansJson[];
45
+ }
46
+ export type CustomTimestamp = [seconds: number, nanos: number];
47
+ export type ResourceAttributes = Record<string, unknown>;
48
+ export interface TraceExporterData {
49
+ resource_spans: OtlpResourceSpansJson[];
50
+ }
51
+ export interface CxSpanOtelSpan {
52
+ spanId: string;
53
+ traceId: string;
54
+ sessionId?: string;
55
+ parentSpanId?: string;
56
+ name?: string;
57
+ attributes?: Record<string, unknown>;
58
+ startTime?: HrTime;
59
+ endTime?: HrTime;
60
+ status?: {
61
+ code: SpanStatusCode | number;
62
+ message?: string;
63
+ };
64
+ }
65
+ export interface CxSpanOtelResource {
66
+ attributes?: Record<string, unknown>;
67
+ }
68
+ export interface BuildCustomTracesResult {
69
+ tracesPayload: TraceExporterData;
70
+ strippedLogs: CxSpan[];
71
+ }
@@ -0,0 +1,10 @@
1
+ import { SpanStatusCode } from '@opentelemetry/api';
2
+ import { CustomTimestamp, CxSpanOtelSpan, OtlpKeyValueJson, OtlpStatusCodeJson, OtlpStatusJson } from './traces-exporter.types';
3
+ export declare function toOtlpKeyValue(key: string, value: unknown): OtlpKeyValueJson;
4
+ export declare function toOtlpAttributes(attributes?: Record<string, unknown>): OtlpKeyValueJson[];
5
+ export declare function timestampToNanosNumber(timestamp: CustomTimestamp): number;
6
+ export declare function timestampToNanosString(timestamp: CustomTimestamp): string;
7
+ export declare function hexToBase64(hex: string): string;
8
+ export declare function mapStatusCodeToOtlp(code?: SpanStatusCode | number): OtlpStatusCodeJson | undefined;
9
+ export declare function toOtlpStatus(spanStatus?: CxSpanOtelSpan['status']): OtlpStatusJson | undefined;
10
+ export declare function isWithinAllowedWindow(start: number, now: number): boolean;
package/src/types.d.ts CHANGED
@@ -17,6 +17,7 @@ import { MemoryUsageContext } from './instrumentations/memory-usage';
17
17
  import { MFEMetadata } from './mfe/mfe.types';
18
18
  import { UrlBasedLabelProvider } from './label-providers/url-based-label-provider';
19
19
  import { CustomTracer, CustomTracerIgnoredInstruments } from './custom-spans';
20
+ import { TraceExporterData } from './traces-exporter/traces-exporter.types';
20
21
  export interface CxStackFrame extends Partial<MFEMetadata> {
21
22
  fileName?: string;
22
23
  columnNumber?: number;
@@ -157,6 +158,7 @@ export interface CoralogixBrowserSdkConfig {
157
158
  networkExtraConfig?: NetworkExtraConfig[];
158
159
  supportMfe?: boolean;
159
160
  workerSupport?: boolean;
161
+ tracesExporter?: (data: TraceExporterData) => void;
160
162
  }
161
163
  export interface CoralogixOtelWebType extends SendLog {
162
164
  init: (options: CoralogixBrowserSdkConfig) => void;
package/src/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.11.1";
1
+ export declare const SDK_VERSION = "2.12.0";