signalfx/splunk-otel-js-web

Next.js: how to use SplunkRum.setGlobalAttributes?

seanparmelee opened this issue · 10 comments

We have integrating Splunk RUM into a number of Next.js apps, following the directions here.

We'd like to now use setGlobalAttributes to add some additional information to our spans. It seems that if we import SplunkRum from '@splunk/otel-web' and try to use it in one of our components, we'll encounter an error during build:

./node_modules/@splunk/otel-web/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js
Critical dependency: the request of a dependency is an expression

Import trace for requested module:
./node_modules/@splunk/otel-web/node_modules/@opentelemetry/instrumentation/build/src/platform/node/index.js
./node_modules/@splunk/otel-web/node_modules/@opentelemetry/instrumentation/build/src/platform/index.js
./node_modules/@splunk/otel-web/node_modules/@opentelemetry/instrumentation/build/src/index.js
./node_modules/@splunk/otel-web/dist/esm/index.js
./pages/list.js


> Build error occurred
ReferenceError: NodeList is not defined

I saw this was brought up in #222, but I'm still left wondering how to use setGlobalAttributes. I understand that Splunk Rum is only meant to be used on the client side, but even if we add some checks to ensure setGlobalAttributes is only called on the client-side, simply having the import in the page causes the build to fail with the aforementioned error.

Are you sure you're using @splunk/otel-web only on the client side? As explained in https://github.com/signalfx/splunk-otel-js-web/blob/main/examples/next-ssr-example/README.md.

@jtmal-signalfx can you please provide an example of how to call setGlobalAttributes in a NextJS application? Happy to add it the README.

I just added:

SplunkRum.setGlobalAttributes({
  testAttr1: 'test-attr-1-value',
});

to src/splunk-rum.js like in the tutorial in docs. Works fine for me. Maybe create a reproduction in a repo or codesandbox.io?

It looks like you're trying to use Splunk RUM in server-side code.

@jtmal-signalfx We're already setting some global attributes in the splunk-rum file (no need to call setGlobalAttributes there because they can be passed into the init call):

SplunkRum.init({
   ...
   testAttr1: 'test-attr-1-value'
});

It'd be useful to be able to call that function when the app is running (in my case, I don't know the values until then).

Here's a reproduction repo to check out: https://github.com/seanparmelee/next-splunk-rum

I added the call to a useEffect so it only runs on the client: https://github.com/seanparmelee/next-splunk-rum/blob/main/pages/index.tsx#L9-L13, but the app won't even build.

  useEffect(() => {
    window.SplunkRum?.setGlobalAttributes({
      foo: 'bar'
    });
  }, []);
import SplunkRum from '@splunk/otel-web';

(typeof window == 'object' ? window : {}).SplunkRum = SplunkRum;

seems to be working for me, so you should be able to get it up and running. I'll talk to the team about it.

Edit: to clarify, you can't import @splunk/otel-web in potentially server-side code.

Thanks @jtmal-signalfx. I'd be curious if there's any benefit to continuing down this path vs instrumenting splunk rum via a script tag. At the moment, seems the npm package causes more JS to be bundled into the app (due to multiple versions of the otel dependencies being bundled with the app) and it requires SplunkRum to be manually added to the window if you want to be call any of the functions elsewhere in the app. We decided to self host the script for now.

I'll move this to our internal backlog for now. If anyone finds their way here and we still haven't implemented anything that works for you, let us know.

I have a similar problem. We are trying to use SplunkRum in our React project which uses Next.js. I've already modified next.config.js as recommended in the Splunk guideline.

So the SplunkRum.init() code alone works fine. But when I try to also add SplunkRum.setGlobalAttributes() in a different place in the code, then the project build fails with a ReferenceError: NodeList is not defined error.

Is there a solution for this yet?

That means you put the code in either a backend section, or an isomorphic section. If we just made it compile, which is technically possible, we could end up with a code which executes on the backend, and the attributes are not present on data sent from the frontend. I'll try to raise this again internally.

I'm using the same guide, I read and tested all of your comments, but when I run this code the output is: undefined

"use client";
import { useEffect } from "react";

export const useMonitoring = () => {
  useEffect(() => {
    console.log(window?.SplunkRum);
  }, []);
};

instrument.js

/* globals process */
import SplunkRum from "@splunk/otel-web";

(typeof window == "object" ? window : {}).SplunkRum = SplunkRum;


SplunkRum.init({
    beaconEndpoint: process.env.NEXT_PUBLIC_SPLUNK_URL,
    rumAccessToken: process.env.NEXT_PUBLIC_SPLUNK_TOKEN,
    applicationName: process.env.NEXT_PUBLIC_SPLUNK_APP,
    deploymentEnvironment: process.env.NEXT_PUBLIC_SPLUNK_ENV,
  });

next.config in the same folder as instrument

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "export",
  images: {
    unoptimized: true,
  },
  transpilePackages: ["ui"],
  reactStrictMode: true,
  webpack: (config, { isServer }) => {
    if (isServer) {
      return config;
    }

    const origEntry = config.entry;
    const entry = async () => {
      let entries = origEntry;
      if (typeof entries === "function") {
        entries = await entries();
      }

      const instrumentFile = "./instrument.js";

      // Webpack accepts string, string[] or object as entrypoint's value.
      // https://webpack.js.org/configuration/entry-context/#entry
      // Generally in our testing main is just a string value
      // but for completeness/future saftey this covers all
      if (typeof entries.main === "string") {
        entries.main = [instrumentFile, entries.main];
      } else if (Array.isArray(entries.main)) {
        entries.main = [instrumentFile, ...entries.main];
      } else {
        let imported = entries.main.import;
        if (typeof imported === "string") {
          imported = [instrumentFile, imported];
        } else {
          imported = [instrumentFile, ...imported];
        }

        entries.main = {
          ...entries.main,
          import: imported,
        };
      }

      return entries;
    };

    // Replace entry in config with new value
    return {
      ...config,
      entry,
    };
  },
};

module.exports = nextConfig;

I'm using this version

"@splunk/otel-web": "^0.16.4",