opentelemetry-js-contrib icon indicating copy to clipboard operation
opentelemetry-js-contrib copied to clipboard

UserInteractionInstrumentation create too many traces pr user interaction

Open martitv opened this issue 2 years ago • 22 comments
trafficstars

What version of OpenTelemetry are you using? 1.4

What version of Node are you using? v18.12.1

What did you do?

Created a plain React app using Vite. Registered a UserInteractionIntrumentation. Clicked anywhere on the page.

What did you expect to see?

One trace (print to the console)

What did you see instead?

4 traces

Additional context

main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";

/* document-load.ts|js file - the code snippet is the same for both the languages */
import {
  ConsoleSpanExporter,
  SimpleSpanProcessor,
  WebTracerProvider,
} from "@opentelemetry/sdk-trace-web";
import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load";
import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-user-interaction";
import { ZoneContextManager } from "@opentelemetry/context-zone";
import { registerInstrumentations } from "@opentelemetry/instrumentation";

const provider = new WebTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));

provider.register({
  // Changing default contextManager to use ZoneContextManager - supports asynchronous operations - optional
  contextManager: new ZoneContextManager(),
});

// Registering instrumentations
registerInstrumentations({
  instrumentations: [
    new DocumentLoadInstrumentation(),
    new UserInteractionInstrumentation(),
  ],
});

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

App.tsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </div>
  )
}

export default App

Note: We also tried the same tracing in a real production application. Instead of "only" 4 traces, we got around 50-80 traces pr user click which is insane.

martitv avatar Jan 31 '23 11:01 martitv

@martitv We were talking about this in the SIG meeting: could you please post a screenshot of the traces? This issue is likely something that would improve with the upcoming Events API/SDK, but it will take quite a bit of time for this to come along.

@obecny Do you maybe have some insights on this?

pichlermarc avatar Feb 01 '23 17:02 pichlermarc

Experiencing the same thing. In a starter React project with Vite every interaction is logged 4 times.

Screenshot: image

birk-bre avatar Feb 03 '23 08:02 birk-bre

The screenshot @birk-bre posted is basically the same as what I get. Its just that one event manifests into 4 for some reason. Our first theory was that it had something to do with StrictMode and React's synthetic events. But after turning off StrictMode it still produced 4 traces in the console after a single click

martitv avatar Feb 08 '23 08:02 martitv

as far as I remember it was raised in past, the way react handles the events was the problem, I cannot recall all the details, but the outcome was not to use it with react as it produces extra events. But as I said I don't remember all the details now

obecny avatar Feb 09 '23 16:02 obecny

Ouch, so Open Telemetry and React doesn't work together? Nothing one can do to remedy this? Could it be something about the Contexts and how they work in React?

martitv avatar Feb 13 '23 08:02 martitv

React isn't supported at all? What are the challenges around it?

oldclesleycode avatar Feb 15 '23 12:02 oldclesleycode

Hello there,

I'm facing the same issue in our client React application. Is there any update?

Thanks!

fmigot avatar Mar 09 '23 17:03 fmigot

No updates; sorry this is taking so long. :slightly_frowning_face:

We currently need more contributors familiar with this instrumentation - I'm also not very familiar with it (I need to get familiar with react, react internals, and this instrumentation), and higher-priority items keep popping up that prevent me from working on this.

If anyone has more expertise/time to spend on the topic, I'll gladly hand this issue over so that it can get the attention it deserves. I am unassigning myself to indicate that this is up for grabs, but I'll pick it up again when I have enough time.

pichlermarc avatar Mar 10 '23 10:03 pichlermarc

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

github-actions[bot] avatar May 15 '23 06:05 github-actions[bot]

I encountered similar issue in an Angular application. It is apparent in my case that zone.js is triggering callbacks for DOM event (click in my case) multiple times.

const globalZoneAwareCallback = function (event) {
        return globalCallback(this, event, false);
};

running through each currentTarget whereas since instrumentation api only captures target, it appears that all the spans are same.

Since I do not have access to event object in shouldPreventSpanCreation, is there a good way to filter out unwanted callbacks?

prakharjaiswal avatar Jun 21 '23 15:06 prakharjaiswal

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

github-actions[bot] avatar Aug 21 '23 06:08 github-actions[bot]

