@grafana/faro-react-native-tracing 1.0.0 → 1.2.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/README.md +88 -8
- package/dist/cjs/instrumentation.js +23 -5
- package/dist/cjs/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/devServerIgnoreUrls.js +10 -0
- package/dist/cjs/instrumentations/devServerIgnoreUrls.js.map +1 -0
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +10 -5
- package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/instrumentation.js +24 -6
- package/dist/esm/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/devServerIgnoreUrls.js +7 -0
- package/dist/esm/instrumentations/devServerIgnoreUrls.js.map +1 -0
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +10 -5
- package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/types/instrumentations/devServerIgnoreUrls.d.ts +2 -0
- package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +1 -1
- package/dist/types/types.d.ts +2 -0
- package/package.json +6 -5
- package/src/instrumentation.ts +33 -5
- package/src/instrumentations/devServerIgnoreUrls.test.ts +23 -0
- package/src/instrumentations/devServerIgnoreUrls.ts +9 -0
- package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +31 -5
- package/src/instrumentations/getDefaultOTELInstrumentations.ts +22 -5
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -41,11 +41,63 @@ const faro = initializeFaro({
|
|
|
41
41
|
|
|
42
42
|
That's it! HTTP requests via `fetch()` are now automatically traced (and correlated with user actions when user action tracking is enabled) and sent to your Faro collector.
|
|
43
43
|
|
|
44
|
+
## Fetch vs XHR in React Native
|
|
45
|
+
|
|
46
|
+
By default, this package traces `fetch()` requests only.
|
|
47
|
+
|
|
48
|
+
React Native implements `fetch()` on top of `XMLHttpRequest`. If both fetch and XHR instrumentation are enabled for the same URL, one logical `fetch()` request can produce two spans: one from `FetchInstrumentation` and one from the underlying XHR layer. To avoid duplicate HTTP telemetry, XHR tracing is disabled by default.
|
|
49
|
+
|
|
50
|
+
If your app uses axios or direct `XMLHttpRequest` calls instead of `fetch()`, enable XHR tracing explicitly:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
initializeFaro({
|
|
54
|
+
enableTracing: true,
|
|
55
|
+
tracingOptions: {
|
|
56
|
+
instrumentationOptions: {
|
|
57
|
+
enableXhrInstrumentation: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If your app uses both `fetch()` and axios/XHR, avoid tracing the same request twice. Either trace one API surface only:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
initializeFaro({
|
|
67
|
+
enableTracing: true,
|
|
68
|
+
tracingOptions: {
|
|
69
|
+
instrumentationOptions: {
|
|
70
|
+
// Useful for axios/direct XHR apps.
|
|
71
|
+
enableFetchInstrumentation: false,
|
|
72
|
+
enableXhrInstrumentation: true,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or enable both and use `ignoreUrls` on one instrumentation so URL patterns do not overlap:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
initializeFaro({
|
|
82
|
+
enableTracing: true,
|
|
83
|
+
tracingOptions: {
|
|
84
|
+
instrumentationOptions: {
|
|
85
|
+
enableXhrInstrumentation: true,
|
|
86
|
+
xhrInstrumentationOptions: {
|
|
87
|
+
// Example: fetch() handles your API calls, so ignore those URLs in XHR.
|
|
88
|
+
ignoreUrls: [/\/api\//],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
44
95
|
## Features
|
|
45
96
|
|
|
46
97
|
### 🚀 **Automatic Tracing**
|
|
47
98
|
|
|
48
|
-
- **Fetch Instrumentation**:
|
|
99
|
+
- **Fetch Instrumentation**: `fetch()` requests are automatically traced with no code changes
|
|
100
|
+
- **Optional XHR Instrumentation**: Enable for axios or direct `XMLHttpRequest` calls
|
|
49
101
|
- **Session Correlation**: Traces are correlated with Faro sessions for complete user journey tracking
|
|
50
102
|
- **User Action Correlation**: Traces are correlated with Faro user actions (when user action tracking is enabled)
|
|
51
103
|
- **User Context**: User information is automatically added to span attributes
|
|
@@ -114,6 +166,10 @@ new TracingInstrumentation({
|
|
|
114
166
|
// URLs that should receive trace context headers
|
|
115
167
|
propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/, 'https://other-api.com'],
|
|
116
168
|
|
|
169
|
+
// Optional: enable XHR tracing for apps that use axios or XHR directly.
|
|
170
|
+
// Disabled by default because React Native's current fetch implementation is backed by XHR.
|
|
171
|
+
enableXhrInstrumentation: false,
|
|
172
|
+
|
|
117
173
|
// Optional: Customize fetch instrumentation
|
|
118
174
|
fetchInstrumentationOptions: {
|
|
119
175
|
// Ignore network performance events (default: true)
|
|
@@ -157,10 +213,10 @@ new TracingInstrumentation({
|
|
|
157
213
|
|
|
158
214
|
### Automatic HTTP Tracing
|
|
159
215
|
|
|
160
|
-
|
|
216
|
+
`fetch()` requests are automatically traced with no code changes:
|
|
161
217
|
|
|
162
218
|
```typescript
|
|
163
|
-
// This fetch call is automatically traced
|
|
219
|
+
// This fetch call is automatically traced by default.
|
|
164
220
|
const response = await fetch('https://api.example.com/users');
|
|
165
221
|
const data = await response.json();
|
|
166
222
|
|
|
@@ -172,6 +228,23 @@ const data = await response.json();
|
|
|
172
228
|
// - Device metadata
|
|
173
229
|
```
|
|
174
230
|
|
|
231
|
+
### Axios and Direct XHR Tracing
|
|
232
|
+
|
|
233
|
+
Axios and direct `XMLHttpRequest` calls require XHR instrumentation:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
initializeFaro({
|
|
237
|
+
enableTracing: true,
|
|
238
|
+
tracingOptions: {
|
|
239
|
+
instrumentationOptions: {
|
|
240
|
+
enableXhrInstrumentation: true,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
If the same app also uses `fetch()` for the same backend URLs, add `ignoreUrls` to either fetch or XHR instrumentation to avoid duplicate spans.
|
|
247
|
+
|
|
175
248
|
**Trace includes:**
|
|
176
249
|
|
|
177
250
|
- `http.method`: `GET`
|
|
@@ -359,8 +432,8 @@ function performOperation() {
|
|
|
359
432
|
│ fetch() / XHR calls
|
|
360
433
|
↓
|
|
361
434
|
┌─────────────────────────────────────────────────────────────┐
|
|
362
|
-
│ FetchInstrumentation
|
|
363
|
-
│ • Intercepts fetch()
|
|
435
|
+
│ FetchInstrumentation, optionally XMLHttpRequest │
|
|
436
|
+
│ • Intercepts fetch() by default │
|
|
364
437
|
│ • Creates spans automatically │
|
|
365
438
|
│ • Propagates W3C Trace Context headers │
|
|
366
439
|
└──────────────────────┬──────────────────────────────────────┘
|
|
@@ -408,7 +481,7 @@ function performOperation() {
|
|
|
408
481
|
|
|
409
482
|
### Span Lifecycle
|
|
410
483
|
|
|
411
|
-
1. **Span Creation**: FetchInstrumentation
|
|
484
|
+
1. **Span Creation**: FetchInstrumentation creates a span when fetch() is called; XMLHttpRequestInstrumentation can be enabled for XHR/axios apps. W3C Trace Context headers are added to the request if the URL matches `propagateTraceHeaderCorsUrls`.
|
|
412
485
|
2. **Enrichment**: HttpRequestMonitorSpanProcessor notifies for user action correlation; FaroMetaAttributesSpanProcessor adds session and user to spans (device/service come from resource)
|
|
413
486
|
3. **Batching**: BatchSpanProcessor collects spans (max 30 spans or 1000ms delay)
|
|
414
487
|
4. **Export**: FaroTraceExporter converts spans to OTLP format; also sends Faro events (e.g. `faro.tracing.fetch`) for CLIENT spans
|
|
@@ -667,10 +740,10 @@ interface TracingInstrumentationOptions {
|
|
|
667
740
|
// Custom OTEL propagator (default: W3CTraceContextPropagator)
|
|
668
741
|
propagator?: TextMapPropagator;
|
|
669
742
|
|
|
670
|
-
// Custom OTEL context manager (default:
|
|
743
|
+
// Custom OTEL context manager (default: StackContextManager)
|
|
671
744
|
contextManager?: ContextManager;
|
|
672
745
|
|
|
673
|
-
// Custom OTEL instrumentations (replaces default
|
|
746
|
+
// Custom OTEL instrumentations (replaces default fetch/XHR instrumentations)
|
|
674
747
|
instrumentations?: Instrumentation[];
|
|
675
748
|
|
|
676
749
|
// Custom span processor (replaces default BatchSpanProcessor)
|
|
@@ -681,6 +754,12 @@ interface TracingInstrumentationOptions {
|
|
|
681
754
|
// URLs to propagate trace headers to
|
|
682
755
|
propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
|
|
683
756
|
|
|
757
|
+
// Enable fetch instrumentation (default: true)
|
|
758
|
+
enableFetchInstrumentation?: boolean;
|
|
759
|
+
|
|
760
|
+
// Enable XHR instrumentation (default: false)
|
|
761
|
+
enableXhrInstrumentation?: boolean;
|
|
762
|
+
|
|
684
763
|
// Fetch instrumentation options
|
|
685
764
|
fetchInstrumentationOptions?: {
|
|
686
765
|
// Custom attributes function
|
|
@@ -716,6 +795,7 @@ function getDefaultOTELInstrumentations(options?: DefaultInstrumentationsOptions
|
|
|
716
795
|
Currently returns:
|
|
717
796
|
|
|
718
797
|
- `FetchInstrumentation` - Automatic HTTP tracing
|
|
798
|
+
- `XMLHttpRequestInstrumentation` - Optional; enabled with `enableXhrInstrumentation: true`
|
|
719
799
|
|
|
720
800
|
### faro.otel
|
|
721
801
|
|
|
@@ -62,12 +62,15 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
62
62
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
63
|
exports.TracingInstrumentation = void 0;
|
|
64
64
|
var api_1 = require("@opentelemetry/api");
|
|
65
|
+
var core_1 = require("@opentelemetry/core");
|
|
65
66
|
var instrumentation_1 = require("@opentelemetry/instrumentation");
|
|
66
67
|
var resources_1 = require("@opentelemetry/resources");
|
|
67
68
|
var sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
69
|
+
var sdk_trace_web_1 = require("@opentelemetry/sdk-trace-web");
|
|
68
70
|
var semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
69
71
|
var faro_core_1 = require("@grafana/faro-core");
|
|
70
72
|
var faroTraceExporter_1 = require("./exporters/faroTraceExporter");
|
|
73
|
+
var devServerIgnoreUrls_1 = require("./instrumentations/devServerIgnoreUrls");
|
|
71
74
|
var getDefaultOTELInstrumentations_1 = require("./instrumentations/getDefaultOTELInstrumentations");
|
|
72
75
|
var faroMetaAttributesSpanProcessor_1 = require("./processors/faroMetaAttributesSpanProcessor");
|
|
73
76
|
var httpRequestMonitorSpanProcessor_1 = require("./processors/httpRequestMonitorSpanProcessor");
|
|
@@ -113,7 +116,7 @@ var TracingInstrumentation = /** @class */ (function (_super) {
|
|
|
113
116
|
}
|
|
114
117
|
TracingInstrumentation.prototype.initialize = function () {
|
|
115
118
|
var _this = this;
|
|
116
|
-
var _a, _b, _c, _d;
|
|
119
|
+
var _a, _b, _c, _d, _e, _f;
|
|
117
120
|
var options = this.options;
|
|
118
121
|
var attributes = {};
|
|
119
122
|
// App attributes
|
|
@@ -181,13 +184,28 @@ var TracingInstrumentation = /** @class */ (function (_super) {
|
|
|
181
184
|
// Register the provider as the global tracer provider
|
|
182
185
|
// This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
|
|
183
186
|
api_1.trace.setGlobalTracerProvider(this.provider);
|
|
184
|
-
|
|
187
|
+
// Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
|
|
188
|
+
// which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
|
|
189
|
+
// `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
|
|
190
|
+
// wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
|
|
191
|
+
// `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
|
|
192
|
+
api_1.context.setGlobalContextManager((_c = options.contextManager) !== null && _c !== void 0 ? _c : new sdk_trace_web_1.StackContextManager().enable());
|
|
193
|
+
// Register the global text-map propagator. Without this, OTel falls back to a
|
|
194
|
+
// NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
|
|
195
|
+
// `traceparent` / `tracestate` (and `baggage`) headers are never written on the
|
|
196
|
+
// outbound fetch/XHR — so the backend receives no context and starts a new trace.
|
|
197
|
+
api_1.propagation.setGlobalPropagator((_d = options.propagator) !== null && _d !== void 0 ? _d : new core_1.CompositePropagator({
|
|
198
|
+
propagators: [new core_1.W3CTraceContextPropagator(), new core_1.W3CBaggagePropagator()],
|
|
199
|
+
}));
|
|
200
|
+
var _g = (_e = this.options.instrumentationOptions) !== null && _e !== void 0 ? _e : {}, enableFetchInstrumentation = _g.enableFetchInstrumentation, enableXhrInstrumentation = _g.enableXhrInstrumentation, propagateTraceHeaderCorsUrls = _g.propagateTraceHeaderCorsUrls, fetchInstrumentationOptions = _g.fetchInstrumentationOptions, xhrInstrumentationOptions = _g.xhrInstrumentationOptions;
|
|
185
201
|
// Get ignore URLs from transports to prevent infinite loops
|
|
186
202
|
var ignoreUrls = this.getIgnoreUrls();
|
|
187
203
|
// Register instrumentations
|
|
188
204
|
(0, instrumentation_1.registerInstrumentations)({
|
|
189
|
-
instrumentations: (
|
|
205
|
+
instrumentations: (_f = options.instrumentations) !== null && _f !== void 0 ? _f : (0, getDefaultOTELInstrumentations_1.getDefaultOTELInstrumentations)({
|
|
190
206
|
ignoreUrls: ignoreUrls,
|
|
207
|
+
enableFetchInstrumentation: enableFetchInstrumentation,
|
|
208
|
+
enableXhrInstrumentation: enableXhrInstrumentation,
|
|
191
209
|
propagateTraceHeaderCorsUrls: propagateTraceHeaderCorsUrls,
|
|
192
210
|
fetchInstrumentationOptions: fetchInstrumentationOptions,
|
|
193
211
|
xhrInstrumentationOptions: xhrInstrumentationOptions,
|
|
@@ -223,8 +241,8 @@ var TracingInstrumentation = /** @class */ (function (_super) {
|
|
|
223
241
|
}
|
|
224
242
|
return url;
|
|
225
243
|
});
|
|
226
|
-
// Return
|
|
227
|
-
return __spreadArray(__spreadArray([], transportUrls, true), regexPatterns, true);
|
|
244
|
+
// Return dev-server, original transport URLs, and regex patterns for maximum coverage.
|
|
245
|
+
return __spreadArray(__spreadArray(__spreadArray([], (0, devServerIgnoreUrls_1.getReactNativeDevServerIgnoreUrls)(), true), transportUrls, true), regexPatterns, true);
|
|
228
246
|
};
|
|
229
247
|
/**
|
|
230
248
|
* Shutdown the tracer provider
|
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAiE;AAEjE,4CAA2G;AAC3G,kEAA0E;AAC1E,sDAAmF;AACnF,gEAAqH;AAErH,8DAAmE;AACnE,4EAI6C;AAE7C,gDAAmG;AAGnG,mEAAkE;AAClE,8EAA2F;AAC3F,oGAAmG;AACnG,gGAA+F;AAC/F,gGAA+F;AAC/F,qCAamB;AAEnB,2CAAsD;AAEtD,qCAAqC;AACrC,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH;IAA4C,0CAAmB;IAQ7D,gCAAoB,OAA2C;QAA3C,wBAAA,EAAA,YAA2C;QAC7D,YAAA,MAAK,WAAE,SAAC;QADU,aAAO,GAAP,OAAO,CAAoC;QAP/D,UAAI,GAAG,oCAAoC,CAAC;QAC5C,aAAO,GAAG,mBAAO,CAAC;;IAQlB,CAAC;IAED,2CAAU,GAAV;QAAA,iBAyJC;;QAxJC,IAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAM,UAAU,GAAe,EAAE,CAAC;QAElC,iBAAiB;QACjB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACzB,UAAU,CAAC,wCAAiB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACvD,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;YAC9B,UAAU,CAAC,gCAAsB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACjE,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,UAAU,CAAC,2CAAoB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;YAC3D,UAAU,CAAC,0BAAgB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QACzD,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAChC,UAAU,CAAC,0CAAgC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;YAE3E;;;eAGG;YACH,UAAU,CAAC,yDAAkC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QAC/E,CAAC;QAED,+CAA+C;QAC/C,wFAAwF;QACxF,IAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAgC,CAAC;QAC7D,IAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAQvB,CAAC;QAEd,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,EAAE,CAAC;YACtB,UAAU,CAAC,2BAAiB,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QACnD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,EAAE,CAAC;YACtB,UAAU,CAAC,2BAAiB,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QACnD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,MAAM,EAAE,CAAC;YACvB,UAAU,CAAC,8BAAoB,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACvD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,EAAE,CAAC;YAC1B,UAAU,CAAC,gCAAsB,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC;QAC5D,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,MAAM,EAAE,CAAC;YACvB,UAAU,CAAC,4BAAkB,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACrD,CAAC;QAED,UAAU,CAAC,mCAAyB,CAAC,GAAG,cAAc,CAAC;QACvD,UAAU,CAAC,sCAA4B,CAAC,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,mCAAI,SAAS,CAAC;QAE9E,UAAU,CAAC,oCAA0B,CAAC,GAAG,uBAAuB,CAAC;QACjE,UAAU,CAAC,uCAA6B,CAAC,GAAG,mBAAO,CAAC;QAEpD,sCAAsC;QACtC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAEtD,IAAM,QAAQ,GAAG,IAAA,2BAAe,GAAE,CAAC,KAAK,CAAC,IAAA,kCAAsB,EAAC,UAAU,CAAC,CAAC,CAAC;QAE7E,8CAA8C;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,oCAAyB,CAAC;YAC5C,QAAQ,UAAA;YACR,OAAO,EAAE;gBACP,YAAY,EAAE;oBACZ,OAAO;wBACL,QAAQ,EAAE,IAAA,6BAAmB,EAAC,KAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;qBACrD,CAAC;gBACJ,CAAC;aACF;YACD,cAAc,EAAE;gBACd,MAAA,OAAO,CAAC,aAAa,mCACnB,IAAI,iEAA+B,CACjC,IAAI,iEAA+B,CACjC,IAAI,mCAAkB,CAAC,IAAI,qCAAiB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE;oBAC/D,oBAAoB,EAAE,sBAAsB,CAAC,wBAAwB;oBACrE,kBAAkB,EAAE,EAAE;iBACvB,CAAC,EACF,IAAI,CAAC,KAAK,CACX,CACF;aACJ;SACF,CAAC,CAAC;QAEH,sDAAsD;QACtD,kFAAkF;QAClF,WAAK,CAAC,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE7C,4FAA4F;QAC5F,0FAA0F;QAC1F,6FAA6F;QAC7F,6FAA6F;QAC7F,iFAAiF;QACjF,aAAO,CAAC,uBAAuB,CAAC,MAAA,OAAO,CAAC,cAAc,mCAAI,IAAI,mCAAmB,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QAE9F,8EAA8E;QAC9E,+EAA+E;QAC/E,gFAAgF;QAChF,kFAAkF;QAClF,iBAAW,CAAC,mBAAmB,CAC7B,MAAA,OAAO,CAAC,UAAU,mCAChB,IAAI,0BAAmB,CAAC;YACtB,WAAW,EAAE,CAAC,IAAI,gCAAyB,EAAE,EAAE,IAAI,2BAAoB,EAAE,CAAC;SAC3E,CAAC,CACL,CAAC;QAEI,IAAA,KAMF,MAAA,IAAI,CAAC,OAAO,CAAC,sBAAsB,mCAAI,EAAE,EAL3C,0BAA0B,gCAAA,EAC1B,wBAAwB,8BAAA,EACxB,4BAA4B,kCAAA,EAC5B,2BAA2B,iCAAA,EAC3B,yBAAyB,+BACkB,CAAC;QAE9C,4DAA4D;QAC5D,IAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAExC,4BAA4B;QAC5B,IAAA,0CAAwB,EAAC;YACvB,gBAAgB,EACd,MAAA,OAAO,CAAC,gBAAgB,mCACxB,IAAA,+DAA8B,EAAC;gBAC7B,UAAU,YAAA;gBACV,0BAA0B,4BAAA;gBAC1B,wBAAwB,0BAAA;gBACxB,4BAA4B,8BAAA;gBAC5B,2BAA2B,6BAAA;gBAC3B,yBAAyB,2BAAA;aAC1B,CAAC;SACL,CAAC,CAAC;QAEH,uEAAuE;QACvE,mEAAmE;QACnE,IAAM,kBAAkB,GAAG,IAAA,2CAA+B,GAAE,CAAC;QAC7D,IAAI,kBAAkB,EAAE,CAAC;YACvB,uGAAuG;YACtG,kBAA0B,CAAC,IAAI,GAAG;gBACjC,KAAK,aAAA;gBACL,OAAO,eAAA;aACR,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,8CAAa,GAArB;QACE,mDAAmD;QACnD,IAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,UAAC,SAAoB;YAC5E,OAAO,SAAS,CAAC,aAAa,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,8DAA8D;QAC9D,IAAM,aAAa,GAAG,aAAa,CAAC,GAAG,CAAC,UAAC,GAAG;YAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,mEAAmE;gBACnE,IAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;gBAC9D,OAAO,IAAI,MAAM,CAAC,WAAI,UAAU,QAAK,CAAC,CAAC;YACzC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,uFAAuF;QACvF,qDAAW,IAAA,uDAAiC,GAAE,SAAK,aAAa,SAAK,aAAa,QAAE;IACtF,CAAC;IAED;;OAEG;IACG,yCAAQ,GAAd;;;;;6BACM,IAAI,CAAC,QAAQ,EAAb,wBAAa;wBACf,qBAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAA;;wBAA9B,SAA8B,CAAC;;;;;;KAElC;IAnMM,+CAAwB,GAAG,IAAI,AAAP,CAAQ;IAoMzC,6BAAC;CAAA,AAxMD,CAA4C,+BAAmB,GAwM9D;AAxMY,wDAAsB","sourcesContent":["import { context, propagation, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';\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 { StackContextManager } from '@opentelemetry/sdk-trace-web';\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 { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';\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 // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,\n // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does\n // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the\n // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.\n // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.\n context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());\n\n // Register the global text-map propagator. Without this, OTel falls back to a\n // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning\n // `traceparent` / `tracestate` (and `baggage`) headers are never written on the\n // outbound fetch/XHR — so the backend receives no context and starts a new trace.\n propagation.setGlobalPropagator(\n options.propagator ??\n new CompositePropagator({\n propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],\n })\n );\n\n const {\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n 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 enableFetchInstrumentation,\n enableXhrInstrumentation,\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 dev-server, original transport URLs, and regex patterns for maximum coverage.\n return [...getReactNativeDevServerIgnoreUrls(), ...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,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getReactNativeDevServerIgnoreUrls = getReactNativeDevServerIgnoreUrls;
|
|
4
|
+
var REACT_NATIVE_DEV_SERVER_IGNORE_URLS = [
|
|
5
|
+
/^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
|
|
6
|
+
];
|
|
7
|
+
function getReactNativeDevServerIgnoreUrls() {
|
|
8
|
+
return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=devServerIgnoreUrls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devServerIgnoreUrls.js","sourceRoot":"","sources":["../../../src/instrumentations/devServerIgnoreUrls.ts"],"names":[],"mappings":";;AAMA,8EAEC;AAND,IAAM,mCAAmC,GAAwB;IAC/D,6FAA6F;CAC9F,CAAC;AAEF,SAAgB,iCAAiC;IAC/C,OAAO,mCAAmC,CAAC;AAC7C,CAAC","sourcesContent":["import type { MatchUrlDefinitions } from '../types';\n\nconst REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [\n /^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|10\\.0\\.2\\.2)(?::\\d+)?\\/symbolicate(?:[/?#]|$)/,\n];\n\nexport function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {\n return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;\n}\n"]}
|
|
@@ -32,7 +32,7 @@ var instrumentationUtils_1 = require("./instrumentationUtils");
|
|
|
32
32
|
*
|
|
33
33
|
* This function creates the default OpenTelemetry instrumentations for React Native:
|
|
34
34
|
* - FetchInstrumentation: Traces fetch() API calls
|
|
35
|
-
* - XMLHttpRequestInstrumentation:
|
|
35
|
+
* - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
|
|
36
36
|
*
|
|
37
37
|
* IMPORTANT: Infinite loop prevention
|
|
38
38
|
* - ignoreUrls is used to exclude Faro collector URLs
|
|
@@ -44,10 +44,15 @@ var instrumentationUtils_1 = require("./instrumentationUtils");
|
|
|
44
44
|
*/
|
|
45
45
|
function getDefaultOTELInstrumentations(options) {
|
|
46
46
|
if (options === void 0) { options = {}; }
|
|
47
|
-
var fetchInstrumentationOptions = options.fetchInstrumentationOptions, xhrInstrumentationOptions = options.xhrInstrumentationOptions, sharedOptions = __rest(options, ["fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
var _a = options.enableFetchInstrumentation, enableFetchInstrumentation = _a === void 0 ? true : _a, _b = options.enableXhrInstrumentation, enableXhrInstrumentation = _b === void 0 ? false : _b, fetchInstrumentationOptions = options.fetchInstrumentationOptions, xhrInstrumentationOptions = options.xhrInstrumentationOptions, sharedOptions = __rest(options, ["enableFetchInstrumentation", "enableXhrInstrumentation", "fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
|
|
48
|
+
var instrumentations = [];
|
|
49
|
+
if (enableFetchInstrumentation) {
|
|
50
|
+
instrumentations.push(new instrumentation_fetch_1.FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions)));
|
|
51
|
+
}
|
|
52
|
+
if (enableXhrInstrumentation) {
|
|
53
|
+
instrumentations.push(new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions)));
|
|
54
|
+
}
|
|
55
|
+
return instrumentations;
|
|
51
56
|
}
|
|
52
57
|
function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
|
|
53
58
|
return __assign(__assign(__assign(__assign({}, sharedOptions), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,
|
|
1
|
+
{"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,wEAwBC;AAnDD,8EAA4E;AAC5E,oGAAgG;AAEhG,gDAA6F;AAI7F,+DAGgC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,8BAA8B,CAAC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IAEvF,IAAA,KAKE,OAAO,2BALwB,EAAjC,0BAA0B,mBAAG,IAAI,KAAA,EACjC,KAIE,OAAO,yBAJuB,EAAhC,wBAAwB,mBAAG,KAAK,KAAA,EAChC,2BAA2B,GAGzB,OAAO,4BAHkB,EAC3B,yBAAyB,GAEvB,OAAO,0BAFgB,EACtB,aAAa,UACd,OAAO,EANL,sHAML,CADiB,CACN;IAEZ,IAAM,gBAAgB,GAA4B,EAAE,CAAC;IAErD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CACnB,IAAI,4CAAoB,CAAC,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC,CACxG,CAAC;IACJ,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,gBAAgB,CAAC,IAAI,CACnB,IAAI,gEAA6B,CAAC,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC,CAC7G,CAAC;IACJ,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,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: Optional for apps that use XHR/axios directly\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 {\n enableFetchInstrumentation = true,\n enableXhrInstrumentation = false,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n ...sharedOptions\n } = options;\n\n const instrumentations: InstrumentationOption[] = [];\n\n if (enableFetchInstrumentation) {\n instrumentations.push(\n new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))\n );\n }\n\n if (enableXhrInstrumentation) {\n instrumentations.push(\n new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))\n );\n }\n\n return instrumentations;\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"]}
|
package/dist/cjs/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n enableFetchInstrumentation?: boolean;\n enableXhrInstrumentation?: boolean;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
|
|
@@ -7,13 +7,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { context, trace } from '@opentelemetry/api';
|
|
10
|
+
import { context, propagation, trace } from '@opentelemetry/api';
|
|
11
|
+
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
11
12
|
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
12
13
|
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
|
|
13
14
|
import { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
15
|
+
import { StackContextManager } from '@opentelemetry/sdk-trace-web';
|
|
14
16
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, } from '@opentelemetry/semantic-conventions';
|
|
15
17
|
import { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';
|
|
16
18
|
import { FaroTraceExporter } from './exporters/faroTraceExporter';
|
|
19
|
+
import { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';
|
|
17
20
|
import { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';
|
|
18
21
|
import { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';
|
|
19
22
|
import { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';
|
|
@@ -55,7 +58,7 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
55
58
|
this.version = VERSION;
|
|
56
59
|
}
|
|
57
60
|
initialize() {
|
|
58
|
-
var _a, _b, _c, _d;
|
|
61
|
+
var _a, _b, _c, _d, _e, _f;
|
|
59
62
|
const options = this.options;
|
|
60
63
|
const attributes = {};
|
|
61
64
|
// App attributes
|
|
@@ -123,13 +126,28 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
123
126
|
// Register the provider as the global tracer provider
|
|
124
127
|
// This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
|
|
125
128
|
trace.setGlobalTracerProvider(this.provider);
|
|
126
|
-
|
|
129
|
+
// Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
|
|
130
|
+
// which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
|
|
131
|
+
// `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
|
|
132
|
+
// wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
|
|
133
|
+
// `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
|
|
134
|
+
context.setGlobalContextManager((_c = options.contextManager) !== null && _c !== void 0 ? _c : new StackContextManager().enable());
|
|
135
|
+
// Register the global text-map propagator. Without this, OTel falls back to a
|
|
136
|
+
// NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
|
|
137
|
+
// `traceparent` / `tracestate` (and `baggage`) headers are never written on the
|
|
138
|
+
// outbound fetch/XHR — so the backend receives no context and starts a new trace.
|
|
139
|
+
propagation.setGlobalPropagator((_d = options.propagator) !== null && _d !== void 0 ? _d : new CompositePropagator({
|
|
140
|
+
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
|
|
141
|
+
}));
|
|
142
|
+
const { enableFetchInstrumentation, enableXhrInstrumentation, propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions, } = (_e = this.options.instrumentationOptions) !== null && _e !== void 0 ? _e : {};
|
|
127
143
|
// Get ignore URLs from transports to prevent infinite loops
|
|
128
144
|
const ignoreUrls = this.getIgnoreUrls();
|
|
129
145
|
// Register instrumentations
|
|
130
146
|
registerInstrumentations({
|
|
131
|
-
instrumentations: (
|
|
147
|
+
instrumentations: (_f = options.instrumentations) !== null && _f !== void 0 ? _f : getDefaultOTELInstrumentations({
|
|
132
148
|
ignoreUrls,
|
|
149
|
+
enableFetchInstrumentation,
|
|
150
|
+
enableXhrInstrumentation,
|
|
133
151
|
propagateTraceHeaderCorsUrls,
|
|
134
152
|
fetchInstrumentationOptions,
|
|
135
153
|
xhrInstrumentationOptions,
|
|
@@ -165,8 +183,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
165
183
|
}
|
|
166
184
|
return url;
|
|
167
185
|
});
|
|
168
|
-
// Return
|
|
169
|
-
return [...transportUrls, ...regexPatterns];
|
|
186
|
+
// Return dev-server, original transport URLs, and regex patterns for maximum coverage.
|
|
187
|
+
return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];
|
|
170
188
|
}
|
|
171
189
|
/**
|
|
172
190
|
* Shutdown the tracer provider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAErH,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,kCAAkC,GACnC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,+BAA+B,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,8BAA8B,EAAE,MAAM,mDAAmD,CAAC;AACnG,OAAO,EAAE,+BAA+B,EAAE,MAAM,8CAA8C,CAAC;AAC/F,OAAO,EAAE,+BAA+B,EAAE,MAAM,8CAA8C,CAAC;AAC/F,OAAO,EACL,gBAAgB,EAChB,gCAAgC,EAChC,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,qCAAqC;AACrC,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,sBAAuB,SAAQ,mBAAmB;IAQ7D,YAAoB,UAAyC,EAAE;QAC7D,KAAK,EAAE,CAAC;QADU,YAAO,GAAP,OAAO,CAAoC;QAP/D,SAAI,GAAG,oCAAoC,CAAC;QAC5C,YAAO,GAAG,OAAO,CAAC;IAQlB,CAAC;IAED,UAAU;;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,MAAM,UAAU,GAAe,EAAE,CAAC;QAElC,iBAAiB;QACjB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACzB,UAAU,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACvD,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;YAC9B,UAAU,CAAC,sBAAsB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACjE,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,UAAU,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;YAC3D,UAAU,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;QACzD,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAChC,UAAU,CAAC,gCAAgC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;YAE3E;;;eAGG;YACH,UAAU,CAAC,kCAAkC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QAC/E,CAAC;QAED,+CAA+C;QAC/C,wFAAwF;QACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAgC,CAAC;QAC7D,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAQvB,CAAC;QAEd,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,EAAE,CAAC;YACtB,UAAU,CAAC,iBAAiB,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QACnD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,EAAE,CAAC;YACtB,UAAU,CAAC,iBAAiB,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QACnD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,MAAM,EAAE,CAAC;YACvB,UAAU,CAAC,oBAAoB,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACvD,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,EAAE,CAAC;YAC1B,UAAU,CAAC,sBAAsB,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC;QAC5D,CAAC;QAED,IAAI,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,MAAM,EAAE,CAAC;YACvB,UAAU,CAAC,kBAAkB,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACrD,CAAC;QAED,UAAU,CAAC,yBAAyB,CAAC,GAAG,cAAc,CAAC;QACvD,UAAU,CAAC,4BAA4B,CAAC,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,mCAAI,SAAS,CAAC;QAE9E,UAAU,CAAC,0BAA0B,CAAC,GAAG,uBAAuB,CAAC;QACjE,UAAU,CAAC,6BAA6B,CAAC,GAAG,OAAO,CAAC;QAEpD,sCAAsC;QACtC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,CAAC;QAE7E,8CAA8C;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,yBAAyB,CAAC;YAC5C,QAAQ;YACR,OAAO,EAAE;gBACP,YAAY,EAAE,GAAG,EAAE;oBACjB,OAAO;wBACL,QAAQ,EAAE,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;qBACrD,CAAC;gBACJ,CAAC;aACF;YACD,cAAc,EAAE;gBACd,MAAA,OAAO,CAAC,aAAa,mCACnB,IAAI,+BAA+B,CACjC,IAAI,+BAA+B,CACjC,IAAI,kBAAkB,CAAC,IAAI,iBAAiB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE;oBAC/D,oBAAoB,EAAE,sBAAsB,CAAC,wBAAwB;oBACrE,kBAAkB,EAAE,EAAE;iBACvB,CAAC,EACF,IAAI,CAAC,KAAK,CACX,CACF;aACJ;SACF,CAAC,CAAC;QAEH,sDAAsD;QACtD,kFAAkF;QAClF,KAAK,CAAC,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE7C,MAAM,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,GAC5F,MAAA,IAAI,CAAC,OAAO,CAAC,sBAAsB,mCAAI,EAAE,CAAC;QAE5C,4DAA4D;QAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAExC,4BAA4B;QAC5B,wBAAwB,CAAC;YACvB,gBAAgB,EACd,MAAA,OAAO,CAAC,gBAAgB,mCACxB,8BAA8B,CAAC;gBAC7B,UAAU;gBACV,4BAA4B;gBAC5B,2BAA2B;gBAC3B,yBAAyB;aAC1B,CAAC;SACL,CAAC,CAAC;QAEH,uEAAuE;QACvE,mEAAmE;QACnE,MAAM,kBAAkB,GAAG,+BAA+B,EAAE,CAAC;QAC7D,IAAI,kBAAkB,EAAE,CAAC;YACvB,uGAAuG;YACtG,kBAA0B,CAAC,IAAI,GAAG;gBACjC,KAAK;gBACL,OAAO;aACR,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,aAAa;QACnB,mDAAmD;QACnD,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,SAAoB,EAAE,EAAE;YAChF,OAAO,SAAS,CAAC,aAAa,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,8DAA8D;QAC9D,MAAM,aAAa,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAC9C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,mEAAmE;gBACnE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;gBAC9D,OAAO,IAAI,MAAM,CAAC,IAAI,UAAU,KAAK,CAAC,CAAC;YACzC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,oEAAoE;QACpE,OAAO,CAAC,GAAG,aAAa,EAAE,GAAG,aAAa,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACG,QAAQ;;YACZ,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;KAAA;;AA1KM,+CAAwB,GAAG,IAAI,AAAP,CAAQ","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"]}
|
|
1
|
+
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, propagation, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';\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 { StackContextManager } from '@opentelemetry/sdk-trace-web';\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 { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';\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 // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,\n // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does\n // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the\n // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.\n // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.\n context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());\n\n // Register the global text-map propagator. Without this, OTel falls back to a\n // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning\n // `traceparent` / `tracestate` (and `baggage`) headers are never written on the\n // outbound fetch/XHR — so the backend receives no context and starts a new trace.\n propagation.setGlobalPropagator(\n options.propagator ??\n new CompositePropagator({\n propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],\n })\n );\n\n const {\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n 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 enableFetchInstrumentation,\n enableXhrInstrumentation,\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 dev-server, original transport URLs, and regex patterns for maximum coverage.\n return [...getReactNativeDevServerIgnoreUrls(), ...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,7 @@
|
|
|
1
|
+
const REACT_NATIVE_DEV_SERVER_IGNORE_URLS = [
|
|
2
|
+
/^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
|
|
3
|
+
];
|
|
4
|
+
export function getReactNativeDevServerIgnoreUrls() {
|
|
5
|
+
return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=devServerIgnoreUrls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devServerIgnoreUrls.js","sourceRoot":"","sources":["../../../src/instrumentations/devServerIgnoreUrls.ts"],"names":[],"mappings":"AAEA,MAAM,mCAAmC,GAAwB;IAC/D,6FAA6F;CAC9F,CAAC;AAEF,MAAM,UAAU,iCAAiC;IAC/C,OAAO,mCAAmC,CAAC;AAC7C,CAAC","sourcesContent":["import type { MatchUrlDefinitions } from '../types';\n\nconst REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [\n /^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|10\\.0\\.2\\.2)(?::\\d+)?\\/symbolicate(?:[/?#]|$)/,\n];\n\nexport function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {\n return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;\n}\n"]}
|
|
@@ -18,7 +18,7 @@ import { fetchCustomAttributeFunctionWithDefaults, xhrCustomAttributeFunctionWit
|
|
|
18
18
|
*
|
|
19
19
|
* This function creates the default OpenTelemetry instrumentations for React Native:
|
|
20
20
|
* - FetchInstrumentation: Traces fetch() API calls
|
|
21
|
-
* - XMLHttpRequestInstrumentation:
|
|
21
|
+
* - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
|
|
22
22
|
*
|
|
23
23
|
* IMPORTANT: Infinite loop prevention
|
|
24
24
|
* - ignoreUrls is used to exclude Faro collector URLs
|
|
@@ -29,10 +29,15 @@ import { fetchCustomAttributeFunctionWithDefaults, xhrCustomAttributeFunctionWit
|
|
|
29
29
|
* @returns Array of OTEL instrumentations
|
|
30
30
|
*/
|
|
31
31
|
export function getDefaultOTELInstrumentations(options = {}) {
|
|
32
|
-
const { fetchInstrumentationOptions, xhrInstrumentationOptions } = options, sharedOptions = __rest(options, ["fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const { enableFetchInstrumentation = true, enableXhrInstrumentation = false, fetchInstrumentationOptions, xhrInstrumentationOptions } = options, sharedOptions = __rest(options, ["enableFetchInstrumentation", "enableXhrInstrumentation", "fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
|
|
33
|
+
const instrumentations = [];
|
|
34
|
+
if (enableFetchInstrumentation) {
|
|
35
|
+
instrumentations.push(new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions)));
|
|
36
|
+
}
|
|
37
|
+
if (enableXhrInstrumentation) {
|
|
38
|
+
instrumentations.push(new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions)));
|
|
39
|
+
}
|
|
40
|
+
return instrumentations;
|
|
36
41
|
}
|
|
37
42
|
function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
|
|
38
43
|
return Object.assign(Object.assign(Object.assign(Object.assign({}, sharedOptions), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iDAAiD,CAAC;AAEhG,OAAO,EAAE,IAAI,EAAoC,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI7F,OAAO,EACL,wCAAwC,EACxC,sCAAsC,GACvC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAAC,UAA0C,EAAE;IACzF,MAAM,
|
|
1
|
+
{"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iDAAiD,CAAC;AAEhG,OAAO,EAAE,IAAI,EAAoC,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI7F,OAAO,EACL,wCAAwC,EACxC,sCAAsC,GACvC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAAC,UAA0C,EAAE;IACzF,MAAM,EACJ,0BAA0B,GAAG,IAAI,EACjC,wBAAwB,GAAG,KAAK,EAChC,2BAA2B,EAC3B,yBAAyB,KAEvB,OAAO,EADN,aAAa,UACd,OAAO,EANL,sHAML,CAAU,CAAC;IAEZ,MAAM,gBAAgB,GAA4B,EAAE,CAAC;IAErD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CACnB,IAAI,oBAAoB,CAAC,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC,CACxG,CAAC;IACJ,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,gBAAgB,CAAC,IAAI,CACnB,IAAI,6BAA6B,CAAC,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC,CAC7G,CAAC;IACJ,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,mEACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,wCAAwC,CACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,CAAC,IAAU,EAAE,CAAwB,EAAE,EAAE;YACpD,IAAI,CAAC;gBACH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,eAAe,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,mEACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,sCAAsC,CACjE,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: Optional for apps that use XHR/axios directly\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 {\n enableFetchInstrumentation = true,\n enableXhrInstrumentation = false,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n ...sharedOptions\n } = options;\n\n const instrumentations: InstrumentationOption[] = [];\n\n if (enableFetchInstrumentation) {\n instrumentations.push(\n new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))\n );\n }\n\n if (enableXhrInstrumentation) {\n instrumentations.push(\n new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))\n );\n }\n\n return instrumentations;\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"]}
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n enableFetchInstrumentation?: boolean;\n enableXhrInstrumentation?: boolean;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
|
|
@@ -4,7 +4,7 @@ import type { DefaultInstrumentationsOptions, InstrumentationOption } from '../t
|
|
|
4
4
|
*
|
|
5
5
|
* This function creates the default OpenTelemetry instrumentations for React Native:
|
|
6
6
|
* - FetchInstrumentation: Traces fetch() API calls
|
|
7
|
-
* - XMLHttpRequestInstrumentation:
|
|
7
|
+
* - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
|
|
8
8
|
*
|
|
9
9
|
* IMPORTANT: Infinite loop prevention
|
|
10
10
|
* - ignoreUrls is used to exclude Faro collector URLs
|
package/dist/types/types.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type MatchUrlDefinitions = Patterns;
|
|
|
20
20
|
export type DefaultInstrumentationsOptions = {
|
|
21
21
|
ignoreUrls?: MatchUrlDefinitions;
|
|
22
22
|
propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;
|
|
23
|
+
enableFetchInstrumentation?: boolean;
|
|
24
|
+
enableXhrInstrumentation?: boolean;
|
|
23
25
|
fetchInstrumentationOptions?: {
|
|
24
26
|
applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
|
|
25
27
|
ignoreNetworkEvents?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/faro-react-native-tracing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Faro React Native tracing implementation with OpenTelemetry.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"observability",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@grafana/faro-core": "^2.2.3",
|
|
55
|
-
"@grafana/faro-react-native": "^1.
|
|
55
|
+
"@grafana/faro-react-native": "^1.2.0",
|
|
56
56
|
"@opentelemetry/api": "^1.9.0",
|
|
57
57
|
"@opentelemetry/core": "^2.0.0",
|
|
58
58
|
"@opentelemetry/instrumentation": "^0.208.0",
|
|
@@ -61,17 +61,18 @@
|
|
|
61
61
|
"@opentelemetry/otlp-transformer": "^0.208.0",
|
|
62
62
|
"@opentelemetry/resources": "^2.0.0",
|
|
63
63
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
64
|
+
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
|
64
65
|
"@opentelemetry/semantic-conventions": "^1.32.0"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
68
|
"react-native": ">=0.70.0"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
|
-
"@types/node": "24.
|
|
71
|
-
"@types/react-native": "
|
|
71
|
+
"@types/node": "24.12.3",
|
|
72
|
+
"@types/react-native": "0.73.0"
|
|
72
73
|
},
|
|
73
74
|
"publishConfig": {
|
|
74
75
|
"access": "public"
|
|
75
76
|
},
|
|
76
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "65b49f4c649e3400c74495257d33bd28d9af4b58"
|
|
77
78
|
}
|
package/src/instrumentation.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { context, trace } from '@opentelemetry/api';
|
|
1
|
+
import { context, propagation, trace } from '@opentelemetry/api';
|
|
2
2
|
import type { Attributes } from '@opentelemetry/api';
|
|
3
|
+
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
3
4
|
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
4
5
|
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
|
|
5
6
|
import { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
6
7
|
import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
8
|
+
import { StackContextManager } from '@opentelemetry/sdk-trace-web';
|
|
7
9
|
import {
|
|
8
10
|
ATTR_SERVICE_NAME,
|
|
9
11
|
ATTR_SERVICE_VERSION,
|
|
@@ -14,6 +16,7 @@ import { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@
|
|
|
14
16
|
import type { Transport } from '@grafana/faro-core';
|
|
15
17
|
|
|
16
18
|
import { FaroTraceExporter } from './exporters/faroTraceExporter';
|
|
19
|
+
import { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';
|
|
17
20
|
import { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';
|
|
18
21
|
import { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';
|
|
19
22
|
import { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';
|
|
@@ -175,8 +178,31 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
175
178
|
// This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
|
|
176
179
|
trace.setGlobalTracerProvider(this.provider);
|
|
177
180
|
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
// Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
|
|
182
|
+
// which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
|
|
183
|
+
// `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
|
|
184
|
+
// wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
|
|
185
|
+
// `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
|
|
186
|
+
context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());
|
|
187
|
+
|
|
188
|
+
// Register the global text-map propagator. Without this, OTel falls back to a
|
|
189
|
+
// NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
|
|
190
|
+
// `traceparent` / `tracestate` (and `baggage`) headers are never written on the
|
|
191
|
+
// outbound fetch/XHR — so the backend receives no context and starts a new trace.
|
|
192
|
+
propagation.setGlobalPropagator(
|
|
193
|
+
options.propagator ??
|
|
194
|
+
new CompositePropagator({
|
|
195
|
+
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const {
|
|
200
|
+
enableFetchInstrumentation,
|
|
201
|
+
enableXhrInstrumentation,
|
|
202
|
+
propagateTraceHeaderCorsUrls,
|
|
203
|
+
fetchInstrumentationOptions,
|
|
204
|
+
xhrInstrumentationOptions,
|
|
205
|
+
} = this.options.instrumentationOptions ?? {};
|
|
180
206
|
|
|
181
207
|
// Get ignore URLs from transports to prevent infinite loops
|
|
182
208
|
const ignoreUrls = this.getIgnoreUrls();
|
|
@@ -187,6 +213,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
187
213
|
options.instrumentations ??
|
|
188
214
|
getDefaultOTELInstrumentations({
|
|
189
215
|
ignoreUrls,
|
|
216
|
+
enableFetchInstrumentation,
|
|
217
|
+
enableXhrInstrumentation,
|
|
190
218
|
propagateTraceHeaderCorsUrls,
|
|
191
219
|
fetchInstrumentationOptions,
|
|
192
220
|
xhrInstrumentationOptions,
|
|
@@ -226,8 +254,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
|
|
|
226
254
|
return url;
|
|
227
255
|
});
|
|
228
256
|
|
|
229
|
-
// Return
|
|
230
|
-
return [...transportUrls, ...regexPatterns];
|
|
257
|
+
// Return dev-server, original transport URLs, and regex patterns for maximum coverage.
|
|
258
|
+
return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];
|
|
231
259
|
}
|
|
232
260
|
|
|
233
261
|
/**
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getReactNativeDevServerIgnoreUrls } from './devServerIgnoreUrls';
|
|
2
|
+
|
|
3
|
+
describe('getReactNativeDevServerIgnoreUrls', () => {
|
|
4
|
+
const ignoredUrlPattern = getReactNativeDevServerIgnoreUrls()[0] as RegExp;
|
|
5
|
+
|
|
6
|
+
it.each([
|
|
7
|
+
'http://localhost:8081/symbolicate',
|
|
8
|
+
'http://localhost:8081/symbolicate?platform=ios',
|
|
9
|
+
'http://127.0.0.1:8081/symbolicate',
|
|
10
|
+
'http://[::1]:8081/symbolicate',
|
|
11
|
+
'http://10.0.2.2:8081/symbolicate',
|
|
12
|
+
])('matches React Native dev-server symbolication URL %s', (url) => {
|
|
13
|
+
expect(ignoredUrlPattern.test(url)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it.each([
|
|
17
|
+
'https://api.example.com/symbolicate',
|
|
18
|
+
'https://api.example.com/v1/symbolicate',
|
|
19
|
+
'https://api.example.com/symbolicate-events',
|
|
20
|
+
])('does not match backend symbolication-like URL %s', (url) => {
|
|
21
|
+
expect(ignoredUrlPattern.test(url)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MatchUrlDefinitions } from '../types';
|
|
2
|
+
|
|
3
|
+
const REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [
|
|
4
|
+
/^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
export function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {
|
|
8
|
+
return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
|
|
9
|
+
}
|
|
@@ -4,22 +4,48 @@ import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xm
|
|
|
4
4
|
import { getDefaultOTELInstrumentations } from './getDefaultOTELInstrumentations';
|
|
5
5
|
|
|
6
6
|
describe('getDefaultOTELInstrumentations', () => {
|
|
7
|
-
it('should return
|
|
7
|
+
it('should return FetchInstrumentation by default', () => {
|
|
8
8
|
const instrumentations = getDefaultOTELInstrumentations();
|
|
9
9
|
|
|
10
|
+
expect(instrumentations).toHaveLength(1);
|
|
11
|
+
expect(instrumentations[0]).toBeInstanceOf(FetchInstrumentation);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should allow enabling XMLHttpRequestInstrumentation', () => {
|
|
15
|
+
const instrumentations = getDefaultOTELInstrumentations({
|
|
16
|
+
enableXhrInstrumentation: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
10
19
|
expect(instrumentations).toHaveLength(2);
|
|
11
20
|
expect(instrumentations[0]).toBeInstanceOf(FetchInstrumentation);
|
|
12
21
|
expect(instrumentations[1]).toBeInstanceOf(XMLHttpRequestInstrumentation);
|
|
13
22
|
});
|
|
14
23
|
|
|
24
|
+
it('should allow using only XMLHttpRequestInstrumentation', () => {
|
|
25
|
+
const instrumentations = getDefaultOTELInstrumentations({
|
|
26
|
+
enableFetchInstrumentation: false,
|
|
27
|
+
enableXhrInstrumentation: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(instrumentations).toHaveLength(1);
|
|
31
|
+
expect(instrumentations[0]).toBeInstanceOf(XMLHttpRequestInstrumentation);
|
|
32
|
+
});
|
|
33
|
+
|
|
15
34
|
it('should configure ignoreUrls', () => {
|
|
16
35
|
const ignoreUrls = [/test-url/];
|
|
17
36
|
const instrumentations = getDefaultOTELInstrumentations({ ignoreUrls });
|
|
18
37
|
|
|
19
|
-
expect(instrumentations).toHaveLength(
|
|
20
|
-
// FetchInstrumentation should have ignoreUrls configured
|
|
38
|
+
expect(instrumentations).toHaveLength(1);
|
|
21
39
|
expect((instrumentations[0] as any)._config.ignoreUrls).toEqual(ignoreUrls);
|
|
22
|
-
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should configure ignoreUrls for XMLHttpRequestInstrumentation', () => {
|
|
43
|
+
const ignoreUrls = [/test-url/];
|
|
44
|
+
const instrumentations = getDefaultOTELInstrumentations({
|
|
45
|
+
enableXhrInstrumentation: true,
|
|
46
|
+
ignoreUrls,
|
|
47
|
+
});
|
|
48
|
+
|
|
23
49
|
expect((instrumentations[1] as any)._config.ignoreUrls).toEqual(ignoreUrls);
|
|
24
50
|
});
|
|
25
51
|
|
|
@@ -56,7 +82,7 @@ describe('getDefaultOTELInstrumentations', () => {
|
|
|
56
82
|
},
|
|
57
83
|
});
|
|
58
84
|
|
|
59
|
-
expect(instrumentations).toHaveLength(
|
|
85
|
+
expect(instrumentations).toHaveLength(1);
|
|
60
86
|
// Custom function should be wrapped by our defaults
|
|
61
87
|
expect((instrumentations[0] as any)._config.applyCustomAttributesOnSpan).toBeDefined();
|
|
62
88
|
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
*
|
|
17
17
|
* This function creates the default OpenTelemetry instrumentations for React Native:
|
|
18
18
|
* - FetchInstrumentation: Traces fetch() API calls
|
|
19
|
-
* - XMLHttpRequestInstrumentation:
|
|
19
|
+
* - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
|
|
20
20
|
*
|
|
21
21
|
* IMPORTANT: Infinite loop prevention
|
|
22
22
|
* - ignoreUrls is used to exclude Faro collector URLs
|
|
@@ -27,12 +27,29 @@ import {
|
|
|
27
27
|
* @returns Array of OTEL instrumentations
|
|
28
28
|
*/
|
|
29
29
|
export function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {
|
|
30
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
enableFetchInstrumentation = true,
|
|
32
|
+
enableXhrInstrumentation = false,
|
|
33
|
+
fetchInstrumentationOptions,
|
|
34
|
+
xhrInstrumentationOptions,
|
|
35
|
+
...sharedOptions
|
|
36
|
+
} = options;
|
|
31
37
|
|
|
32
|
-
const
|
|
33
|
-
const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);
|
|
38
|
+
const instrumentations: InstrumentationOption[] = [];
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
if (enableFetchInstrumentation) {
|
|
41
|
+
instrumentations.push(
|
|
42
|
+
new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (enableXhrInstrumentation) {
|
|
47
|
+
instrumentations.push(
|
|
48
|
+
new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return instrumentations;
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
function createFetchInstrumentationOptions(
|
package/src/types.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type MatchUrlDefinitions = Patterns;
|
|
|
26
26
|
export type DefaultInstrumentationsOptions = {
|
|
27
27
|
ignoreUrls?: MatchUrlDefinitions;
|
|
28
28
|
propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;
|
|
29
|
+
enableFetchInstrumentation?: boolean;
|
|
30
|
+
enableXhrInstrumentation?: boolean;
|
|
29
31
|
|
|
30
32
|
fetchInstrumentationOptions?: {
|
|
31
33
|
applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
|