@grafana/faro-react-native-tracing 1.0.0-alpha.1
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/LICENSE +201 -0
- package/README.md +833 -0
- package/dist/cjs/exporters/faroTraceExporter.js +101 -0
- package/dist/cjs/exporters/faroTraceExporter.js.map +1 -0
- package/dist/cjs/exporters/faroTraceExporter.utils.js +99 -0
- package/dist/cjs/exporters/faroTraceExporter.utils.js.map +1 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/instrumentation.js +251 -0
- package/dist/cjs/instrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +77 -0
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
- package/dist/cjs/instrumentations/instrumentationUtils.js +89 -0
- package/dist/cjs/instrumentations/instrumentationUtils.js.map +1 -0
- package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js +59 -0
- package/dist/cjs/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
- package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js +98 -0
- package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
- package/dist/cjs/semconv.js +29 -0
- package/dist/cjs/semconv.js.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils/sampler.js +21 -0
- package/dist/cjs/utils/sampler.js.map +1 -0
- package/dist/esm/exporters/faroTraceExporter.js +65 -0
- package/dist/esm/exporters/faroTraceExporter.js.map +1 -0
- package/dist/esm/exporters/faroTraceExporter.utils.js +88 -0
- package/dist/esm/exporters/faroTraceExporter.utils.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/instrumentation.js +183 -0
- package/dist/esm/instrumentation.js.map +1 -0
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +62 -0
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -0
- package/dist/esm/instrumentations/instrumentationUtils.js +83 -0
- package/dist/esm/instrumentations/instrumentationUtils.js.map +1 -0
- package/dist/esm/processors/faroMetaAttributesSpanProcessor.js +54 -0
- package/dist/esm/processors/faroMetaAttributesSpanProcessor.js.map +1 -0
- package/dist/esm/processors/httpRequestMonitorSpanProcessor.js +93 -0
- package/dist/esm/processors/httpRequestMonitorSpanProcessor.js.map +1 -0
- package/dist/esm/semconv.js +26 -0
- package/dist/esm/semconv.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils/sampler.js +17 -0
- package/dist/esm/utils/sampler.js.map +1 -0
- package/dist/types/exporters/faroTraceExporter.d.ts +21 -0
- package/dist/types/exporters/faroTraceExporter.utils.d.ts +17 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/instrumentation.d.ts +46 -0
- package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +17 -0
- package/dist/types/instrumentations/instrumentationUtils.d.ts +26 -0
- package/dist/types/processors/faroMetaAttributesSpanProcessor.d.ts +22 -0
- package/dist/types/processors/httpRequestMonitorSpanProcessor.d.ts +18 -0
- package/dist/types/semconv.d.ts +19 -0
- package/dist/types/types.d.ts +31 -0
- package/dist/types/utils/sampler.d.ts +12 -0
- package/package.json +77 -0
- package/src/exporters/faroTraceExporter.test.ts +110 -0
- package/src/exporters/faroTraceExporter.ts +64 -0
- package/src/exporters/faroTraceExporter.utils.ts +105 -0
- package/src/index.ts +16 -0
- package/src/instrumentation.ts +241 -0
- package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +70 -0
- package/src/instrumentations/getDefaultOTELInstrumentations.ts +83 -0
- package/src/instrumentations/instrumentationUtils.test.ts +107 -0
- package/src/instrumentations/instrumentationUtils.ts +106 -0
- package/src/processors/faroMetaAttributesSpanProcessor.test.ts +127 -0
- package/src/processors/faroMetaAttributesSpanProcessor.ts +71 -0
- package/src/processors/httpRequestMonitorSpanProcessor.ts +106 -0
- package/src/semconv.ts +31 -0
- package/src/types.ts +39 -0
- package/src/utils/sampler.test.ts +51 -0
- package/src/utils/sampler.ts +19 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.FaroTraceExporter = void 0;
|
|
40
|
+
var core_1 = require("@opentelemetry/core");
|
|
41
|
+
var internal_1 = require("@opentelemetry/otlp-transformer/build/src/trace/internal");
|
|
42
|
+
var faro_core_1 = require("@grafana/faro-core");
|
|
43
|
+
var internalLogger = (0, faro_core_1.createInternalLogger)();
|
|
44
|
+
var faroTraceExporter_utils_1 = require("./faroTraceExporter.utils");
|
|
45
|
+
/**
|
|
46
|
+
* FaroTraceExporter for React Native
|
|
47
|
+
*
|
|
48
|
+
* Exports OpenTelemetry spans to Faro backend using pushTraces API.
|
|
49
|
+
*
|
|
50
|
+
* IMPORTANT: To avoid infinite loops:
|
|
51
|
+
* - Uses internalLogger instead of console
|
|
52
|
+
* - Does NOT log during export (except errors)
|
|
53
|
+
* - Relies on Faro's internal deduplication
|
|
54
|
+
*/
|
|
55
|
+
var FaroTraceExporter = /** @class */ (function () {
|
|
56
|
+
function FaroTraceExporter(config) {
|
|
57
|
+
this.config = config;
|
|
58
|
+
this._isShutdown = false;
|
|
59
|
+
}
|
|
60
|
+
FaroTraceExporter.prototype.export = function (spans, resultCallback) {
|
|
61
|
+
if (this._isShutdown) {
|
|
62
|
+
internalLogger.error('FaroTraceExporter: Cannot export spans, exporter is shut down');
|
|
63
|
+
resultCallback({ code: core_1.ExportResultCode.FAILED });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
// Convert spans to OTLP format
|
|
68
|
+
var traceEvent = (0, internal_1.createExportTraceServiceRequest)(spans, { useHex: true, useLongBits: false });
|
|
69
|
+
// Send traces to Faro
|
|
70
|
+
this.config.api.pushTraces(traceEvent);
|
|
71
|
+
// Send Faro events for CLIENT spans (HTTP requests, etc.)
|
|
72
|
+
// This is done WITHOUT logging to avoid infinite loops
|
|
73
|
+
(0, faroTraceExporter_utils_1.sendFaroEvents)(traceEvent.resourceSpans);
|
|
74
|
+
resultCallback({ code: core_1.ExportResultCode.SUCCESS });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// Only log critical errors
|
|
78
|
+
internalLogger.error('FaroTraceExporter: Failed to export spans', error);
|
|
79
|
+
resultCallback({ code: core_1.ExportResultCode.FAILED });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
FaroTraceExporter.prototype.shutdown = function () {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
84
|
+
return __generator(this, function (_a) {
|
|
85
|
+
this._isShutdown = true;
|
|
86
|
+
return [2 /*return*/, Promise.resolve(undefined)];
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
FaroTraceExporter.prototype.forceFlush = function () {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
92
|
+
return __generator(this, function (_a) {
|
|
93
|
+
// No-op for now - spans are sent immediately via pushTraces
|
|
94
|
+
return [2 /*return*/, Promise.resolve(undefined)];
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
return FaroTraceExporter;
|
|
99
|
+
}());
|
|
100
|
+
exports.FaroTraceExporter = FaroTraceExporter;
|
|
101
|
+
//# sourceMappingURL=faroTraceExporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faroTraceExporter.js","sourceRoot":"","sources":["../../../src/exporters/faroTraceExporter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAuD;AAEvD,qFAA2G;AAG3G,gDAA0D;AAE1D,IAAM,cAAc,GAAG,IAAA,gCAAoB,GAAE,CAAC;AAI9C,qEAA2D;AAE3D;;;;;;;;;GASG;AACH;IAGE,2BAAoB,MAA+B;QAA/B,WAAM,GAAN,MAAM,CAAyB;QAF3C,gBAAW,GAAG,KAAK,CAAC;IAE0B,CAAC;IAEvD,kCAAM,GAAN,UAAO,KAAqB,EAAE,cAA8C;QAC1E,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,cAAc,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACtF,cAAc,CAAC,EAAE,IAAI,EAAE,uBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,+BAA+B;YAC/B,IAAM,UAAU,GAAG,IAAA,0CAA+B,EAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAEhG,sBAAsB;YACtB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YAEvC,0DAA0D;YAC1D,uDAAuD;YACvD,IAAA,wCAAc,EAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAEzC,cAAc,CAAC,EAAE,IAAI,EAAE,uBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2BAA2B;YAC3B,cAAc,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;YACzE,cAAc,CAAC,EAAE,IAAI,EAAE,uBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAEK,oCAAQ,GAAd;;;gBACE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,sBAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAC;;;KACnC;IAEK,sCAAU,GAAhB;;;gBACE,4DAA4D;gBAC5D,sBAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAC;;;KACnC;IACH,wBAAC;AAAD,CAAC,AAxCD,IAwCC;AAxCY,8CAAiB","sourcesContent":["import { ExportResultCode } from '@opentelemetry/core';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { createExportTraceServiceRequest } from '@opentelemetry/otlp-transformer/build/src/trace/internal';\nimport type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';\n\nimport { createInternalLogger } from '@grafana/faro-core';\n\nconst internalLogger = createInternalLogger();\n\nimport type { FaroTraceExporterConfig } from '../types';\n\nimport { sendFaroEvents } from './faroTraceExporter.utils';\n\n/**\n * FaroTraceExporter for React Native\n *\n * Exports OpenTelemetry spans to Faro backend using pushTraces API.\n *\n * IMPORTANT: To avoid infinite loops:\n * - Uses internalLogger instead of console\n * - Does NOT log during export (except errors)\n * - Relies on Faro's internal deduplication\n */\nexport class FaroTraceExporter implements SpanExporter {\n private _isShutdown = false;\n\n constructor(private config: FaroTraceExporterConfig) {}\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n if (this._isShutdown) {\n internalLogger.error('FaroTraceExporter: Cannot export spans, exporter is shut down');\n resultCallback({ code: ExportResultCode.FAILED });\n return;\n }\n\n try {\n // Convert spans to OTLP format\n const traceEvent = createExportTraceServiceRequest(spans, { useHex: true, useLongBits: false });\n\n // Send traces to Faro\n this.config.api.pushTraces(traceEvent);\n\n // Send Faro events for CLIENT spans (HTTP requests, etc.)\n // This is done WITHOUT logging to avoid infinite loops\n sendFaroEvents(traceEvent.resourceSpans);\n\n resultCallback({ code: ExportResultCode.SUCCESS });\n } catch (error) {\n // Only log critical errors\n internalLogger.error('FaroTraceExporter: Failed to export spans', error);\n resultCallback({ code: ExportResultCode.FAILED });\n }\n }\n\n async shutdown(): Promise<void> {\n this._isShutdown = true;\n return Promise.resolve(undefined);\n }\n\n async forceFlush(): Promise<void> {\n // No-op for now - spans are sent immediately via pushTraces\n return Promise.resolve(undefined);\n }\n}\n"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendFaroEvents = sendFaroEvents;
|
|
4
|
+
var internal_types_1 = require("@opentelemetry/otlp-transformer/build/src/trace/internal-types");
|
|
5
|
+
var faro_core_1 = require("@grafana/faro-core");
|
|
6
|
+
var internalLogger = (0, faro_core_1.createInternalLogger)();
|
|
7
|
+
var DURATION_NS_KEY = 'duration_ns';
|
|
8
|
+
/**
|
|
9
|
+
* Send Faro events for CLIENT spans (HTTP requests, navigation, etc.)
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: This function is called during trace export and must be careful to avoid infinite loops.
|
|
12
|
+
*
|
|
13
|
+
* Infinite loop prevention strategy:
|
|
14
|
+
* 1. Only process SPAN_KIND_CLIENT spans (HTTP requests, etc.)
|
|
15
|
+
* 2. Use faro.api.pushEvent which:
|
|
16
|
+
* - Does NOT trigger console logs if ConsoleInstrumentation is configured properly
|
|
17
|
+
* - Does NOT trigger HTTP instrumentation for collector URLs (they're ignored)
|
|
18
|
+
* 3. Use internalLogger for debugging instead of console
|
|
19
|
+
* 4. Never call console.log/warn/error in this function
|
|
20
|
+
*
|
|
21
|
+
* @param resourceSpans - OTLP resource spans from trace exporter
|
|
22
|
+
*/
|
|
23
|
+
function sendFaroEvents(resourceSpans) {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
if (resourceSpans === void 0) { resourceSpans = []; }
|
|
26
|
+
try {
|
|
27
|
+
for (var _i = 0, resourceSpans_1 = resourceSpans; _i < resourceSpans_1.length; _i++) {
|
|
28
|
+
var resourceSpan = resourceSpans_1[_i];
|
|
29
|
+
var scopeSpans = resourceSpan.scopeSpans;
|
|
30
|
+
for (var _c = 0, scopeSpans_1 = scopeSpans; _c < scopeSpans_1.length; _c++) {
|
|
31
|
+
var scopeSpan = scopeSpans_1[_c];
|
|
32
|
+
var scope = scopeSpan.scope, _d = scopeSpan.spans, spans = _d === void 0 ? [] : _d;
|
|
33
|
+
var _loop_1 = function (span) {
|
|
34
|
+
// Only process CLIENT spans (HTTP requests, external calls)
|
|
35
|
+
// This avoids processing internal spans which could cause loops
|
|
36
|
+
if (span.kind !== internal_types_1.ESpanKind.SPAN_KIND_CLIENT) {
|
|
37
|
+
return "continue";
|
|
38
|
+
}
|
|
39
|
+
var spanContext = {
|
|
40
|
+
traceId: span.traceId.toString(),
|
|
41
|
+
spanId: span.spanId.toString(),
|
|
42
|
+
};
|
|
43
|
+
var faroEventAttributes = {};
|
|
44
|
+
for (var _f = 0, _g = span.attributes; _f < _g.length; _f++) {
|
|
45
|
+
var attribute = _g[_f];
|
|
46
|
+
faroEventAttributes[attribute.key] = String(Object.values(attribute.value)[0]);
|
|
47
|
+
}
|
|
48
|
+
// Add span duration in nanoseconds
|
|
49
|
+
if (!Number.isNaN(span.endTimeUnixNano) && !Number.isNaN(span.startTimeUnixNano)) {
|
|
50
|
+
faroEventAttributes[DURATION_NS_KEY] = String(Number(span.endTimeUnixNano) - Number(span.startTimeUnixNano));
|
|
51
|
+
}
|
|
52
|
+
var index = ((_a = scope === null || scope === void 0 ? void 0 : scope.name) !== null && _a !== void 0 ? _a : '').indexOf('-');
|
|
53
|
+
var eventName = faro_core_1.unknownString;
|
|
54
|
+
if (scope === null || scope === void 0 ? void 0 : scope.name) {
|
|
55
|
+
if (index === -1) {
|
|
56
|
+
eventName = (_b = scope.name.split('/')[1]) !== null && _b !== void 0 ? _b : scope.name;
|
|
57
|
+
}
|
|
58
|
+
if (index > -1) {
|
|
59
|
+
eventName = scope === null || scope === void 0 ? void 0 : scope.name.substring(index + 1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Push event to Faro
|
|
63
|
+
// This should NOT cause infinite loops because:
|
|
64
|
+
// 1. The collector URL is in ignoreUrls for HttpInstrumentation
|
|
65
|
+
// 2. ConsoleInstrumentation won't log this event
|
|
66
|
+
// 3. No console.log calls are made here
|
|
67
|
+
faro_core_1.faro.api.pushEvent("faro.tracing.".concat(eventName), faroEventAttributes, undefined, {
|
|
68
|
+
spanContext: spanContext,
|
|
69
|
+
// Convert nanoseconds to milliseconds
|
|
70
|
+
timestampOverwriteMs: Number(span.endTimeUnixNano) / 1000000,
|
|
71
|
+
customPayloadTransformer: function (payload) {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
if (faroEventAttributes['faro.action.user.name'] != null &&
|
|
74
|
+
faroEventAttributes['faro.action.user.parentId'] != null) {
|
|
75
|
+
payload.action = {
|
|
76
|
+
name: faroEventAttributes['faro.action.user.name'],
|
|
77
|
+
parentId: faroEventAttributes['faro.action.user.parentId'],
|
|
78
|
+
};
|
|
79
|
+
(_a = payload.attributes) === null || _a === void 0 ? true : delete _a['faro.action.user.name'];
|
|
80
|
+
(_b = payload.attributes) === null || _b === void 0 ? true : delete _b['faro.action.user.parentId'];
|
|
81
|
+
}
|
|
82
|
+
return payload;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
for (var _e = 0, spans_1 = spans; _e < spans_1.length; _e++) {
|
|
87
|
+
var span = spans_1[_e];
|
|
88
|
+
_loop_1(span);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Only log critical errors using internal logger
|
|
95
|
+
// Do NOT use console.log here as it could cause infinite loops
|
|
96
|
+
internalLogger.error('sendFaroEvents: Failed to process spans', error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=faroTraceExporter.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faroTraceExporter.utils.js","sourceRoot":"","sources":["../../../src/exporters/faroTraceExporter.utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SpanContext } from '@opentelemetry/api';\nimport { ESpanKind, type IResourceSpans } from '@opentelemetry/otlp-transformer/build/src/trace/internal-types';\n\nimport { createInternalLogger, faro, unknownString } from '@grafana/faro-core';\nimport type { EventAttributes as FaroEventAttributes } from '@grafana/faro-core';\n\nconst internalLogger = createInternalLogger();\n\nconst DURATION_NS_KEY = 'duration_ns';\n\n/**\n * Send Faro events for CLIENT spans (HTTP requests, navigation, etc.)\n *\n * IMPORTANT: This function is called during trace export and must be careful to avoid infinite loops.\n *\n * Infinite loop prevention strategy:\n * 1. Only process SPAN_KIND_CLIENT spans (HTTP requests, etc.)\n * 2. Use faro.api.pushEvent which:\n * - Does NOT trigger console logs if ConsoleInstrumentation is configured properly\n * - Does NOT trigger HTTP instrumentation for collector URLs (they're ignored)\n * 3. Use internalLogger for debugging instead of console\n * 4. Never call console.log/warn/error in this function\n *\n * @param resourceSpans - OTLP resource spans from trace exporter\n */\nexport function sendFaroEvents(resourceSpans: IResourceSpans[] = []) {\n try {\n for (const resourceSpan of resourceSpans) {\n const { scopeSpans } = resourceSpan;\n\n for (const scopeSpan of scopeSpans) {\n const { scope, spans = [] } = scopeSpan;\n\n for (const span of spans) {\n // Only process CLIENT spans (HTTP requests, external calls)\n // This avoids processing internal spans which could cause loops\n if (span.kind !== ESpanKind.SPAN_KIND_CLIENT) {\n continue;\n }\n\n const spanContext: Pick<SpanContext, 'traceId' | 'spanId'> = {\n traceId: span.traceId.toString(),\n spanId: span.spanId.toString(),\n };\n\n const faroEventAttributes: FaroEventAttributes = {};\n for (const attribute of span.attributes) {\n faroEventAttributes[attribute.key] = String(Object.values(attribute.value)[0]);\n }\n\n // Add span duration in nanoseconds\n if (!Number.isNaN(span.endTimeUnixNano) && !Number.isNaN(span.startTimeUnixNano)) {\n faroEventAttributes[DURATION_NS_KEY] = String(\n Number(span.endTimeUnixNano) - Number(span.startTimeUnixNano)\n );\n }\n\n const index = (scope?.name ?? '').indexOf('-');\n let eventName = unknownString;\n\n if (scope?.name) {\n if (index === -1) {\n eventName = scope.name.split('/')[1] ?? scope.name;\n }\n\n if (index > -1) {\n eventName = scope?.name.substring(index + 1);\n }\n }\n\n // Push event to Faro\n // This should NOT cause infinite loops because:\n // 1. The collector URL is in ignoreUrls for HttpInstrumentation\n // 2. ConsoleInstrumentation won't log this event\n // 3. No console.log calls are made here\n faro.api.pushEvent(`faro.tracing.${eventName}`, faroEventAttributes, undefined, {\n spanContext,\n // Convert nanoseconds to milliseconds\n timestampOverwriteMs: Number(span.endTimeUnixNano) / 1_000_000,\n customPayloadTransformer: (payload) => {\n if (\n faroEventAttributes['faro.action.user.name'] != null &&\n faroEventAttributes['faro.action.user.parentId'] != null\n ) {\n payload.action = {\n name: faroEventAttributes['faro.action.user.name'],\n parentId: faroEventAttributes['faro.action.user.parentId'],\n };\n\n delete payload.attributes?.['faro.action.user.name'];\n delete payload.attributes?.['faro.action.user.parentId'];\n }\n\n return payload;\n },\n });\n }\n }\n }\n } catch (error) {\n // Only log critical errors using internal logger\n // Do NOT use console.log here as it could cause infinite loops\n internalLogger.error('sendFaroEvents: Failed to process spans', error);\n }\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FaroMetaAttributesSpanProcessor = exports.fetchCustomAttributeFunctionWithDefaults = exports.setSpanStatusOnFetchError = exports.getSamplingDecision = exports.TracingInstrumentation = exports.getDefaultOTELInstrumentations = exports.FaroTraceExporter = void 0;
|
|
4
|
+
var faroTraceExporter_1 = require("./exporters/faroTraceExporter");
|
|
5
|
+
Object.defineProperty(exports, "FaroTraceExporter", { enumerable: true, get: function () { return faroTraceExporter_1.FaroTraceExporter; } });
|
|
6
|
+
var getDefaultOTELInstrumentations_1 = require("./instrumentations/getDefaultOTELInstrumentations");
|
|
7
|
+
Object.defineProperty(exports, "getDefaultOTELInstrumentations", { enumerable: true, get: function () { return getDefaultOTELInstrumentations_1.getDefaultOTELInstrumentations; } });
|
|
8
|
+
var instrumentation_1 = require("./instrumentation");
|
|
9
|
+
Object.defineProperty(exports, "TracingInstrumentation", { enumerable: true, get: function () { return instrumentation_1.TracingInstrumentation; } });
|
|
10
|
+
var sampler_1 = require("./utils/sampler");
|
|
11
|
+
Object.defineProperty(exports, "getSamplingDecision", { enumerable: true, get: function () { return sampler_1.getSamplingDecision; } });
|
|
12
|
+
var instrumentationUtils_1 = require("./instrumentations/instrumentationUtils");
|
|
13
|
+
Object.defineProperty(exports, "setSpanStatusOnFetchError", { enumerable: true, get: function () { return instrumentationUtils_1.setSpanStatusOnFetchError; } });
|
|
14
|
+
Object.defineProperty(exports, "fetchCustomAttributeFunctionWithDefaults", { enumerable: true, get: function () { return instrumentationUtils_1.fetchCustomAttributeFunctionWithDefaults; } });
|
|
15
|
+
var faroMetaAttributesSpanProcessor_1 = require("./processors/faroMetaAttributesSpanProcessor");
|
|
16
|
+
Object.defineProperty(exports, "FaroMetaAttributesSpanProcessor", { enumerable: true, get: function () { return faroMetaAttributesSpanProcessor_1.FaroMetaAttributesSpanProcessor; } });
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,mEAAkE;AAAzD,sHAAA,iBAAiB,OAAA;AAE1B,oGAAmG;AAA1F,gJAAA,8BAA8B,OAAA;AAEvC,qDAA2D;AAAlD,yHAAA,sBAAsB,OAAA;AAE/B,2CAAsD;AAA7C,8GAAA,mBAAmB,OAAA;AAI5B,gFAGiD;AAF/C,iIAAA,yBAAyB,OAAA;AACzB,gJAAA,wCAAwC,OAAA;AAG1C,gGAA+F;AAAtF,kJAAA,+BAA+B,OAAA","sourcesContent":["export { FaroTraceExporter } from './exporters/faroTraceExporter';\n\nexport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\n\nexport { TracingInstrumentation } from './instrumentation';\n\nexport { getSamplingDecision } from './utils/sampler';\n\nexport type { FaroTraceExporterConfig, TracingInstrumentationOptions } from './types';\n\nexport {\n setSpanStatusOnFetchError,\n fetchCustomAttributeFunctionWithDefaults,\n} from './instrumentations/instrumentationUtils';\n\nexport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\n"]}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
28
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
+
function step(op) {
|
|
31
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
+
switch (op[0]) {
|
|
36
|
+
case 0: case 1: t = op; break;
|
|
37
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
+
default:
|
|
41
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
+
if (t[2]) _.ops.pop();
|
|
46
|
+
_.trys.pop(); continue;
|
|
47
|
+
}
|
|
48
|
+
op = body.call(thisArg, _);
|
|
49
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
54
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
55
|
+
if (ar || !(i in from)) {
|
|
56
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
57
|
+
ar[i] = from[i];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
61
|
+
};
|
|
62
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
+
exports.TracingInstrumentation = void 0;
|
|
64
|
+
var api_1 = require("@opentelemetry/api");
|
|
65
|
+
var instrumentation_1 = require("@opentelemetry/instrumentation");
|
|
66
|
+
var resources_1 = require("@opentelemetry/resources");
|
|
67
|
+
var sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
68
|
+
var semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
69
|
+
var faro_core_1 = require("@grafana/faro-core");
|
|
70
|
+
var faroTraceExporter_1 = require("./exporters/faroTraceExporter");
|
|
71
|
+
var getDefaultOTELInstrumentations_1 = require("./instrumentations/getDefaultOTELInstrumentations");
|
|
72
|
+
var faroMetaAttributesSpanProcessor_1 = require("./processors/faroMetaAttributesSpanProcessor");
|
|
73
|
+
var httpRequestMonitorSpanProcessor_1 = require("./processors/httpRequestMonitorSpanProcessor");
|
|
74
|
+
var semconv_1 = require("./semconv");
|
|
75
|
+
var sampler_1 = require("./utils/sampler");
|
|
76
|
+
// Import React Native TracerProvider
|
|
77
|
+
// Note: We use the base provider since React Native doesn't have a specific one
|
|
78
|
+
/**
|
|
79
|
+
* TracingInstrumentation for React Native
|
|
80
|
+
*
|
|
81
|
+
* Enables distributed tracing with OpenTelemetry for React Native applications.
|
|
82
|
+
*
|
|
83
|
+
* IMPORTANT: Infinite loop prevention
|
|
84
|
+
* - Uses internalLogger for debugging instead of console
|
|
85
|
+
* - Collector URLs are added to ignoreUrls in HTTP instrumentation
|
|
86
|
+
* - BatchSpanProcessor delays span export to avoid blocking
|
|
87
|
+
* - No console logging during trace export
|
|
88
|
+
*
|
|
89
|
+
* Example usage:
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { initializeFaro } from '@grafana/faro-react-native';
|
|
92
|
+
* import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';
|
|
93
|
+
*
|
|
94
|
+
* initializeFaro({
|
|
95
|
+
* // ... other config
|
|
96
|
+
* instrumentations: [
|
|
97
|
+
* new TracingInstrumentation({
|
|
98
|
+
* propagateTraceHeaderCorsUrls: [/https:\\/\\/my-api\\.com/],
|
|
99
|
+
* }),
|
|
100
|
+
* ],
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
var TracingInstrumentation = /** @class */ (function (_super) {
|
|
105
|
+
__extends(TracingInstrumentation, _super);
|
|
106
|
+
function TracingInstrumentation(options) {
|
|
107
|
+
if (options === void 0) { options = {}; }
|
|
108
|
+
var _this = _super.call(this) || this;
|
|
109
|
+
_this.options = options;
|
|
110
|
+
_this.name = '@grafana/faro-react-native-tracing';
|
|
111
|
+
_this.version = faro_core_1.VERSION;
|
|
112
|
+
return _this;
|
|
113
|
+
}
|
|
114
|
+
TracingInstrumentation.prototype.initialize = function () {
|
|
115
|
+
var _this = this;
|
|
116
|
+
var _a, _b, _c, _d;
|
|
117
|
+
var options = this.options;
|
|
118
|
+
var attributes = {};
|
|
119
|
+
// App attributes
|
|
120
|
+
if (this.config.app.name) {
|
|
121
|
+
attributes[semantic_conventions_1.ATTR_SERVICE_NAME] = this.config.app.name;
|
|
122
|
+
}
|
|
123
|
+
if (this.config.app.namespace) {
|
|
124
|
+
attributes[semconv_1.ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;
|
|
125
|
+
}
|
|
126
|
+
if (this.config.app.version) {
|
|
127
|
+
attributes[semantic_conventions_1.ATTR_SERVICE_VERSION] = this.config.app.version;
|
|
128
|
+
attributes[semconv_1.ATTR_APP_VERSION] = this.config.app.version;
|
|
129
|
+
}
|
|
130
|
+
if (this.config.app.environment) {
|
|
131
|
+
attributes[semconv_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)
|
|
134
|
+
* We need to keep this for compatibility with some internal services for now.
|
|
135
|
+
*/
|
|
136
|
+
attributes[semantic_conventions_1.SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;
|
|
137
|
+
}
|
|
138
|
+
// Device/Platform attributes from React Native
|
|
139
|
+
// Note: metas.value contains all meta providers, we need to check if device meta exists
|
|
140
|
+
var allMetas = this.metas.value;
|
|
141
|
+
var deviceMeta = allMetas['device'];
|
|
142
|
+
if (deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.model) {
|
|
143
|
+
attributes[semconv_1.ATTR_DEVICE_MODEL] = deviceMeta.model;
|
|
144
|
+
}
|
|
145
|
+
if (deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.brand) {
|
|
146
|
+
attributes[semconv_1.ATTR_DEVICE_BRAND] = deviceMeta.brand;
|
|
147
|
+
}
|
|
148
|
+
if (deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.osName) {
|
|
149
|
+
attributes[semconv_1.ATTR_DEVICE_PLATFORM] = deviceMeta.osName;
|
|
150
|
+
}
|
|
151
|
+
if (deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.osVersion) {
|
|
152
|
+
attributes[semconv_1.ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;
|
|
153
|
+
}
|
|
154
|
+
if (deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.locale) {
|
|
155
|
+
attributes[semconv_1.ATTR_DEVICE_LOCALE] = deviceMeta.locale;
|
|
156
|
+
}
|
|
157
|
+
attributes[semconv_1.ATTR_PROCESS_RUNTIME_NAME] = 'react-native';
|
|
158
|
+
attributes[semconv_1.ATTR_PROCESS_RUNTIME_VERSION] = (_a = deviceMeta === null || deviceMeta === void 0 ? void 0 : deviceMeta.osVersion) !== null && _a !== void 0 ? _a : 'unknown';
|
|
159
|
+
attributes[semconv_1.ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';
|
|
160
|
+
attributes[semconv_1.ATTR_TELEMETRY_DISTRO_VERSION] = faro_core_1.VERSION;
|
|
161
|
+
// Merge with user-provided attributes
|
|
162
|
+
Object.assign(attributes, options.resourceAttributes);
|
|
163
|
+
var resource = (0, resources_1.defaultResource)().merge((0, resources_1.resourceFromAttributes)(attributes));
|
|
164
|
+
// Create tracer provider with span processors
|
|
165
|
+
this.provider = new sdk_trace_base_1.BasicTracerProvider({
|
|
166
|
+
resource: resource,
|
|
167
|
+
sampler: {
|
|
168
|
+
shouldSample: function () {
|
|
169
|
+
return {
|
|
170
|
+
decision: (0, sampler_1.getSamplingDecision)(_this.api.getSession()),
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
spanProcessors: [
|
|
175
|
+
(_b = options.spanProcessor) !== null && _b !== void 0 ? _b : new httpRequestMonitorSpanProcessor_1.HttpRequestMonitorSpanProcessor(new faroMetaAttributesSpanProcessor_1.FaroMetaAttributesSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new faroTraceExporter_1.FaroTraceExporter({ api: this.api }), {
|
|
176
|
+
scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,
|
|
177
|
+
maxExportBatchSize: 30,
|
|
178
|
+
}), this.metas)),
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
// Register the provider as the global tracer provider
|
|
182
|
+
// This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
|
|
183
|
+
api_1.trace.setGlobalTracerProvider(this.provider);
|
|
184
|
+
var _e = (_c = this.options.instrumentationOptions) !== null && _c !== void 0 ? _c : {}, propagateTraceHeaderCorsUrls = _e.propagateTraceHeaderCorsUrls, fetchInstrumentationOptions = _e.fetchInstrumentationOptions, xhrInstrumentationOptions = _e.xhrInstrumentationOptions;
|
|
185
|
+
// Get ignore URLs from transports to prevent infinite loops
|
|
186
|
+
var ignoreUrls = this.getIgnoreUrls();
|
|
187
|
+
// Register instrumentations
|
|
188
|
+
(0, instrumentation_1.registerInstrumentations)({
|
|
189
|
+
instrumentations: (_d = options.instrumentations) !== null && _d !== void 0 ? _d : (0, getDefaultOTELInstrumentations_1.getDefaultOTELInstrumentations)({
|
|
190
|
+
ignoreUrls: ignoreUrls,
|
|
191
|
+
propagateTraceHeaderCorsUrls: propagateTraceHeaderCorsUrls,
|
|
192
|
+
fetchInstrumentationOptions: fetchInstrumentationOptions,
|
|
193
|
+
xhrInstrumentationOptions: xhrInstrumentationOptions,
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
// Expose OTEL API on the global Faro instance for manual span creation
|
|
197
|
+
// This allows users to access trace and context APIs via faro.otel
|
|
198
|
+
var globalFaroInstance = (0, faro_core_1.getInternalFaroFromGlobalObject)();
|
|
199
|
+
if (globalFaroInstance) {
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API
|
|
201
|
+
globalFaroInstance.otel = {
|
|
202
|
+
trace: api_1.trace,
|
|
203
|
+
context: api_1.context,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Get ignore URLs from all transports to avoid tracing collector requests
|
|
209
|
+
* CRITICAL: This prevents infinite loops where trace exports trigger more traces
|
|
210
|
+
*/
|
|
211
|
+
TracingInstrumentation.prototype.getIgnoreUrls = function () {
|
|
212
|
+
// Get URLs from transports' getIgnoreUrls() method
|
|
213
|
+
var transportUrls = this.transports.transports.flatMap(function (transport) {
|
|
214
|
+
return transport.getIgnoreUrls();
|
|
215
|
+
});
|
|
216
|
+
// Create regex patterns that match both with and without trailing slashes
|
|
217
|
+
// This is critical because fetch() might add trailing slashes
|
|
218
|
+
var regexPatterns = transportUrls.map(function (url) {
|
|
219
|
+
if (typeof url === 'string') {
|
|
220
|
+
// Escape special regex characters and make trailing slash optional
|
|
221
|
+
var escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
222
|
+
return new RegExp("^".concat(escapedUrl, "/?$"));
|
|
223
|
+
}
|
|
224
|
+
return url;
|
|
225
|
+
});
|
|
226
|
+
// Return both original URLs and regex patterns for maximum coverage
|
|
227
|
+
return __spreadArray(__spreadArray([], transportUrls, true), regexPatterns, true);
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Shutdown the tracer provider
|
|
231
|
+
*/
|
|
232
|
+
TracingInstrumentation.prototype.shutdown = function () {
|
|
233
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
234
|
+
return __generator(this, function (_a) {
|
|
235
|
+
switch (_a.label) {
|
|
236
|
+
case 0:
|
|
237
|
+
if (!this.provider) return [3 /*break*/, 2];
|
|
238
|
+
return [4 /*yield*/, this.provider.shutdown()];
|
|
239
|
+
case 1:
|
|
240
|
+
_a.sent();
|
|
241
|
+
_a.label = 2;
|
|
242
|
+
case 2: return [2 /*return*/];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS = 1000;
|
|
248
|
+
return TracingInstrumentation;
|
|
249
|
+
}(faro_core_1.BaseInstrumentation));
|
|
250
|
+
exports.TracingInstrumentation = TracingInstrumentation;
|
|
251
|
+
//# sourceMappingURL=instrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from '@opentelemetry/semantic-conventions';\n\nimport { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';\nimport type { Transport } from '@grafana/faro-core';\n\nimport { FaroTraceExporter } from './exporters/faroTraceExporter';\nimport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\nimport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\nimport { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';\nimport {\n ATTR_APP_VERSION,\n ATTR_DEPLOYMENT_ENVIRONMENT_NAME,\n ATTR_DEVICE_BRAND,\n ATTR_DEVICE_LOCALE,\n ATTR_DEVICE_MODEL,\n ATTR_DEVICE_OS_VERSION,\n ATTR_DEVICE_PLATFORM,\n ATTR_PROCESS_RUNTIME_NAME,\n ATTR_PROCESS_RUNTIME_VERSION,\n ATTR_SERVICE_NAMESPACE,\n ATTR_TELEMETRY_DISTRO_NAME,\n ATTR_TELEMETRY_DISTRO_VERSION,\n} from './semconv';\nimport type { TracingInstrumentationOptions } from './types';\nimport { getSamplingDecision } from './utils/sampler';\n\n// Import React Native TracerProvider\n// Note: We use the base provider since React Native doesn't have a specific one\n\n/**\n * TracingInstrumentation for React Native\n *\n * Enables distributed tracing with OpenTelemetry for React Native applications.\n *\n * IMPORTANT: Infinite loop prevention\n * - Uses internalLogger for debugging instead of console\n * - Collector URLs are added to ignoreUrls in HTTP instrumentation\n * - BatchSpanProcessor delays span export to avoid blocking\n * - No console logging during trace export\n *\n * Example usage:\n * ```ts\n * import { initializeFaro } from '@grafana/faro-react-native';\n * import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';\n *\n * initializeFaro({\n * // ... other config\n * instrumentations: [\n * new TracingInstrumentation({\n * propagateTraceHeaderCorsUrls: [/https:\\\\/\\\\/my-api\\\\.com/],\n * }),\n * ],\n * });\n * ```\n */\nexport class TracingInstrumentation extends BaseInstrumentation {\n name = '@grafana/faro-react-native-tracing';\n version = VERSION;\n\n static SCHEDULED_BATCH_DELAY_MS = 1000;\n\n private provider?: BasicTracerProvider;\n\n constructor(private options: TracingInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n const options = this.options;\n const attributes: Attributes = {};\n\n // App attributes\n if (this.config.app.name) {\n attributes[ATTR_SERVICE_NAME] = this.config.app.name;\n }\n\n if (this.config.app.namespace) {\n attributes[ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;\n }\n\n if (this.config.app.version) {\n attributes[ATTR_SERVICE_VERSION] = this.config.app.version;\n attributes[ATTR_APP_VERSION] = this.config.app.version;\n }\n\n if (this.config.app.environment) {\n attributes[ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;\n\n /**\n * @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)\n * We need to keep this for compatibility with some internal services for now.\n */\n attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;\n }\n\n // Device/Platform attributes from React Native\n // Note: metas.value contains all meta providers, we need to check if device meta exists\n const allMetas = this.metas.value as Record<string, unknown>;\n const deviceMeta = allMetas['device'] as\n | {\n model?: string;\n brand?: string;\n osName?: string;\n osVersion?: string;\n locale?: string;\n }\n | undefined;\n\n if (deviceMeta?.model) {\n attributes[ATTR_DEVICE_MODEL] = deviceMeta.model;\n }\n\n if (deviceMeta?.brand) {\n attributes[ATTR_DEVICE_BRAND] = deviceMeta.brand;\n }\n\n if (deviceMeta?.osName) {\n attributes[ATTR_DEVICE_PLATFORM] = deviceMeta.osName;\n }\n\n if (deviceMeta?.osVersion) {\n attributes[ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;\n }\n\n if (deviceMeta?.locale) {\n attributes[ATTR_DEVICE_LOCALE] = deviceMeta.locale;\n }\n\n attributes[ATTR_PROCESS_RUNTIME_NAME] = 'react-native';\n attributes[ATTR_PROCESS_RUNTIME_VERSION] = deviceMeta?.osVersion ?? 'unknown';\n\n attributes[ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';\n attributes[ATTR_TELEMETRY_DISTRO_VERSION] = VERSION;\n\n // Merge with user-provided attributes\n Object.assign(attributes, options.resourceAttributes);\n\n const resource = defaultResource().merge(resourceFromAttributes(attributes));\n\n // Create tracer provider with span processors\n this.provider = new ReactNativeTracerProvider({\n resource,\n sampler: {\n shouldSample: () => {\n return {\n decision: getSamplingDecision(this.api.getSession()),\n };\n },\n },\n spanProcessors: [\n options.spanProcessor ??\n new HttpRequestMonitorSpanProcessor(\n new FaroMetaAttributesSpanProcessor(\n new BatchSpanProcessor(new FaroTraceExporter({ api: this.api }), {\n scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,\n maxExportBatchSize: 30,\n }),\n this.metas\n )\n ),\n ],\n });\n\n // Register the provider as the global tracer provider\n // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros\n trace.setGlobalTracerProvider(this.provider);\n\n const { propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions } =\n this.options.instrumentationOptions ?? {};\n\n // Get ignore URLs from transports to prevent infinite loops\n const ignoreUrls = this.getIgnoreUrls();\n\n // Register instrumentations\n registerInstrumentations({\n instrumentations:\n options.instrumentations ??\n getDefaultOTELInstrumentations({\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n }),\n });\n\n // Expose OTEL API on the global Faro instance for manual span creation\n // This allows users to access trace and context APIs via faro.otel\n const globalFaroInstance = getInternalFaroFromGlobalObject();\n if (globalFaroInstance) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API\n (globalFaroInstance as any).otel = {\n trace,\n context,\n };\n }\n }\n\n /**\n * Get ignore URLs from all transports to avoid tracing collector requests\n * CRITICAL: This prevents infinite loops where trace exports trigger more traces\n */\n private getIgnoreUrls(): Array<string | RegExp> {\n // Get URLs from transports' getIgnoreUrls() method\n const transportUrls = this.transports.transports.flatMap((transport: Transport) => {\n return transport.getIgnoreUrls();\n });\n\n // Create regex patterns that match both with and without trailing slashes\n // This is critical because fetch() might add trailing slashes\n const regexPatterns = transportUrls.map((url) => {\n if (typeof url === 'string') {\n // Escape special regex characters and make trailing slash optional\n const escapedUrl = url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${escapedUrl}/?$`);\n }\n return url;\n });\n\n // Return both original URLs and regex patterns for maximum coverage\n return [...transportUrls, ...regexPatterns];\n }\n\n /**\n * Shutdown the tracer provider\n */\n async shutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.shutdown();\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
14
|
+
var t = {};
|
|
15
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
16
|
+
t[p] = s[p];
|
|
17
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
18
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
19
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
20
|
+
t[p[i]] = s[p[i]];
|
|
21
|
+
}
|
|
22
|
+
return t;
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.getDefaultOTELInstrumentations = getDefaultOTELInstrumentations;
|
|
26
|
+
var instrumentation_fetch_1 = require("@opentelemetry/instrumentation-fetch");
|
|
27
|
+
var instrumentation_xml_http_request_1 = require("@opentelemetry/instrumentation-xml-http-request");
|
|
28
|
+
var faro_core_1 = require("@grafana/faro-core");
|
|
29
|
+
var instrumentationUtils_1 = require("./instrumentationUtils");
|
|
30
|
+
/**
|
|
31
|
+
* Get default OTEL instrumentations for React Native
|
|
32
|
+
*
|
|
33
|
+
* This function creates the default OpenTelemetry instrumentations for React Native:
|
|
34
|
+
* - FetchInstrumentation: Traces fetch() API calls
|
|
35
|
+
* - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)
|
|
36
|
+
*
|
|
37
|
+
* IMPORTANT: Infinite loop prevention
|
|
38
|
+
* - ignoreUrls is used to exclude Faro collector URLs
|
|
39
|
+
* - ignoreNetworkEvents is true to avoid duplicate events
|
|
40
|
+
* - No console logging during instrumentation
|
|
41
|
+
*
|
|
42
|
+
* @param options - Configuration options
|
|
43
|
+
* @returns Array of OTEL instrumentations
|
|
44
|
+
*/
|
|
45
|
+
function getDefaultOTELInstrumentations(options) {
|
|
46
|
+
if (options === void 0) { options = {}; }
|
|
47
|
+
var fetchInstrumentationOptions = options.fetchInstrumentationOptions, xhrInstrumentationOptions = options.xhrInstrumentationOptions, sharedOptions = __rest(options, ["fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
|
|
48
|
+
var fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);
|
|
49
|
+
var xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);
|
|
50
|
+
return [new instrumentation_fetch_1.FetchInstrumentation(fetchOpts), new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(xhrOpts)];
|
|
51
|
+
}
|
|
52
|
+
function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
|
|
53
|
+
return __assign(__assign(__assign(__assign({}, sharedOptions), {
|
|
54
|
+
// Ignore network performance events to avoid duplicates
|
|
55
|
+
ignoreNetworkEvents: true }), fetchInstrumentationOptions), {
|
|
56
|
+
// Always keep this function
|
|
57
|
+
applyCustomAttributesOnSpan: (0, instrumentationUtils_1.fetchCustomAttributeFunctionWithDefaults)(fetchInstrumentationOptions === null || fetchInstrumentationOptions === void 0 ? void 0 : fetchInstrumentationOptions.applyCustomAttributesOnSpan),
|
|
58
|
+
// Request hook to add user action context
|
|
59
|
+
requestHook: function (span, _) {
|
|
60
|
+
try {
|
|
61
|
+
var currentAction = faro_core_1.faro.api.getActiveUserAction();
|
|
62
|
+
if (currentAction &&
|
|
63
|
+
(currentAction === null || currentAction === void 0 ? void 0 : currentAction.getState()) === faro_core_1.UserActionState.Started) {
|
|
64
|
+
span.setAttribute('faro.action.user.name', currentAction.name);
|
|
65
|
+
span.setAttribute('faro.action.user.parentId', currentAction.parentId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (_error) {
|
|
69
|
+
// Silently fail - don't log to avoid infinite loops
|
|
70
|
+
// The span will just not have user action context
|
|
71
|
+
}
|
|
72
|
+
} });
|
|
73
|
+
}
|
|
74
|
+
function createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions) {
|
|
75
|
+
return __assign(__assign(__assign(__assign({}, sharedOptions), { ignoreNetworkEvents: true }), xhrInstrumentationOptions), { applyCustomAttributesOnSpan: (0, instrumentationUtils_1.xhrCustomAttributeFunctionWithDefaults)(xhrInstrumentationOptions === null || xhrInstrumentationOptions === void 0 ? void 0 : xhrInstrumentationOptions.applyCustomAttributesOnSpan) });
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=getDefaultOTELInstrumentations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,wEAOC;AAlCD,8EAA4E;AAC5E,oGAAgG;AAEhG,gDAA6F;AAI7F,+DAGgC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,8BAA8B,CAAC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IACjF,IAAA,2BAA2B,GAAkD,OAAO,4BAAzD,EAAE,yBAAyB,GAAuB,OAAO,0BAA9B,EAAK,aAAa,UAAK,OAAO,EAAtF,4DAA4E,CAAF,CAAa;IAE7F,IAAM,SAAS,GAAG,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;IAChG,IAAM,OAAO,GAAG,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC;IAE1F,OAAO,CAAC,IAAI,4CAAoB,CAAC,SAAS,CAAC,EAAE,IAAI,gEAA6B,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,+CACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,IAAA,+DAAwC,EACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,UAAC,IAAU,EAAE,CAAwB;YAChD,IAAI,CAAC;gBACH,IAAM,aAAa,GAAG,gBAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,2BAAe,CAAC,OAAO,EACjG,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;oBAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,oDAAoD;gBACpD,kDAAkD;YACpD,CAAC;QACH,CAAC,IACD;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,yBAAsF,EACtF,aAAsC;IAEtC,+CACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,IAAA,6DAAsC,EACjE,yBAAyB,aAAzB,yBAAyB,uBAAzB,yBAAyB,CAAE,2BAA2B,CACvD,IACD;AACJ,CAAC","sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n fetchCustomAttributeFunctionWithDefaults,\n xhrCustomAttributeFunctionWithDefaults,\n} from './instrumentationUtils';\n\n/**\n * Get default OTEL instrumentations for React Native\n *\n * This function creates the default OpenTelemetry instrumentations for React Native:\n * - FetchInstrumentation: Traces fetch() API calls\n * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)\n *\n * IMPORTANT: Infinite loop prevention\n * - ignoreUrls is used to exclude Faro collector URLs\n * - ignoreNetworkEvents is true to avoid duplicate events\n * - No console logging during instrumentation\n *\n * @param options - Configuration options\n * @returns Array of OTEL instrumentations\n */\nexport function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {\n const { fetchInstrumentationOptions, xhrInstrumentationOptions, ...sharedOptions } = options;\n\n const fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);\n const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);\n\n return [new FetchInstrumentation(fetchOpts), new XMLHttpRequestInstrumentation(xhrOpts)];\n}\n\nfunction createFetchInstrumentationOptions(\n fetchInstrumentationOptions: DefaultInstrumentationsOptions['fetchInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n // Ignore network performance events to avoid duplicates\n ignoreNetworkEvents: true,\n // Keep this here to overwrite the defaults above if provided by the users\n ...fetchInstrumentationOptions,\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Request hook to add user action context\n requestHook: (span: Span, _: Request | RequestInit) => {\n try {\n const currentAction = faro.api.getActiveUserAction();\n if (\n currentAction &&\n (currentAction as unknown as UserActionInternalInterface)?.getState() === UserActionState.Started\n ) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_error) {\n // Silently fail - don't log to avoid infinite loops\n // The span will just not have user action context\n }\n },\n };\n}\n\nfunction createXhrInstrumentationOptions(\n xhrInstrumentationOptions: DefaultInstrumentationsOptions['xhrInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n ignoreNetworkEvents: true,\n ...xhrInstrumentationOptions,\n applyCustomAttributesOnSpan: xhrCustomAttributeFunctionWithDefaults(\n xhrInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n };\n}\n"]}
|