Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempting to use Datadog RUM with Nuxt in plugin, but fetch is already aliased before RUM can overwrite it. #339

Open
bradlis7 opened this issue Dec 6, 2023 · 4 comments

Comments

@bradlis7
Copy link

bradlis7 commented Dec 6, 2023

Environment

"@datadog/browser-rum": "^5.0.0",
"nuxt": "^3.7.4",

Reproduction

Not sure if I can set up an example as this might be a paid product only.

Describe the bug

I have the integration set up as a Nuxt plugin, and datadog attempts to override window.fetch, but at that point, fetch has already been stored as the original function in ofetch, so none of the callbacks get intercepted with appropriate headers. I don't see a manual way to override fetch after the fact. Is there a way to override this fetch in Nuxt?

Additional context

No response

Logs

No response

@adam-gipril
Copy link

I also experience this issue.

@adam-gipril
Copy link

adam-gipril commented Jan 7, 2024

I've found a workaround for my use case: a small Nuxt app generated to a static SPA with no server-side component.

"@datadog/browser-rum": "^5.4.0",
"nuxt": "^3.9.0",
"ofetch": "^1.3.3",
nuxt.config.ts
export default defineNuxtConfig({
  ssr: false,
  nitro: {
   static: true,
    prerender: {
      autoSubfolderIndex: false,
      routes: ['/index.html', '/success.html'],
    },
  },
})

plugins/01.datadogRum.ts

import { datadogRum } from '@datadog/browser-rum'

export default defineNuxtPlugin(nuxtApp => {
  datadogRum.init({
    clientToken: '<CLIENT_TOKEN>',
    applicationId: '<APPLICATION_ID>',
    site: 'datadoghq.com',
  })

  return {
    provide: {
      datadogRum,
    },
  }
})

Invoking datadogRum.init eventually causes createFetchObservable which causes instrumentMethod.

Important

To capture RUM resource data on fetch calls, the RUM instrumentationWrapper (packaged by the instrumentMethod function as window.fetch) must be invoked. ofetch invokes native fetch directly through its stored reference, bypassing Datadog's instrumentationWrapper.

I wonder if a Datadog Nuxt module could solve this issue as a way to run datadogRum.init before $fetch gets created? 🤔

plugins/02.instrumentedFetch.ts (the workaround)

import { createFetch, type FetchRequest, type FetchOptions } from 'ofetch'

export default defineNuxtPlugin(nuxtApp => {
  /**
   * Create a new ofetch, passing in the version of fetch that is now the RUM instrumentationWrapper
   * put in place by the invocation of datadogRum.init in the 01.datadogRum plugin.
   */
  function instrumentedFetch<
    T = unknown,
    R extends FetchRequest = FetchRequest,
    O extends FetchOptions = FetchOptions,
  >(request: R, opts: O) {
    return <Promise<T>>createFetch({ fetch })(request, opts) // Optionally configure fetch defaults here
  }

  return {
    provide: {
      instrumentedFetch,
    },
  }
})

You can now invoke useNuxtApp().$instrumentedFetch() with mostly the same function signature as $fetch().

Warning

As I mentioned, my application doesn't have a server component. I wonder if useAsyncData could be used with the instrumented fetch? I could be unaware of other server-side functionality this approach lacks from the built-in fetch composables.

@adam-gipril
Copy link

That said, I don't think this is actually an issue with ofetch.

@adam-gipril
Copy link

adam-gipril commented Jan 7, 2024

The workaround above did break the functionality of the registerEndpoint helper from @nuxt/test-utils v3.9.0.

For some reason in my runtime, only globalThis.$fetch gets replaced with the vitest-environment-nuxt version assigned here (or maybe globalThis.fetch gets overridden by something else after vitest-environment-nuxt while globalThis.$fetch doesn't?).

Whatever the cause, adding this line to a file included in our Vitest setupFiles got our instrumentedFetch helper to work with registerEndpoint.

__tests__/setup.ts

import type { $Fetch } from 'ofetch'

/* Causes instrumentedFetch to use the fetch that works with registerEndpoint */
globalThis.fetch = (globalThis.$fetch as $Fetch).native
vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    setupFiles: ['__tests__/setup.ts'],
  },
})

Tip

This plugin that runs only in our test environment was a convenient way to mock the RUM SDK.

__tests__/__plugins__/mockDatadogRum.ts
import { vi } from 'vitest'
import { defineNuxtPlugin } from 'nuxt/app'

export default defineNuxtPlugin(() => {
  vi.mock('@datadog/browser-rum', () => ({
    datadogRum: {
      init: vi.fn(),
      addAction: vi.fn(),
      addError: vi.fn(),
    },
  }))
})
vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        overrides: {
          plugins: ['__tests__/__plugins__/mockDatadogRum.ts'],
        },
      },
    },
    setupFiles: ['__tests__/setup.ts'],
  },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants