Skip to content

XHR (Ajax) auto-instrumentation never attaches to XMLHttpRequest.prototype in Vite/Rollup production build (fetch instrumentation works fine) #2750

Description

Describe the bug

In a production build produced by Vite/Rollup, the Ajax (XHR) auto-instrumentation performed by @microsoft/applicationinsights-dependencies-js (AjaxPlugin / AjaxMonitor) never attaches to XMLHttpRequest.prototype. As a result:

  • XMLHttpRequest.prototype.open / .send remain the browser's native functions after SDK initialization (verified via XMLHttpRequest.prototype.open.toString()"function open() { [native code] }").
  • No _ajaxData marker is ever attached to XHR instances.
  • No traceparent (W3C distributed tracing) header, nor any dependency telemetry, is ever generated for requests made via XMLHttpRequest (e.g. through axios's default xhr adapter).
  • Fetch instrumentation works correctly in the exact same build/runtime: window.fetch is wrapped (not native), traceparent is correctly injected, and dependency telemetry is correctly generated for fetch()-based calls (e.g. @azure/msal-browser, which uses fetch internally).

This means, in this build, disableAjaxTracking: false is effectively a no-op for XHR: automatic Ajax dependency tracking and distributed-tracing header injection silently never happen for any XHR-based HTTP client, while disableFetchTracking: false behaves as expected.

To Reproduce

  1. Vue 3 + TypeScript SPA, built with Vite 5.4.10 (Rollup under the hood), served via vite preview (i.e. testing the real production bundle, not the dev server).
  2. @microsoft/applicationinsights-web 3.4.3, with the applicationinsights-dependencies-js 3.4.3 Ajax extension included (default), configured with:
    {
      distributedTracingMode: DistributedTracingModes.W3C,
      enableCorsCorrelation: true,
      maxAjaxCallsPerView: -1,
      isBeaconApiDisabled: false,
      disableAjaxTracking: false,
      disableFetchTracking: false
    }
  3. Application performs API calls via axios (default xhr adapter) to a same-origin/CORS-enabled backend, and separately performs fetch() calls (via MSAL).
  4. Load the production build in a real browser (Chromium), open DevTools/network capture, and observe:
    • Requests made via axios/XHR never carry a traceparent header.
    • Requests made via fetch (MSAL) correctly carry a traceparent header.
  5. Confirm directly in the console:
    XMLHttpRequest.prototype.open.toString().includes('[native code]') // → true (should be false once instrumented)
    XMLHttpRequest.prototype.send.toString().includes('[native code]') // → true (should be false once instrumented)
    window.fetch.toString().includes('[native code]')                 // → false (correctly wrapped)

Expected behavior

With disableAjaxTracking: false, XMLHttpRequest.prototype.open/.send should be patched by the SDK (via InstrumentProto) the same way window.fetch is patched (via InstrumentFunc), and XHR requests should carry the traceparent header and produce dependency telemetry, consistently with fetch-based requests.

Investigation done so far

We spent significant effort ruling out application-level causes before concluding this looks like an SDK/bundler-interaction issue:

  • Not Azure-infrastructure-specific (reproduces identically against a locally-served production build with vite preview).
  • Not caused by any custom app-level network patch (fully disabled/removed, bug persists).
  • Not caused by duplicate SDK instances.
  • Not a Rollup tree-shaking / dynamicProto side-effect elimination issue: we forced treeshake: { moduleSideEffects: true } in the Vite/Rollup config, rebuilt, and compared the output bundle — the emitted JS was byte-identical with and without this setting, proving nothing relevant was being tree-shaken to begin with (consistent with the fact that none of the @microsoft/* packages declare "sideEffects": false).
  • XMLHttpRequest.prototype is not frozen/non-extensible/non-writable in the affected runtime (verified directly: Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'open'){ writable: true, enumerable: true, configurable: true }, and manually reassigning XMLHttpRequest.prototype.open = XMLHttpRequest.prototype.open does not throw), so the SDK's own internal _supportsAjaxMonitoring() write-capability probe (in ajax.js) should not fail either.
  • Traced the source in applicationinsights-dependencies-js/dist-es5/ajax.js: initialize() calls _populateDefaults(config); _instrumentXhr(); _instrumentFetch(); _populateContext(); in sequence — since _instrumentFetch() demonstrably runs and succeeds (fetch is instrumented), _instrumentXhr() must also execute (no way for the second statement to be skipped without the first running), yet it fails to leave any observable trace on XMLHttpRequest.prototype, and does not log any AI-internal critical/warning message even with loggingLevelConsole: 2 set.
  • _instrumentXhr()'s only external difference vs _instrumentFetch() is that it instruments via InstrumentProto(XMLHttpRequest, "open"/"send", ...) (i.e. InstrumentFunc(target.prototype, ...)), while fetch is instrumented via InstrumentFunc(globalObject, "fetch", ...) directly on the global object rather than through a .prototype indirection — this appears to be the key structural difference between the two code paths, though we were not able to pin down the exact failing line inside the minified/bundled InstrumentFunc/InstrumentProto implementation (applicationinsights-core-js/dist-es5/core/InstrumentHooks.js) in this specific Rollup output.

Versions

  • @microsoft/applicationinsights-web: 3.4.3
  • @microsoft/applicationinsights-dependencies-js: 3.4.3 (bundled via applicationinsights-web)
  • @microsoft/applicationinsights-core-js: (bundled dependency of the above)
  • Build tool: Vite 5.4.10 / Rollup (via vite build)
  • Browser tested: Chromium (Playwright-driven), also reproduced in a real user session
  • axios: 1.16.0 (default xhr adapter)

Additional context

We are aware axios 1.7+ supports an adapter: 'fetch' option, and switching to it works around the issue entirely (since fetch instrumentation is unaffected) — but we wanted to report this because it seems like a genuine compatibility issue between the SDK's Ajax/XHR auto-instrumentation and a Vite/Rollup-bundled production build, which could affect any consumer using XHR-based HTTP clients (axios, jQuery.ajax, raw XHR, etc.) under similar bundler conditions. Happy to provide a minimal reproduction repository if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions