Closed FutureKode closed 5 months ago
Thanks for reporting @FutureKode It's the first time seeing this error/
Would you mind sharing your Faro init code?
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>
@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
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?
@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 😿
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.
Thanks @codecapitano - that fixed it 👍🏻
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
Expected behavior
It should work as intended.
Actual behavior
Fails with error message
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