@grafana/faro-react-native-tracing 1.3.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +33 -0
  2. package/dist/cjs/exporters/faroTraceExporter.utils.js +33 -0
  3. package/dist/cjs/exporters/faroTraceExporter.utils.js.map +1 -1
  4. package/dist/cjs/instrumentation.js +259 -44
  5. package/dist/cjs/instrumentation.js.map +1 -1
  6. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js +28 -20
  7. package/dist/cjs/instrumentations/getDefaultOTELInstrumentations.js.map +1 -1
  8. package/dist/cjs/instrumentations/instrumentationUtils.js +15 -0
  9. package/dist/cjs/instrumentations/instrumentationUtils.js.map +1 -1
  10. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js +27 -13
  11. package/dist/cjs/processors/httpRequestMonitorSpanProcessor.js.map +1 -1
  12. package/dist/cjs/types.js.map +1 -1
  13. package/dist/esm/exporters/faroTraceExporter.utils.js +32 -0
  14. package/dist/esm/exporters/faroTraceExporter.utils.js.map +1 -1
  15. package/dist/esm/instrumentation.js +237 -43
  16. package/dist/esm/instrumentation.js.map +1 -1
  17. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js +27 -21
  18. package/dist/esm/instrumentations/getDefaultOTELInstrumentations.js.map +1 -1
  19. package/dist/esm/instrumentations/instrumentationUtils.js +14 -0
  20. package/dist/esm/instrumentations/instrumentationUtils.js.map +1 -1
  21. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js +27 -13
  22. package/dist/esm/processors/httpRequestMonitorSpanProcessor.js.map +1 -1
  23. package/dist/esm/types.js.map +1 -1
  24. package/dist/types/instrumentation.d.ts +12 -4
  25. package/dist/types/instrumentations/getDefaultOTELInstrumentations.d.ts +2 -0
  26. package/dist/types/instrumentations/instrumentationUtils.d.ts +2 -0
  27. package/dist/types/types.d.ts +4 -0
  28. package/package.json +3 -3
  29. package/src/exporters/faroTraceExporter.events.test.ts +90 -0
  30. package/src/exporters/faroTraceExporter.utils.ts +32 -0
  31. package/src/httpSemantics.test.ts +300 -0
  32. package/src/instrumentation.test.ts +740 -0
  33. package/src/instrumentation.ts +299 -67
  34. package/src/instrumentations/getDefaultOTELInstrumentations.test.ts +28 -1
  35. package/src/instrumentations/getDefaultOTELInstrumentations.ts +41 -32
  36. package/src/instrumentations/instrumentationUtils.ts +13 -0
  37. package/src/processors/httpRequestMonitorSpanProcessor.ts +27 -15
  38. package/src/types.ts +4 -0
@@ -23,9 +23,9 @@ var __rest = (this && this.__rest) || function (s, e) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.getDefaultOTELInstrumentations = getDefaultOTELInstrumentations;
26
+ exports.updateDefaultOTELInstrumentations = updateDefaultOTELInstrumentations;
26
27
  var instrumentation_fetch_1 = require("@opentelemetry/instrumentation-fetch");
27
28
  var instrumentation_xml_http_request_1 = require("@opentelemetry/instrumentation-xml-http-request");
28
- var faro_core_1 = require("@grafana/faro-core");
29
29
  var instrumentationUtils_1 = require("./instrumentationUtils");
30
30
  /**
31
31
  * Get default OTEL instrumentations for React Native
@@ -44,37 +44,45 @@ var instrumentationUtils_1 = require("./instrumentationUtils");
44
44
  */
45
45
  function getDefaultOTELInstrumentations(options) {
46
46
  if (options === void 0) { options = {}; }
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"]);
47
+ var _a = resolveDefaultInstrumentationOptions(options), enableFetchInstrumentation = _a.enableFetchInstrumentation, enableXhrInstrumentation = _a.enableXhrInstrumentation, fetchConfig = _a.fetchConfig, xhrConfig = _a.xhrConfig;
48
48
  var instrumentations = [];
49
49
  if (enableFetchInstrumentation) {
50
- instrumentations.push(new instrumentation_fetch_1.FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions)));
50
+ instrumentations.push(new instrumentation_fetch_1.FetchInstrumentation(fetchConfig));
51
51
  }
52
52
  if (enableXhrInstrumentation) {
53
- instrumentations.push(new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions)));
53
+ instrumentations.push(new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(xhrConfig));
54
54
  }
55
55
  return instrumentations;
56
56
  }
57
+ function updateDefaultOTELInstrumentations(instrumentations, options) {
58
+ if (options === void 0) { options = {}; }
59
+ var _a = resolveDefaultInstrumentationOptions(options), fetchConfig = _a.fetchConfig, xhrConfig = _a.xhrConfig;
60
+ instrumentations.forEach(function (instrumentation) {
61
+ if (instrumentation instanceof instrumentation_fetch_1.FetchInstrumentation) {
62
+ instrumentation.setConfig(fetchConfig);
63
+ }
64
+ else if (instrumentation instanceof instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation) {
65
+ instrumentation.setConfig(xhrConfig);
66
+ }
67
+ });
68
+ }
69
+ function resolveDefaultInstrumentationOptions(options) {
70
+ 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"]);
71
+ return {
72
+ enableFetchInstrumentation: enableFetchInstrumentation,
73
+ enableXhrInstrumentation: enableXhrInstrumentation,
74
+ fetchConfig: createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions),
75
+ xhrConfig: createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions),
76
+ };
77
+ }
57
78
  function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
