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

Cannot read properties of undefined (reading 'pushError') #573

Closed
FutureKode opened this issue Apr 30, 2024 · 7 comments
Closed

Cannot read properties of undefined (reading 'pushError') #573

FutureKode opened this issue Apr 30, 2024 · 7 comments
Labels
bug Report a bug

Comments

@FutureKode
Copy link

Description

Trying to replace my ErrorBoundary with FaroErrorBoundar, this error appears when the boundary is hit:

Cannot read properties of undefined (reading 'pushError')

Steps to reproduce

  1. Add Faro to the project
  2. Replace ErrorBoundary with FaroErrorBoundary

Expected behavior

It should work as intended.

Actual behavior

Fails with error message

Screenshot 2024-04-30 at 11 26 37

Environment

"@grafana/faro-react": "^1.7.0",
"@grafana/faro-web-sdk": "^1.7.0",
"@grafana/faro-web-tracing": "^1.7.0",
MacBook Air
macOS Sonoma
Chrome 124

Demo

Context

@FutureKode FutureKode added the bug Report a bug label Apr 30, 2024
@codecapitano
Copy link
Collaborator

Thanks for reporting @FutureKode
It's the first time seeing this error/

Would you mind sharing your Faro init code?

@FutureKode
Copy link
Author

Thanks for looking @codecapitano

faro init:

import {
  ExceptionEvent,
  getWebInstrumentations,
  initializeFaro,
  LogEvent,
  LogLevel,
  TraceEvent,
  TransportItemType,
} from "@grafana/faro-web-sdk";
import { TracingInstrumentation } from "@grafana/faro-web-tracing";
import type { IKeyValue } from "@opentelemetry/otlp-transformer";

import env from "@/env";
import { getLogRocketSessionUrl } from "@/utils/logrocketUtil";

import { storage } from "../../../utils/localStorageUtils";

