grafana / faro-web-sdk

The Grafana Faro Web SDK, part of the Grafana Faro project, is a highly configurable web SDK for real user monitoring (RUM) that instruments browser frontend applications to capture observability signals. Frontend telemetry can then be correlated with backend and infrastructure data for full-stack observability.
https://grafana.com/oss/faro/
Apache License 2.0
750 stars 66 forks source link

initializeFaro works only once #689

Closed DuudeXX8 closed 2 weeks ago

DuudeXX8 commented 1 month ago

I need send traceId to beforeSend method. To get track whole process from start to end. But each time it get's first value which I applied. It does not update. Even if traceId value does change multiple times it does not update traceId. Always use initial traceId. How can I make this initializeFaro update when new traceId sended to this hook?

    export const useGrafanaFaro = (
      serviceName: string,
      traceId?: string,
      options: IFaroOptionalParams<typeof useHistory> = {
        version: DEFAULT_VERSION,
        collectorURL: DEFAULT_COLLECTOR_URL,
      },
    ) => {
    // other configuration
    beforeSend: (item: TransportItem<APIEvent>) => {
      const getTraceId = (traceIdFromMap: string | Uint8Array): string | Uint8Array => {
        if (traceId) {
          return traceId as string;
        }

        return traceIdFromMap;
      };

      if (item?.type === 'trace' && 'resourceSpans' in item.payload) {
        const updatedItem = {
          ...item,
          payload: {
            ...item.payload,
            resourceSpans: item.payload.resourceSpans.map((resourceSpan) => ({
              ...resourceSpan,
              scopeSpans: resourceSpan.scopeSpans.map((scopeSpan) => ({
                ...scopeSpan,
                spans: scopeSpan.spans.map((span) => ({
                  ...span,
                  traceId: getTraceId(span.traceId), // This line gets only first value. But in console I seen that traceId value changes.
                })),
              })),
            })),
          },
        };
        return updatedItem;
      }
      return item;
    },
export const useGrafanaFaro = (
  serviceName: string,
  traceId?: string,
  options: IFaroOptionalParams<typeof useHistory> = {
    version: DEFAULT_VERSION,
    collectorURL: DEFAULT_COLLECTOR_URL,
  },
) => {
  const faroConfig: any = useMemo(() => {
    configureGrafanaFaro<typeof history>(serviceName, traceId, { ...options, history, Route });
  }, [serviceName, options, traceId]);

  useEffect(
    /** Initialize Grafana Faro with the specified service name and options. */
    () => {
      initializeFaro(faroConfig);
    },
    [serviceName, options, traceId, faroConfig],
  );
};

I try almost everything. Any help would be appreciated.

codecapitano commented 1 month ago

Hi @DuudeXX8

The beforeSend function is called for each item before sending it to the collector. Means if a new trace is send via pushTrace() the beforeSend hook it is called again.

Can you elaborate on your use case? Like why do you want to manually mutate the traceID in a span? Each span already contains the parent traceID.

codecapitano commented 1 month ago

I also do would not recommend to re-initialize the whole Faro if e. g. the traceId changes. This can lead to problems in the worst and but puts a lot of extra load on your users browsers.

DuudeXX8 commented 1 month ago

@codecapitano hi and thanks for the answer. Could you please show me a example how to send traceId via pushTrace?

About my case why I need to mutate traceId, this need for track current process start and end phases. When traceId is not the same we can't track when process start and when it ends. If you have solution for track processes start/end in application without mutate traceId please share with me. But I can't find one.

codecapitano commented 1 month ago

It looks like you use Faro + Otel in the browser?

If yes, there are two ways you can connect faro with otel.

Either you add the web-tracing package which adds otel xhr and fetch instrumentation under the hood. It can be configured to you needs.

Or if you need it more flexible you can connect Faro and otel like this:

DuudeXX8 commented 4 weeks ago

I'm not using opentelemetry only grafana and related packages. Grafana packages list and versions.

"@grafana/faro-react": "^1.7.3", "@grafana/faro-web-sdk": "^1.7.3", "@grafana/faro-web-tracing": "^1.7.3",

My grafana configuration


  serviceName: string,
  traceId,
  options: IFaroOptionalParams<H>,
) => {
  const {
    collectorURL, version, history, Route,
  } = options ?? {};

  return {
    url: collectorURL,
    app: {
      // The version of the application.
      version,
      // The name of the application, combining a static prefix with the service name.
      name: myurl,
      // The deployment environment, defaulting to PREPROD if not specified.
      environment: process.env.DEPLOY_ENV || EEnv.PREPROD,
    },
    dedupe: true,
    instrumentations: [
      // Mandatory, overwriting the instrumentations array would cause the default instrumentations to be omitted.
      ...getWebInstrumentations(),
      // Initialization of the tracing package.
      // This package is optional because it increases the bundle size noticeably. Only add it if you want tracing data.
      new TracingInstrumentation(),
      // React Integration for Faro, including React Router v6 dependencies.
      new ReactIntegration({
        router: createReactRouterV5Options({
          history, // the history object used by react-router
          Route, // Route component imported from react-router package
        }),
      }),
    ],

    isolate: true,
    internalLoggerLevel: InternalLoggerLevel.INFO,
    beforeSend: (item: TransportItem<APIEvent>) => {
      const getTraceId = (traceIdFromMap: string | Uint8Array): string | Uint8Array => {
        if (traceId) {
          return traceId as string;
        }

        return traceIdFromMap;
      };

      if (item.type === 'trace' && 'resourceSpans' in item.payload) {
        const updatedItem = {
          ...item,
          payload: {
            ...item.payload,
            resourceSpans: item.payload.resourceSpans.map((resourceSpan) => ({
              ...resourceSpan,
              scopeSpans: resourceSpan.scopeSpans.map((scopeSpan) => ({
                ...scopeSpan,
                spans: scopeSpan.spans.map((span) => ({
                  ...span,
                  traceId: getTraceId(span.traceId),
                })),
              })),
            })),
          },
        };
        return updatedItem;
      }

      return item;
    },
  };
};```

And then use this hook `useGrafanaFaro(SERVICE_NAME, uniqueKeyForGrafana);` in each microfrontend applications. 
Question: Is this possible to somehow change `traceId`  which grafana sends in each payload to collect url?

I tried almost everything. This solution also does not work. 

  ```const tracer = trace.getTracer('MyApp-Tracer');

  const createNewTrace = () => {
    const span = tracer.startSpan('New Trace Span', { root: true });

    context.with(trace.setSpan(context.active(), span), () => {
      let z = trace.getActiveSpan()?.spanContext()?.traceId;
      z = traceId;
      span.end();
    });
  };```

Any ideas? 
codecapitano commented 4 weeks ago

The "@grafana/faro-web-tracing": "^1.7.3", uses otel under the hood and creates otel traces.

Where does the new traceID which you want add to add come from?

What is your goal with that approach? Like when do you want to change the traceId or start a new trace?

What may work (untested) is the following: Since you use the web-tracing package you have otel available

  1. Acquire a reference to the OTEL api
const otel = faro.api.getOTEL();
  1. Create a new span context

    const newCtx = otel?.trace.setSpanContext(otel.context.active(), {
    traceId: '123',
    spanId: '456',
    traceFlags: 1 /* or 0 depending if you use sampling or not  */,
    });
  2. Set your new span context as the new context

otel?.context.with(newCtx, () => {
    // ..
  });

Maybe this helps?

DuudeXX8 commented 4 weeks ago
  1. Where does the new traceID which you want add to add come from? traceId come from each microfrontend app which use this hook. useGrafanaFaro(SERVICE_NAME, grafanaKey);. This grafanaKey is the traceId which I need to put inside of payload. I run this hook in the main parent component.

  2. When do you want to change the traceId or start a new trace? When user will be in a different route or user information will change I run this code for update my grafanaKey. In component, I run this code below.

useEffect(() => {
    setGrafanaKey(uuidv4().replace(/-/g, ''));
}, [customer?.data?.mobilePhone]);

This traceId come to my custom hook as argument

export const useGrafanaFaro = (
  serviceName: string,
  traceId?: string,
  options: IFaroOptionalParams<typeof useHistory> = {
    version: DEFAULT_VERSION,
    collectorURL: DEFAULT_COLLECTOR_URL,
  },
) => {

And I put in to my faro config

const faroConfig = {
// other configuration
beforeSend: (item: TransportItem<APIEvent>): TransportItem<APIEvent> => {
  const getTraceId = (
    traceIdFromMap: string | Uint8Array
  ): string | Uint8Array => {
    if (traceId) { // if trace id exists use it
      return traceId as string;
    }

    return traceIdFromMap; // if it's not, use default traceId
  };

  if (item?.type === 'trace' && 'resourceSpans' in item.payload) {
    updatedItem = {
      ...item,
      payload: {
        ...item.payload,
        resourceSpans: item.payload.resourceSpans.map((resourceSpan) => ({
          ...resourceSpan,
          scopeSpans: resourceSpan.scopeSpans.map((scopeSpan) => ({
            ...scopeSpan,
            spans: scopeSpan.spans.map((span) => ({
              ...span,
              traceId: getTraceId(span.traceId), // Here my 
            })),
          })),
        })),
      },
    };
    return updatedItem;
  }
  return item;
},

And in useEffect inside of useGrafanaFaro hook, I initialize my faro.

export const useGrafanaFaro = (
  serviceName: string,
  traceId?: string,
  options: IFaroOptionalParams<typeof useHistory> = {
    version: DEFAULT_VERSION,
    collectorURL: DEFAULT_COLLECTOR_URL,
  },
) => {

const faroConfig = {
 // initial configuration 
}
useEffect(
/** Initialize Grafana Faro with the specified service name and options. */
() => {
  initializeFaro(faroConfig);
},
[serviceName, options, traceId, faroConfig],
);

Here's full map how it works in application.

Also tried code which you provide. But every time in console I didn't see any changes. Same (first) traceId was sent to collect url in payload. It does not change.

  const otel = faro?.api?.getOTEL();

  useEffect(
  /** Initialize Grafana Faro with the specified service name and options. */
  () => {
    const newCtx = otel?.trace.setSpanContext(otel.context.active(), {
      traceId,
      spanId: '123',
      traceFlags: 0,
    });

    otel?.context.with(newCtx, () => {});

    initializeFaro(faroConfig);
  },
  [serviceName, options, traceId, faroConfig],
  );

Please help 😫