58
79
  return __assign(__assign(__assign(__assign({}, sharedOptions), {
59
80
  // Ignore network performance events to avoid duplicates
60
- ignoreNetworkEvents: true }), fetchInstrumentationOptions), {
81
+ ignoreNetworkEvents: true }), fetchInstrumentationOptions), { semconvStabilityOptIn: 'http',
61
82
  // Always keep this function
62
83
  applyCustomAttributesOnSpan: (0, instrumentationUtils_1.fetchCustomAttributeFunctionWithDefaults)(fetchInstrumentationOptions === null || fetchInstrumentationOptions === void 0 ? void 0 : fetchInstrumentationOptions.applyCustomAttributesOnSpan),
63
- // Request hook to add user action context
64
- requestHook: function (span, _) {
65
- try {
66
- var currentAction = faro_core_1.faro.api.getActiveUserAction();
67
- if (currentAction &&
68
- (currentAction === null || currentAction === void 0 ? void 0 : currentAction.getState()) === faro_core_1.UserActionState.Started) {
69
- span.setAttribute('faro.action.user.name', currentAction.name);
70
- span.setAttribute('faro.action.user.parentId', currentAction.parentId);
71
- }
72
- }
73
- catch (_error) {
74
- // Silently fail - don't log to avoid infinite loops
75
- // The span will just not have user action context
76
- }
77
- } });
84
+ // Also supports callers using a custom processor without the RN request monitor.
85
+ requestHook: instrumentationUtils_1.captureFetchUserAction });
78
86
  }
79
87
  function createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions) {
80
88
  return __assign(__assign(__assign(__assign({}, sharedOptions), { ignoreNetworkEvents: true }), xhrInstrumentationOptions), { applyCustomAttributesOnSpan: (0, instrumentationUtils_1.xhrCustomAttributeFunctionWithDefaults)(xhrInstrumentationOptions === null || xhrInstrumentationOptions === void 0 ? void 0 : xhrInstrumentationOptions.applyCustomAttributesOnSpan) });
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2BA,wEAeC;AAED,8EAaC;AAxDD,8EAA4E;AAC5E,oGAAgG;AAIhG,+DAIgC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,8BAA8B,CAAC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IACnF,IAAA,KACJ,oCAAoC,CAAC,OAAO,CAAC,EADvC,0BAA0B,gCAAA,EAAE,wBAAwB,8BAAA,EAAE,WAAW,iBAAA,EAAE,SAAS,eACrC,CAAC;IAEhD,IAAM,gBAAgB,GAA4B,EAAE,CAAC;IAErD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CAAC,IAAI,4CAAoB,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,gBAAgB,CAAC,IAAI,CAAC,IAAI,gEAA6B,CAAC,SAAS,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAgB,iCAAiC,CAC/C,gBAAmC,EACnC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IAEtC,IAAA,KAA6B,oCAAoC,CAAC,OAAO,CAAC,EAAxE,WAAW,iBAAA,EAAE,SAAS,eAAkD,CAAC;IAEjF,gBAAgB,CAAC,OAAO,CAAC,UAAC,eAAe;QACvC,IAAI,eAAe,YAAY,4CAAoB,EAAE,CAAC;YACpD,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACzC,CAAC;aAAM,IAAI,eAAe,YAAY,gEAA6B,EAAE,CAAC;YACpE,eAAe,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,oCAAoC,CAAC,OAAuC;IAEjF,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,OAAO;QACL,0BAA0B,4BAAA;QAC1B,wBAAwB,0BAAA;QACxB,WAAW,EAAE,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC;QAC1F,SAAS,EAAE,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC;KACrF,CAAC;AACJ,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,+CACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B,KAC9B,qBAAqB,EAAE,MAAM;QAC7B,4BAA4B;QAC5B,2BAA2B,EAAE,IAAA,+DAAwC,EACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,iFAAiF;QACjF,WAAW,EAAE,6CAAsB,IACnC;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 { Instrumentation } from '@opentelemetry/instrumentation';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n captureFetchUserAction,\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 { enableFetchInstrumentation, enableXhrInstrumentation, fetchConfig, xhrConfig } =\n resolveDefaultInstrumentationOptions(options);\n\n const instrumentations: InstrumentationOption[] = [];\n\n if (enableFetchInstrumentation) {\n instrumentations.push(new FetchInstrumentation(fetchConfig));\n }\n\n if (enableXhrInstrumentation) {\n instrumentations.push(new XMLHttpRequestInstrumentation(xhrConfig));\n }\n\n return instrumentations;\n}\n\nexport function updateDefaultOTELInstrumentations(\n instrumentations: Instrumentation[],\n options: DefaultInstrumentationsOptions = {}\n): void {\n const { fetchConfig, xhrConfig } = resolveDefaultInstrumentationOptions(options);\n\n instrumentations.forEach((instrumentation) => {\n if (instrumentation instanceof FetchInstrumentation) {\n instrumentation.setConfig(fetchConfig);\n } else if (instrumentation instanceof XMLHttpRequestInstrumentation) {\n instrumentation.setConfig(xhrConfig);\n }\n });\n}\n\nfunction resolveDefaultInstrumentationOptions(options: DefaultInstrumentationsOptions) {\n const {\n enableFetchInstrumentation = true,\n enableXhrInstrumentation = false,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n ...sharedOptions\n } = options;\n\n return {\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n fetchConfig: createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions),\n xhrConfig: createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions),\n };\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 semconvStabilityOptIn: 'http',\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Also supports callers using a custom processor without the RN request monitor.\n requestHook: captureFetchUserAction,\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"]}
@@ -1,11 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.captureFetchUserAction = captureFetchUserAction;
3
4
  exports.setSpanStatusOnFetchError = setSpanStatusOnFetchError;
4
5
  exports.fetchCustomAttributeFunctionWithDefaults = fetchCustomAttributeFunctionWithDefaults;
5
6
  exports.setSpanStatusOnXMLHttpRequestError = setSpanStatusOnXMLHttpRequestError;
6
7
  exports.xhrCustomAttributeFunctionWithDefaults = xhrCustomAttributeFunctionWithDefaults;
7
8
  var api_1 = require("@opentelemetry/api");
8
9
  var faro_core_1 = require("@grafana/faro-core");