This issue was closed because it has been stale for 14 days with no activity.

github-actions[bot] avatar Sep 04 '23 06:09 github-actions[bot]

Any updates?

ido-g-coralogix avatar Sep 05 '23 11:09 ido-g-coralogix

@ido-g-coralogix no updates, sorry. This issue is up for grabs.

We're also accepting component owners to https://github.com/open-telemetry/opentelemetry-js-contrib/blob/de221b087773c74d518443e4bc6277fffd3712a4/.github/component_owners.yml#L119 in case someone sees themselves up to the task to maintain @opentelemetry/instrumentation-user-interaction long-term.

pichlermarc avatar Sep 05 '23 14:09 pichlermarc

I have the same issue on react-app, React version is 18.2.0.

lucasdengPru avatar Dec 21 '23 03:12 lucasdengPru

The issue remains

I have an idea (workaround)

Set target attribute when pressed and delete this when released

It's inconvenient, but it should work out. @obecny

cangSDARM avatar Jan 24 '24 09:01 cangSDARM

Marking this as up-for-grabs to make it explicit that PRs for this are welcome.

Note: the owner of this package left the project a long while ago so we're also looking for people to maintain it long-term. Due to this reviews may take longer than usual.

pichlermarc avatar Mar 06 '24 16:03 pichlermarc

const allowedElements = new Set<string>(["A", "BUTTON"]);
let previousTargetedElementXPath = "";
// Registering instrumentations
registerInstrumentations({
  instrumentations: [
    new DocumentLoadInstrumentation(),
    new UserInteractionInstrumentation({
      eventNames: ["submit", "click", "keypress"],
      shouldPreventSpanCreation: (_, element, span) => {
        // @ts-ignore
        const targetElement = span.attributes.target_element;
        if (targetElement && !allowedElements.has(targetElement)) return true;

        // @ts-ignore
        const targetElementXPath = span.attributes.target_xpath;
        if (previousTargetedElementXPath == targetElementXPath) return true;
        else previousTargetedElementXPath = targetElementXPath;

        span.setAttribute("target.id", element.id);
        span.setAttribute("target.text", element.textContent || "");

        return false;
      },
    }),
  ],
});

This code will prevent repeat span.

  • We check if the target_element attribute exists on the span and whether it is included in the allowedElements set. If not, we return true to prevent the span from being created.
  • We check if the XPath of the current targeted element (targetElementXPath) is the same as the XPath of the previously targeted element (previousTargetedElementXPath). If they are the same, it indicates a repeat span, and we return true to prevent duplication. Otherwise, we update previousTargetedElementXPath with the current value.
  • Finally, we set attributes on the span to store information about the targeted element, such as its ID and text content.

vimalraj3 avatar Mar 17 '24 12:03 vimalraj3

Nice. Sometimes we get different xpath for same element. So i implemented this way..

const allowedElements = new Set<string>(["A", "BUTTON"]);
let previousElement = "";
// Registering instrumentations
registerInstrumentations({
  instrumentations: [
    new DocumentLoadInstrumentation(),
    new UserInteractionInstrumentation({  
      eventNames: ["submit", "click", "keypress"],
      shouldPreventSpanCreation: (_, element, span) => {
        // @ts-ignore
        const targetElement = span.attributes.target_element;
        if (targetElement && !allowedElements.has(targetElement)) return true;
        // @ts-ignore
        if (previousElement.isEqualNode(element)) {
        return true;
        }
        previousElement = element;
        span.setAttribute("target.id", element.id);
        span.setAttribute("target.text", element.textContent || "");
        return false;
      },
    }),
  ],
});

I stored element in previous element and used isEqualNode function to compare. It has full browser support.

p4prawin avatar Mar 27 '24 12:03 p4prawin

Sometimes, a user can click the same element continuously n times. We would lose those records, and we need these continuous clicks to have a better understanding of user behavior.

I think we can rewrite the logic with timestamps.

vimalraj3 avatar Mar 27 '24 16:03 vimalraj3

Here is an example project with pure HTML+JS (no React) that exhibits the issue: https://github.com/honeycombio/academy-instrumentation-python. I see three click spans at Honeycomb for a single press of the button.

clintonb avatar Aug 24 '24 18:08 clintonb