export const initFaro = () => {
  const faroCollectorUrl = `https://faro-collector-prod-eu-west-0.grafana.net/collect/${env.FARO_KEY}`;

  const enrichResourceWithAttributes = new Map([
    ["deployment.environment", env.ROGER_ENV],
    ["service.namespace", "roger"],
  ]);

  console.log("env", env);

  const cluster = env.ROGER_ENV?.replace("production", "prod");
  const allowedHosts = [env.API_URL_PUBLIC, env.OIDC_ENDPOINT].map(
    (url) => new URL(url).host,
  );

  return initializeFaro({
    url: faroCollectorUrl,
    beforeSend: (data) => {
      if (window.location.pathname.includes("/auth")) {
        return null;
      }

      const logRocketUrl = getLogRocketSessionUrl() ?? "n/a";

      switch (data.type) {
        case TransportItemType.EXCEPTION:
          enrichExceptions();
          break;
        case TransportItemType.LOG:
          enrichLogs();
          break;
        case TransportItemType.TRACE:
          filterTraces();
          enrichTraces();
          break;
        default:
          break;
      }

      return data;

      function enrichExceptions() {
        const exceptionEvent = data.payload as ExceptionEvent;

        if (!exceptionEvent.context) {
          // eslint-disable-next-line functional/immutable-data
          exceptionEvent.context = {};
        }
        // eslint-disable-next-line functional/immutable-data
        exceptionEvent.context["logrocket_url"] = logRocketUrl;
      }

      function enrichLogs() {
        const logEvent = data.payload as LogEvent;

        if (logEvent.level === LogLevel.ERROR) {
          // eslint-disable-next-line functional/immutable-data
          logEvent.context["logrocket_url"] = logRocketUrl;
        }
      }

      function enrichTraces() {
        const traceEvent = data.payload as TraceEvent;

        const enrichSpanWithAttributes = new Map([
          ["cluster", cluster],
          ["team.id", storage.team.getActiveTeamId()],
          ["user.id", storage.user.getUserId()],
          ["expense.id", getExpenseId()],
        ]);

        traceEvent.resourceSpans?.forEach((resourceSpan) => {
          const attributes = resourceSpan?.resource?.attributes;
          // eslint-disable-next-line functional/prefer-readonly-type
          const resourceAttributes = attributes as IKeyValue[];

          enrichResourceAttributes();
          enrichSpanAttributes();

          function enrichSpanAttributes() {
            resourceSpan.scopeSpans?.forEach((scopeSpan) => {
              scopeSpan.spans?.forEach((span) => {
                // eslint-disable-next-line functional/prefer-readonly-type
                const spanAttributes = span.attributes as IKeyValue[];
                enrichSpanWithAttributes.forEach((value, key) => {
                  const attribute = spanAttributes.find((x) => x.key === key);

                  if (attribute || !value) {
                    return;
                  }
                  // eslint-disable-next-line functional/immutable-data
                  spanAttributes.push({
                    key,
                    value: {
                      stringValue: value,
                    },
                  });
                });
              });
            });
          }

          function enrichResourceAttributes() {
            enrichResourceWithAttributes.forEach((value, key) => {
              const attribute = resourceAttributes.find((x) => x.key === key);
              if (attribute || !value) {
                return;
              }
              // eslint-disable-next-line functional/immutable-data
              resourceAttributes.push({
                key,
                value: {
                  stringValue: value,
                },
              });
            });
          }
        });

        function getExpenseId(): string | undefined {
          const regex = /\/expenses\/([a-zA-Z0-9]+)/;
          const match = window.location.pathname.match(regex);

          if (match && match[1]) {
            const expenseId = match[1];
            return expenseId;
          }

          return storage.expense.getExpenseId();
        }
      }

      function filterTraces() {
        const traceEvent = data.payload as TraceEvent;

        traceEvent.resourceSpans?.forEach((resourceSpan) => {
          resourceSpan.scopeSpans?.forEach((scopeSpan) => {
            const filteredSpans = scopeSpan.spans?.filter((span) => {
              // eslint-disable-next-line functional/prefer-readonly-type
              const spanAttributes = span.attributes as IKeyValue[];

              const ignore = spanAttributes.find(
                (a) =>
                  a.key === "http.host" &&
                  a.value.stringValue &&
                  !allowedHosts.includes(a.value.stringValue),
              );
              return !ignore;
            });

            // eslint-disable-next-line functional/immutable-data
            scopeSpan.spans = filteredSpans;
          });
        });
      }
    },
    app: {
      name: "web",
      version: "1.0.1",
      environment: env.ROGER_ENV,
    },
    instrumentations: [
      // mandatory, overwriting the instrumentations array would cause the default instrumentations to be omitted
      ...getWebInstrumentations({
        captureConsole: true,
        captureConsoleDisabledLevels: [
          LogLevel.DEBUG,
          LogLevel.INFO,
          LogLevel.LOG,
        ],
        enablePerformanceInstrumentation: false,
      }),

      // initialization of the tracing package.
      // this packages is optional because it increases the bundle size noticeably. Only add it if you want tracing data.
      new TracingInstrumentation({
        instrumentationOptions: {
          // requests to these URLs will have tracing headers attached.
          propagateTraceHeaderCorsUrls: allowedHosts.map(
            (host) => new RegExp(`^.*${host}.*$`),
          ),
        },
      }),
    ],
  });
};

Error boundary:

    <FaroErrorBoundary onError={onError} fallback={<ErrorBoundaryUI type={type} />}>
      {children}
    </FaroErrorBoundary>

@codecapitano
Copy link
Collaborator

@FutureKode Config looks good so far.
When do you initialize Faro?
Is it initialized and available before the React App?

Here's an example form the Faro Demo app

initializeFaro();

@codecapitano
Copy link
Collaborator

codecapitano commented Apr 30, 2024

Maybe one other thing to try.

You mentioned the following dependencies:
"@grafana/faro-react": "^1.7.0",
"@grafana/faro-web-sdk": "^1.7.0",
"@grafana/faro-web-tracing": "^1.7.0",

When you use faro-react you only need to install "@grafana/faro-react": "^1.7.0" and the "@grafana/faro-web-tracing": "^1.7.0", if you want otel tracing.

Can you test if the error goes away if you only install those two packages?

@FutureKode
Copy link
Author

@codecapitano Yes faro is initialized like your example, from the app entry point, and I see a console.log from inside the initialisation code.

I tried removing "@grafana/faro-web-sdk" and just using faro-react and faro-web-tracing. But still the error persists 😿

@codecapitano
Copy link
Collaborator

codecapitano commented May 2, 2024

Hi @FutureKode I found the problem aka I've overseen this part in the init code.

So the problem is that you still need to manually instantiate the ReactInstrumentation.
It's missing in the docs, I'll update them.

Future wise faro-react should do this automatically if no extra config is needed. I'll open an issue.

You need to add this to the instrumentations array in your config: new ReactIntegration()

Cheers
Marco

If you want to instrument the React router you do this via the new ReactIntegration() as well.

@FutureKode
Copy link
Author

Thanks @codecapitano - that fixed it 👍🏻

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

No branches or pull requests

2 participants