10
+ /** Capture request ownership at start, before the monitor can halt the action. */
11
+ function captureFetchUserAction(span) {
12
+ var _a, _b;
13
+ try {
14
+ var action = (_b = (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.getActiveUserAction) === null || _b === void 0 ? void 0 : _b.call(_a);
15
+ if (action && action.getState() === faro_core_1.UserActionState.Started) {
16
+ span.setAttribute('faro.action.user.name', action.name);
17
+ span.setAttribute('faro.action.user.parentId', action.parentId);
18
+ }
19
+ }
20
+ catch (_c) {
21
+ // Instrumentation must not interrupt the request or log recursively.
22
+ }
23
+ }
9
24
  /**
10
25
  * Set span status to ERROR when fetch fails
11
26
  *
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentationUtils.js","sourceRoot":"","sources":["../../../src/instrumentations/instrumentationUtils.ts"],"names":[],"mappings":";;AAqBA,8DAMC;AAiBD,4FAqBC;AAKD,gFAMC;AAqBD,wFAQC;AAzGD,0CAAoD;AAKpD,gDAA6F;AAW7F;;;;GAIG;AACH,SAAgB,yBAAyB,CAAC,IAAU,EAAE,KAAqB;IACzE,IAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IAClE,IAAI,CAAC,SAAS,CAAC;QACb,IAAI,EAAE,oBAAc,CAAC,KAAK;QAC1B,OAAO,SAAA;KACR,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,MAA6B;IACjD,OAAO,SAAS,IAAI,MAAM,IAAI,OAAQ,MAAqB,CAAC,OAAO,KAAK,QAAQ,CAAC;AACnF,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,wCAAwC,CACtD,YAA2C;IAE3C,IAAM,EAAE,GAAiC,UACvC,IAAU,EACV,OAA8B,EAC9B,MAA6B;QAE7B,uCAAuC;QACvC,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QAED,6BAA6B;QAC7B,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,MAAM,YAAY,QAAQ,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACrF,yBAAyB,CAAC,IAAI,EAAE,eAAQ,MAAM,CAAC,MAAM,eAAK,MAAM,CAAC,UAAU,CAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC,CAAC;IACF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,SAAgB,kCAAkC,CAAC,IAAU,EAAE,GAAmB;IAChF,IAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAC1B,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO;IAC3B,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,oBAAc,CAAC,KAAK,EAAE,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,0BAA0B,CAAC,IAAU;;IAC5C,IAAI,CAAC;QACH,IAAM,aAAa,GAAG,MAAA,MAAA,gBAAI,CAAC,GAAG,0CAAE,mBAAmB,kDAAI,CAAC;QACxD,IAAM,KAAK,GAAG,MAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,6DAAI,CAAC;QACtF,IAAI,aAAa,IAAI,CAAC,KAAK,KAAK,2BAAe,CAAC,OAAO,IAAI,KAAK,KAAK,2BAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7F,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;YAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,2DAA2D;IAC7D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,sCAAsC,CACpD,YAAyC;IAEzC,OAAO,UAAC,IAAU,EAAE,GAAmB;QACrC,kCAAkC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9C,0BAA0B,CAAC,IAAI,CAAC,CAAC;QACjC,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/api';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\n/**\n * FetchError interface matching OpenTelemetry's internal type\n * Note: FetchError is not exported from @opentelemetry/instrumentation-fetch\n */\ninterface FetchError {\n status?: number;\n message: string;\n}\n\n/**\n * Set span status to ERROR when fetch fails\n *\n * This ensures that failed HTTP requests are marked as errors in traces.\n */\nexport function setSpanStatusOnFetchError(span: Span, error: Error | string): void {\n const message = typeof error === 'string' ? error : error.message;\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message,\n });\n}\n\n/**\n * Type guard to check if result is a FetchError\n */\nfunction isFetchError(result: Response | FetchError): result is FetchError {\n return 'message' in result && typeof (result as FetchError).message === 'string';\n}\n\n/**\n * Custom attribute function for fetch instrumentation with defaults\n *\n * Combines user-provided custom attributes with default handling.\n *\n * @param userFunction - Optional user-provided custom attribute function\n * @returns Combined custom attribute function\n */\nexport function fetchCustomAttributeFunctionWithDefaults(\n userFunction?: FetchCustomAttributeFunction\n): FetchCustomAttributeFunction {\n const fn: FetchCustomAttributeFunction = (\n span: Span,\n request: Request | RequestInit,\n result: Response | FetchError\n ) => {\n // Call user function first if provided\n if (userFunction) {\n userFunction(span, request, result);\n }\n\n // Add default error handling\n if (isFetchError(result)) {\n setSpanStatusOnFetchError(span, result.message);\n } else if (result instanceof Response && result.status >= 400 && result.status < 600) {\n setSpanStatusOnFetchError(span, `HTTP ${result.status}: ${result.statusText}`);\n }\n };\n return fn;\n}\n\n/**\n * Set span status to ERROR for XHR failures (status 0 or 4xx/5xx).\n */\nexport function setSpanStatusOnXMLHttpRequestError(span: Span, xhr: XMLHttpRequest): void {\n const status = xhr.status;\n if (status == null) return;\n if (status === 0 || (status >= 400 && status < 600)) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n}\n\n/**\n * Add user action context to span when active (for HTTP Errors column in user action table).\n */\nfunction addUserActionContextToSpan(span: Span): void {\n try {\n const currentAction = faro.api?.getActiveUserAction?.();\n const state = (currentAction as unknown as UserActionInternalInterface)?.getState?.();\n if (currentAction && (state === UserActionState.Started || state === UserActionState.Halted)) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_) {\n // Silently fail - don't log to avoid instrumentation loops\n }\n}\n\n/**\n * Custom attribute function for XHR instrumentation with defaults.\n */\nexport function xhrCustomAttributeFunctionWithDefaults(\n userFunction?: XHRCustomAttributeFunction\n): XHRCustomAttributeFunction {\n return (span: Span, xhr: XMLHttpRequest) => {\n setSpanStatusOnXMLHttpRequestError(span, xhr);\n addUserActionContextToSpan(span);\n userFunction?.(span, xhr);\n };\n}\n"]}
1
+ {"version":3,"file":"instrumentationUtils.js","sourceRoot":"","sources":["../../../src/instrumentations/instrumentationUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { SpanStatusCode } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/api';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\n/** Capture request ownership at start, before the monitor can halt the action. */\nexport function captureFetchUserAction(span: Span): void {\n try {\n const action = faro.api?.getActiveUserAction?.();\n if (action && (action as UserActionInternalInterface).getState() === UserActionState.Started) {\n span.setAttribute('faro.action.user.name', action.name);\n span.setAttribute('faro.action.user.parentId', action.parentId);\n }\n } catch {\n // Instrumentation must not interrupt the request or log recursively.\n }\n}\n\n/**\n * FetchError interface matching OpenTelemetry's internal type\n * Note: FetchError is not exported from @opentelemetry/instrumentation-fetch\n */\ninterface FetchError {\n status?: number;\n message: string;\n}\n\n/**\n * Set span status to ERROR when fetch fails\n *\n * This ensures that failed HTTP requests are marked as errors in traces.\n */\nexport function setSpanStatusOnFetchError(span: Span, error: Error | string): void {\n const message = typeof error === 'string' ? error : error.message;\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message,\n });\n}\n\n/**\n * Type guard to check if result is a FetchError\n */\nfunction isFetchError(result: Response | FetchError): result is FetchError {\n return 'message' in result && typeof (result as FetchError).message === 'string';\n}\n\n/**\n * Custom attribute function for fetch instrumentation with defaults\n *\n * Combines user-provided custom attributes with default handling.\n *\n * @param userFunction - Optional user-provided custom attribute function\n * @returns Combined custom attribute function\n */\nexport function fetchCustomAttributeFunctionWithDefaults(\n userFunction?: FetchCustomAttributeFunction\n): FetchCustomAttributeFunction {\n const fn: FetchCustomAttributeFunction = (\n span: Span,\n request: Request | RequestInit,\n result: Response | FetchError\n ) => {\n // Call user function first if provided\n if (userFunction) {\n userFunction(span, request, result);\n }\n\n // Add default error handling\n if (isFetchError(result)) {\n setSpanStatusOnFetchError(span, result.message);\n } else if (result instanceof Response && result.status >= 400 && result.status < 600) {\n setSpanStatusOnFetchError(span, `HTTP ${result.status}: ${result.statusText}`);\n }\n };\n return fn;\n}\n\n/**\n * Set span status to ERROR for XHR failures (status 0 or 4xx/5xx).\n */\nexport function setSpanStatusOnXMLHttpRequestError(span: Span, xhr: XMLHttpRequest): void {\n const status = xhr.status;\n if (status == null) return;\n if (status === 0 || (status >= 400 && status < 600)) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n}\n\n/**\n * Add user action context to span when active (for HTTP Errors column in user action table).\n */\nfunction addUserActionContextToSpan(span: Span): void {\n try {\n const currentAction = faro.api?.getActiveUserAction?.();\n const state = (currentAction as unknown as UserActionInternalInterface)?.getState?.();\n if (currentAction && (state === UserActionState.Started || state === UserActionState.Halted)) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_) {\n // Silently fail - don't log to avoid instrumentation loops\n }\n}\n\n/**\n * Custom attribute function for XHR instrumentation with defaults.\n */\nexport function xhrCustomAttributeFunctionWithDefaults(\n userFunction?: XHRCustomAttributeFunction\n): XHRCustomAttributeFunction {\n return (span: Span, xhr: XMLHttpRequest) => {\n setSpanStatusOnXMLHttpRequestError(span, xhr);\n addUserActionContextToSpan(span);\n userFunction?.(span, xhr);\n };\n}\n"]}
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HttpRequestMonitorSpanProcessor = void 0;
4
+ var core_1 = require("@opentelemetry/core");
4
5
  var faro_react_native_1 = require("@grafana/faro-react-native");
6
+ var instrumentationUtils_1 = require("../instrumentations/instrumentationUtils");
7
+ // Optional XHR instrumentation still emits these attributes.
5
8
  var ATTR_HTTP_METHOD = 'http.method';
6
9
  var ATTR_HTTP_STATUS_CODE = 'http.status_code';
7
10
  var ATTR_HTTP_URL = 'http.url';
@@ -30,7 +33,16 @@ function getAttr(span, key) {
30
33
  * Check if a span is an HTTP span (from FetchInstrumentation).
31
34
  */
32
35
  function isHttpSpan(span) {
33
- return getAttr(span, ATTR_HTTP_URL) != null || getAttr(span, ATTR_HTTP_METHOD) != null;
36
+ // url.full is shared by non-HTTP spans; stable HTTP spans identify their method at creation.
37
+ return getHttpMethod(span) != null || getAttr(span, ATTR_HTTP_URL) != null;
38
+ }
39
+ function getHttpUrl(span) {
40
+ var _a;
41
+ return (_a = getAttr(span, 'url.full')) !== null && _a !== void 0 ? _a : getAttr(span, ATTR_HTTP_URL);
42
+ }
43
+ function getHttpMethod(span) {
44
+ var _a;
45
+ return (_a = getAttr(span, 'http.request.method')) !== null && _a !== void 0 ? _a : getAttr(span, ATTR_HTTP_METHOD);
34
46
  }
35
47
  /**
36
48
  * SpanProcessor that notifies httpRequestMonitor when HTTP spans start and end.
@@ -47,10 +59,15 @@ var HttpRequestMonitorSpanProcessor = /** @class */ (function () {
47
59
  HttpRequestMonitorSpanProcessor.prototype.onStart = function (span, parentContext) {
48
60
  var _a, _b;
49
61
  if (isHttpSpan(span)) {
50
- var url = (_a = getAttr(span, ATTR_HTTP_URL)) !== null && _a !== void 0 ? _a : '';
51
- var method = (_b = getAttr(span, ATTR_HTTP_METHOD)) !== null && _b !== void 0 ? _b : 'GET';
62
+ // Capture before notifyStart changes Started to Halted. New requests while
63
+ // already Halted are not tracked by the controller and must not inherit it.
64
+ if (span.instrumentationScope.name === '@opentelemetry/instrumentation-fetch') {
65
+ (0, instrumentationUtils_1.captureFetchUserAction)(span);
66
+ }
67
+ var url = (_a = getHttpUrl(span)) !== null && _a !== void 0 ? _a : '';
68
+ var method = (_b = getHttpMethod(span)) !== null && _b !== void 0 ? _b : 'GET';
52
69
  var requestId = span.spanContext().spanId;
53
- var startTimeMs = Date.now();
70
+ var startTimeMs = (0, core_1.hrTimeToMilliseconds)(span.startTime);
54
71
  var payload = {
55
72
  requestId: requestId,
56
73
  url: url,
@@ -62,18 +79,15 @@ var HttpRequestMonitorSpanProcessor = /** @class */ (function () {
62
79
  this.processor.onStart(span, parentContext);
63
80
  };
64
81
  HttpRequestMonitorSpanProcessor.prototype.onEnd = function (span) {
65
- var _a, _b;
82
+ var _a, _b, _c;
66
83
  if (isHttpSpan(span)) {
67
- var url = (_a = getAttr(span, ATTR_HTTP_URL)) !== null && _a !== void 0 ? _a : '';
68
- var method = (_b = getAttr(span, ATTR_HTTP_METHOD)) !== null && _b !== void 0 ? _b : 'GET';
84
+ var url = (_a = getHttpUrl(span)) !== null && _a !== void 0 ? _a : '';
85
+ var method = (_b = getHttpMethod(span)) !== null && _b !== void 0 ? _b : 'GET';
69
86
  var requestId = span.spanContext().spanId;
70
- var statusAttr = getAttr(span, ATTR_HTTP_STATUS_CODE);
87
+ var statusAttr = (_c = getAttr(span, 'http.response.status_code')) !== null && _c !== void 0 ? _c : getAttr(span, ATTR_HTTP_STATUS_CODE);
71
88
  var status_1 = statusAttr != null ? parseInt(statusAttr, 10) : undefined;
72
- var spanWithTime = span;
73
- var startNs = spanWithTime.startTimeUnixNano;
74
- var endNs = spanWithTime.endTimeUnixNano;
75
- var startTimeMs = startNs != null && !Number.isNaN(Number(startNs)) ? Number(startNs) / 1000000 : Date.now();
76
- var endTimeMs = endNs != null && !Number.isNaN(Number(endNs)) ? Number(endNs) / 1000000 : Date.now();
89
+ var startTimeMs = (0, core_1.hrTimeToMilliseconds)(span.startTime);
90
+ var endTimeMs = (0, core_1.hrTimeToMilliseconds)(span.endTime);
77
91
  var payload = {
78
92
  requestId: requestId,
79
93
  url: url,
@@ -1 +1 @@
1
- {"version":3,"file":"httpRequestMonitorSpanProcessor.js","sourceRoot":"","sources":["../../../src/processors/httpRequestMonitorSpanProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from '@opentelemetry/api';\nimport type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport {\n type HttpRequestMessagePayload,\n notifyHttpRequestEnd,\n notifyHttpRequestStart,\n} from '@grafana/faro-react-native';\n\nconst ATTR_HTTP_METHOD = 'http.method';\nconst ATTR_HTTP_STATUS_CODE = 'http.status_code';\nconst ATTR_HTTP_URL = 'http.url';\n\n/**\n * Get string attribute from span.\n * Handles both SDK attribute format and OTLP-style attribute values.\n */\nfunction getAttr(span: Span | ReadableSpan, key: string): string | undefined {\n const attrs = span.attributes as Record<string, unknown> | undefined;\n if (attrs == null) return undefined;\n const raw = typeof attrs['get'] === 'function' ? (attrs as { get: (k: string) => unknown })['get'](key) : attrs[key];\n if (raw == null) return undefined;\n if (typeof raw === 'string') return raw.length > 0 ? raw : undefined;\n if (typeof raw === 'object' && raw !== null && 'stringValue' in raw) {\n const s = String((raw as { stringValue?: string }).stringValue ?? '');\n return s.length > 0 ? s : undefined;\n }\n const str = String(raw);\n return str.length > 0 ? str : undefined;\n}\n\n/**\n * Check if a span is an HTTP span (from FetchInstrumentation).\n */\nfunction isHttpSpan(span: Span | ReadableSpan): boolean {\n return getAttr(span, ATTR_HTTP_URL) != null || getAttr(span, ATTR_HTTP_METHOD) != null;\n}\n\n/**\n * SpanProcessor that notifies httpRequestMonitor when HTTP spans start and end.\n *\n * This enables user action correlation (UserActionController halt logic) when\n * TracingInstrumentation is used instead of HttpInstrumentation.\n *\n * IMPORTANT: Must not use console or trigger any instrumentation to avoid loops.\n */\nexport class HttpRequestMonitorSpanProcessor implements SpanProcessor {\n constructor(private readonly processor: SpanProcessor) {}\n\n onStart(span: Span, parentContext: Context): void {\n if (isHttpSpan(span)) {\n const url = getAttr(span, ATTR_HTTP_URL) ?? '';\n const method = getAttr(span, ATTR_HTTP_METHOD) ?? 'GET';\n const requestId = span.spanContext().spanId;\n const startTimeMs = Date.now();\n\n const payload: HttpRequestMessagePayload = {\n requestId,\n url,\n method,\n startTime: startTimeMs,\n };\n notifyHttpRequestStart(payload);\n }\n\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n if (isHttpSpan(span)) {\n const url = getAttr(span, ATTR_HTTP_URL) ?? '';\n const method = getAttr(span, ATTR_HTTP_METHOD) ?? 'GET';\n const requestId = span.spanContext().spanId;\n const statusAttr = getAttr(span, ATTR_HTTP_STATUS_CODE);\n const status = statusAttr != null ? parseInt(statusAttr, 10) : undefined;\n const spanWithTime = span as ReadableSpan & {\n startTimeUnixNano?: number | bigint;\n endTimeUnixNano?: number | bigint;\n };\n const startNs = spanWithTime.startTimeUnixNano;\n const endNs = spanWithTime.endTimeUnixNano;\n const startTimeMs = startNs != null && !Number.isNaN(Number(startNs)) ? Number(startNs) / 1_000_000 : Date.now();\n const endTimeMs = endNs != null && !Number.isNaN(Number(endNs)) ? Number(endNs) / 1_000_000 : Date.now();\n\n const payload: HttpRequestMessagePayload = {\n requestId,\n url,\n method,\n startTime: startTimeMs,\n endTime: endTimeMs,\n status: !Number.isNaN(status) ? status : undefined,\n };\n notifyHttpRequestEnd(payload);\n }\n\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n"]}
1
+ {"version":3,"file":"httpRequestMonitorSpanProcessor.js","sourceRoot":"","sources":["../../../src/processors/httpRequestMonitorSpanProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from '@opentelemetry/api';\nimport { hrTimeToMilliseconds } from '@opentelemetry/core';\nimport type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport {\n type HttpRequestMessagePayload,\n notifyHttpRequestEnd,\n notifyHttpRequestStart,\n} from '@grafana/faro-react-native';\n\nimport { captureFetchUserAction } from '../instrumentations/instrumentationUtils';\n\n// Optional XHR instrumentation still emits these attributes.\nconst ATTR_HTTP_METHOD = 'http.method';\nconst ATTR_HTTP_STATUS_CODE = 'http.status_code';\nconst ATTR_HTTP_URL = 'http.url';\n\n/**\n * Get string attribute from span.\n * Handles both SDK attribute format and OTLP-style attribute values.\n */\nfunction getAttr(span: Span | ReadableSpan, key: string): string | undefined {\n const attrs = span.attributes as Record<string, unknown> | undefined;\n if (attrs == null) return undefined;\n const raw = typeof attrs['get'] === 'function' ? (attrs as { get: (k: string) => unknown })['get'](key) : attrs[key];\n if (raw == null) return undefined;\n if (typeof raw === 'string') return raw.length > 0 ? raw : undefined;\n if (typeof raw === 'object' && raw !== null && 'stringValue' in raw) {\n const s = String((raw as { stringValue?: string }).stringValue ?? '');\n return s.length > 0 ? s : undefined;\n }\n const str = String(raw);\n return str.length > 0 ? str : undefined;\n}\n\n/**\n * Check if a span is an HTTP span (from FetchInstrumentation).\n */\nfunction isHttpSpan(span: Span | ReadableSpan): boolean {\n // url.full is shared by non-HTTP spans; stable HTTP spans identify their method at creation.\n return getHttpMethod(span) != null || getAttr(span, ATTR_HTTP_URL) != null;\n}\n\nfunction getHttpUrl(span: Span | ReadableSpan): string | undefined {\n return getAttr(span, 'url.full') ?? getAttr(span, ATTR_HTTP_URL);\n}\n\nfunction getHttpMethod(span: Span | ReadableSpan): string | undefined {\n return getAttr(span, 'http.request.method') ?? getAttr(span, ATTR_HTTP_METHOD);\n}\n\n/**\n * SpanProcessor that notifies httpRequestMonitor when HTTP spans start and end.\n *\n * This enables user action correlation (UserActionController halt logic) when\n * TracingInstrumentation is used instead of HttpInstrumentation.\n *\n * IMPORTANT: Must not use console or trigger any instrumentation to avoid loops.\n */\nexport class HttpRequestMonitorSpanProcessor implements SpanProcessor {\n constructor(private readonly processor: SpanProcessor) {}\n\n onStart(span: Span, parentContext: Context): void {\n if (isHttpSpan(span)) {\n // Capture before notifyStart changes Started to Halted. New requests while\n // already Halted are not tracked by the controller and must not inherit it.\n if (span.instrumentationScope.name === '@opentelemetry/instrumentation-fetch') {\n captureFetchUserAction(span);\n }\n const url = getHttpUrl(span) ?? '';\n const method = getHttpMethod(span) ?? 'GET';\n const requestId = span.spanContext().spanId;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n\n const payload: HttpRequestMessagePayload = {\n requestId,\n url,\n method,\n startTime: startTimeMs,\n };\n notifyHttpRequestStart(payload);\n }\n\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n if (isHttpSpan(span)) {\n const url = getHttpUrl(span) ?? '';\n const method = getHttpMethod(span) ?? 'GET';\n const requestId = span.spanContext().spanId;\n const statusAttr = getAttr(span, 'http.response.status_code') ?? getAttr(span, ATTR_HTTP_STATUS_CODE);\n const status = statusAttr != null ? parseInt(statusAttr, 10) : undefined;\n const startTimeMs = hrTimeToMilliseconds(span.startTime);\n const endTimeMs = hrTimeToMilliseconds(span.endTime);\n\n const payload: HttpRequestMessagePayload = {\n requestId,\n url,\n method,\n startTime: startTimeMs,\n endTime: endTimeMs,\n status: !Number.isNaN(status) ? status : undefined,\n };\n notifyHttpRequestEnd(payload);\n }\n\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n"]}
@@ -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 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"]}
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 /**\n * A caller-supplied processor. Faro removal flushes it so the tracing instrumentation can be re-added;\n * TracingInstrumentation.shutdown() shuts it down and is terminal for that processor.\n */\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"]}
@@ -2,6 +2,35 @@ import { ESpanKind } from '@opentelemetry/otlp-transformer/build/src/trace/inter
2
2
  import { createInternalLogger, faro, unknownString } from '@grafana/faro-core';
3
3
  const internalLogger = createInternalLogger();
4
4
  const DURATION_NS_KEY = 'duration_ns';
5
+ /** Map stable OTel span attributes to Faro's fetch event contract. */
6
+ function projectFetchEventAttributes(attributes) {
7
+ var _a, _b, _c;
8
+ const fields = {
9
+ 'http.request.method': 'http.method',
10
+ 'url.full': 'http.url',
11
+ 'http.response.status_code': 'http.status_code',
12
+ };
13
+ for (const [spanField, eventField] of Object.entries(fields)) {
14
+ if (attributes[spanField] != null) {
15
+ attributes[eventField] = attributes[spanField];
16
+ delete attributes[spanField];
17
+ }
18
+ }
19
+ const url = attributes['http.url'];
20
+ if (url) {
21
+ try {
22
+ const parsed = new URL(url);
23
+ attributes['http.host'] = (_a = attributes['http.host']) !== null && _a !== void 0 ? _a : parsed.host;
24
+ attributes['http.scheme'] = (_b = attributes['http.scheme']) !== null && _b !== void 0 ? _b : parsed.protocol.replace(':', '');
25
+ }
26
+ catch (_d) {
27
+ // Keep the captured URL even if it cannot supply host/scheme metadata.
28
+ }
29
+ }
30
+ delete attributes['server.address'];
31
+ delete attributes['server.port'];
32
+ attributes['component'] = (_c = attributes['component']) !== null && _c !== void 0 ? _c : 'fetch';
33
+ }
5
34
  /**
6
35
  * Send Faro events for CLIENT spans (HTTP requests, navigation, etc.)
7
36
  *
@@ -38,6 +67,9 @@ export function sendFaroEvents(resourceSpans = []) {
38
67
  for (const attribute of span.attributes) {
39
68
  faroEventAttributes[attribute.key] = String(Object.values(attribute.value)[0]);
40
69
  }
70
+ if ((scope === null || scope === void 0 ? void 0 : scope.name) === '@opentelemetry/instrumentation-fetch') {
71
+ projectFetchEventAttributes(faroEventAttributes);
72
+ }
41
73
  // Add span duration in nanoseconds
42
74
  if (!Number.isNaN(span.endTimeUnixNano) && !Number.isNaN(span.startTimeUnixNano)) {
43
75
  faroEventAttributes[DURATION_NS_KEY] = String(Number(span.endTimeUnixNano) - Number(span.startTimeUnixNano));
@@ -1 +1 @@
1
- {"version":3,"file":"faroTraceExporter.utils.js","sourceRoot":"","sources":["../../../src/exporters/faroTraceExporter.utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SpanContext } from '@opentelemetry/api';\nimport { ESpanKind, type IResourceSpans } from '@opentelemetry/otlp-transformer/build/src/trace/internal-types';\n\nimport { createInternalLogger, faro, unknownString } from '@grafana/faro-core';\nimport type { EventAttributes as FaroEventAttributes } from '@grafana/faro-core';\n\nconst internalLogger = createInternalLogger();\n\nconst DURATION_NS_KEY = 'duration_ns';\n\n/**\n * Send Faro events for CLIENT spans (HTTP requests, navigation, etc.)\n *\n * IMPORTANT: This function is called during trace export and must be careful to avoid infinite loops.\n *\n * Infinite loop prevention strategy:\n * 1. Only process SPAN_KIND_CLIENT spans (HTTP requests, etc.)\n * 2. Use faro.api.pushEvent which:\n * - Does NOT trigger console logs if ConsoleInstrumentation is configured properly\n * - Does NOT trigger HTTP instrumentation for collector URLs (they're ignored)\n * 3. Use internalLogger for debugging instead of console\n * 4. Never call console.log/warn/error in this function\n *\n * @param resourceSpans - OTLP resource spans from trace exporter\n */\nexport function sendFaroEvents(resourceSpans: IResourceSpans[] = []) {\n try {\n for (const resourceSpan of resourceSpans) {\n const { scopeSpans } = resourceSpan;\n\n for (const scopeSpan of scopeSpans) {\n const { scope, spans = [] } = scopeSpan;\n\n for (const span of spans) {\n // Only process CLIENT spans (HTTP requests, external calls)\n // This avoids processing internal spans which could cause loops\n if (span.kind !== ESpanKind.SPAN_KIND_CLIENT) {\n continue;\n }\n\n const spanContext: Pick<SpanContext, 'traceId' | 'spanId'> = {\n traceId: span.traceId.toString(),\n spanId: span.spanId.toString(),\n };\n\n const faroEventAttributes: FaroEventAttributes = {};\n for (const attribute of span.attributes) {\n faroEventAttributes[attribute.key] = String(Object.values(attribute.value)[0]);\n }\n\n // Add span duration in nanoseconds\n if (!Number.isNaN(span.endTimeUnixNano) && !Number.isNaN(span.startTimeUnixNano)) {\n faroEventAttributes[DURATION_NS_KEY] = String(\n Number(span.endTimeUnixNano) - Number(span.startTimeUnixNano)\n );\n }\n\n const index = (scope?.name ?? '').indexOf('-');\n let eventName = unknownString;\n\n if (scope?.name) {\n if (index === -1) {\n eventName = scope.name.split('/')[1] ?? scope.name;\n }\n\n if (index > -1) {\n eventName = scope?.name.substring(index + 1);\n }\n }\n\n // Push event to Faro\n // This should NOT cause infinite loops because:\n // 1. The collector URL is in ignoreUrls for HttpInstrumentation\n // 2. ConsoleInstrumentation won't log this event\n // 3. No console.log calls are made here\n faro.api.pushEvent(`faro.tracing.${eventName}`, faroEventAttributes, undefined, {\n spanContext,\n // Convert nanoseconds to milliseconds\n timestampOverwriteMs: Number(span.endTimeUnixNano) / 1_000_000,\n customPayloadTransformer: (payload) => {\n if (\n faroEventAttributes['faro.action.user.name'] != null &&\n faroEventAttributes['faro.action.user.parentId'] != null\n ) {\n payload.action = {\n name: faroEventAttributes['faro.action.user.name'],\n parentId: faroEventAttributes['faro.action.user.parentId'],\n };\n\n delete payload.attributes?.['faro.action.user.name'];\n delete payload.attributes?.['faro.action.user.parentId'];\n }\n\n return payload;\n },\n });\n }\n }\n }\n } catch (error) {\n // Only log critical errors using internal logger\n // Do NOT use console.log here as it could cause infinite loops\n internalLogger.error('sendFaroEvents: Failed to process spans', error);\n }\n}\n"]}
1
+ {"version":3,"file":"faroTraceExporter.utils.js","sourceRoot":"","sources":["../../../src/exporters/faroTraceExporter.utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SpanContext } from '@opentelemetry/api';\nimport { ESpanKind, type IResourceSpans } from '@opentelemetry/otlp-transformer/build/src/trace/internal-types';\n\nimport { createInternalLogger, faro, unknownString } from '@grafana/faro-core';\nimport type { EventAttributes as FaroEventAttributes } from '@grafana/faro-core';\n\nconst internalLogger = createInternalLogger();\n\nconst DURATION_NS_KEY = 'duration_ns';\n/** Map stable OTel span attributes to Faro's fetch event contract. */\nfunction projectFetchEventAttributes(attributes: FaroEventAttributes): void {\n const fields = {\n 'http.request.method': 'http.method',\n 'url.full': 'http.url',\n 'http.response.status_code': 'http.status_code',\n };\n for (const [spanField, eventField] of Object.entries(fields)) {\n if (attributes[spanField] != null) {\n attributes[eventField] = attributes[spanField];\n delete attributes[spanField];\n }\n }\n\n const url = attributes['http.url'];\n if (url) {\n try {\n const parsed = new URL(url);\n attributes['http.host'] = attributes['http.host'] ?? parsed.host;\n attributes['http.scheme'] = attributes['http.scheme'] ?? parsed.protocol.replace(':', '');\n } catch {\n // Keep the captured URL even if it cannot supply host/scheme metadata.\n }\n }\n delete attributes['server.address'];\n delete attributes['server.port'];\n attributes['component'] = attributes['component'] ?? 'fetch';\n}\n\n/**\n * Send Faro events for CLIENT spans (HTTP requests, navigation, etc.)\n *\n * IMPORTANT: This function is called during trace export and must be careful to avoid infinite loops.\n *\n * Infinite loop prevention strategy:\n * 1. Only process SPAN_KIND_CLIENT spans (HTTP requests, etc.)\n * 2. Use faro.api.pushEvent which:\n * - Does NOT trigger console logs if ConsoleInstrumentation is configured properly\n * - Does NOT trigger HTTP instrumentation for collector URLs (they're ignored)\n * 3. Use internalLogger for debugging instead of console\n * 4. Never call console.log/warn/error in this function\n *\n * @param resourceSpans - OTLP resource spans from trace exporter\n */\nexport function sendFaroEvents(resourceSpans: IResourceSpans[] = []) {\n try {\n for (const resourceSpan of resourceSpans) {\n const { scopeSpans } = resourceSpan;\n\n for (const scopeSpan of scopeSpans) {\n const { scope, spans = [] } = scopeSpan;\n\n for (const span of spans) {\n // Only process CLIENT spans (HTTP requests, external calls)\n // This avoids processing internal spans which could cause loops\n if (span.kind !== ESpanKind.SPAN_KIND_CLIENT) {\n continue;\n }\n\n const spanContext: Pick<SpanContext, 'traceId' | 'spanId'> = {\n traceId: span.traceId.toString(),\n spanId: span.spanId.toString(),\n };\n\n const faroEventAttributes: FaroEventAttributes = {};\n for (const attribute of span.attributes) {\n faroEventAttributes[attribute.key] = String(Object.values(attribute.value)[0]);\n }\n\n if (scope?.name === '@opentelemetry/instrumentation-fetch') {\n projectFetchEventAttributes(faroEventAttributes);\n }\n\n // Add span duration in nanoseconds\n if (!Number.isNaN(span.endTimeUnixNano) && !Number.isNaN(span.startTimeUnixNano)) {\n faroEventAttributes[DURATION_NS_KEY] = String(\n Number(span.endTimeUnixNano) - Number(span.startTimeUnixNano)\n );\n }\n\n const index = (scope?.name ?? '').indexOf('-');\n let eventName = unknownString;\n\n if (scope?.name) {\n if (index === -1) {\n eventName = scope.name.split('/')[1] ?? scope.name;\n }\n\n if (index > -1) {\n eventName = scope?.name.substring(index + 1);\n }\n }\n\n // Push event to Faro\n // This should NOT cause infinite loops because:\n // 1. The collector URL is in ignoreUrls for HttpInstrumentation\n // 2. ConsoleInstrumentation won't log this event\n // 3. No console.log calls are made here\n faro.api.pushEvent(`faro.tracing.${eventName}`, faroEventAttributes, undefined, {\n spanContext,\n // Convert nanoseconds to milliseconds\n timestampOverwriteMs: Number(span.endTimeUnixNano) / 1_000_000,\n customPayloadTransformer: (payload) => {\n if (\n faroEventAttributes['faro.action.user.name'] != null &&\n faroEventAttributes['faro.action.user.parentId'] != null\n ) {\n payload.action = {\n name: faroEventAttributes['faro.action.user.name'],\n parentId: faroEventAttributes['faro.action.user.parentId'],\n };\n\n delete payload.attributes?.['faro.action.user.name'];\n delete payload.attributes?.['faro.action.user.parentId'];\n }\n\n return payload;\n },\n });\n }\n }\n }\n } catch (error) {\n // Only log critical errors using internal logger\n // Do NOT use console.log here as it could cause infinite loops\n internalLogger.error('sendFaroEvents: Failed to process spans', error);\n }\n}\n"]}