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

Client side instrumentation with a parent trace that groups child spans #3237

Closed
1 of 2 tasks
drazenbuljovcic opened this issue Sep 7, 2022 · 14 comments
Closed
1 of 2 tasks
Labels

Comments

@drazenbuljovcic
Copy link

  • This only affects the JavaScript OpenTelemetry library
  • This may affect other libraries, but I would like to get opinions here first

Hey, my question is related to enabling the default client side packages to be instrumented with an existing parent trace.
This way we could achieve a waterfall in which we open with the event that could be generated by @opentelemetry/instrumentation-document-load and all other events that are generated by @opentelemetry/instrumentation-xml-http-request and @opentelemetry/instrumentation-fetch can be grouped under it - this way we can assemble the entire user journey and get the visualization of the entire flow from session start until session end.

image
Taken from Front-end-Observability-Whitepaper by Honeycomb

For now I see these packages exist independently, and looking into the code of each I do not see an out-of-the-box way of achieving this.
I'm wondering what would it take to enable this kind of behaviour by default - or could we add configurations on spans generated by the xml-http and fetch instrumentations to make them child spans at runtime?
Or, of course, do you have advice on how to achieve this manually for now writing some custom logic?

Thank you!

@AkselAllas
Copy link
Contributor

AkselAllas commented Sep 26, 2022

The picture from Honeycomb's whitepaper is how I hoped/imagined that autoinstrumented opentelemetry-js also works.

After implementing it I was disappointed to see it doesn't 😞

@Flarna
Copy link
Member

Flarna commented Sep 26, 2022

Not sure if this behavior should be the default. Note that spans are only exported once they are ended, at least with most exporters (e.g. OTLP, Jaeager, zipkin). So you will get a lot spans with the same traceId but the root span is potentially missing for a long time - if it arrives at all (export on unload may fail).

I think you could reach something like this by creating a span manually and set is as active for all code running on your page. But as I'm not an browers expert I don't know how the zone.js context manager usually used on Web works and if this is easily possible.

Maybe OTel RUM is intended to solve the actual use case?

@AkselAllas
Copy link
Contributor

So I'm trying to do manual frontend browser instrumentation for React to get nested fetch request spans.

Does anyone have thoughts on why it's not working 🤔

I'm trying to achieve something like the Honeycomb example pointed out by OP

I tried the suggested blog approach, but it doesn't work for React 🤔

window.onload = (event) => {
 // ... do loading things
 root_span.end(); //once page is loaded, end the span
};

I am starting a span currently on page load in entrypoint App.tsx and I would like to see all fetched 3rd party requests via fetchInstrumentation.

I've tried adding the following to the last component in my page

  useEffect(() => {
    fetch('http://localhost:8080/actuator/health/readiness')
    root_span.end()
  }, [])

I've also tried the same with useLayoutEffect() , but no luck in getting nested spans.
My instrumentation code is as follows:

export const observability = () => {
  const exporter = new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  })
  const provider = new WebTracerProvider()
  provider.addSpanProcessor(new BatchSpanProcessor(exporter))
  provider.register({
    contextManager: new ZoneContextManager(),
  })
  registerInstrumentations({
    tracerProvider: provider,
    instrumentations: [
      new FetchInstrumentation({
        propagateTraceHeaderCorsUrls: ['http://localhost:8080/'],
      }),
    ],
  })

  const tracer = trace.getTracer('AkselTest')
  const root_span = tracer.startSpan('document_load')
  root_span.setAttribute('pageUrlwindow', window.location.href)
  return root_span
}

@Flarna
Copy link
Member

Flarna commented Sep 26, 2022

What is missing in your code is the enable() call of ZoneContextManager so try contextManager: new ZoneContextManager().enable(),.

As said I'm not familiar with web and which context is use where so don't know if this solves your issue.

But I know how to cheat :o) - here is an implementation of a high performance context manager allowing you to specify the one and only active context:

import { trace, ContextManager, Context, ROOT_CONTEXT } from "@opentelemetry/api";
import { BasicTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";

class AlwaysSameContextManager implements ContextManager {
  static theContext = ROOT_CONTEXT;
  active(): Context { return AlwaysSameContextManager.theContext; }
  with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(_context: Context, fn: F, thisArg?: ThisParameterType<F>, ...args: A): ReturnType<F> { return Reflect.apply(fn, thisArg, args); }
  bind<T>(_context: Context, target: T): T { return target; }
  enable(): this { return this; }
  disable(): this { return this; }
}

const tp = new BasicTracerProvider();
tp.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
tp.register({
    contextManager: new AlwaysSameContextManager().enable()
});

const tracer = trace.getTracer("RootTracer");
const span = tracer.startSpan("RootSpan");
AlwaysSameContextManager.theContext = trace.setSpan(ROOT_CONTEXT, span);

tracer.startSpan("a").end();              // child of RootSpan
tracer.startSpan("b").end();              // child of RootSpan
tracer.startActiveSpan("c", (span) => {   // child of RootSpan
  tracer.startSpan("d").end();            // even this is a child of RootSpan!
  span.end();
});

... not intended for production but you may get what you want. It should be not that difficult to extend it a bit to ensure that propagators inject the right span instead RootSpan.

@dyladan
Copy link
Member

dyladan commented Sep 26, 2022

The always same context manager shown would mean every span is always disconnected because there is never a parent. I think you would instead want a context manager where you can specify an alternative root context but otherwise uses zone normally.

@Flarna
Copy link
Member

Flarna commented Sep 26, 2022

@dyladan see AlwaysSameContextManager.theContext = trace.setSpan(ROOT_CONTEXT, span); to set the one and only parent.

But as written this includes propagators so a bit more is needed.

@AkselAllas
Copy link
Contributor

Hi @Flarna

I'm looking into how to get this working with propagators.

Can you perhaps give any guidance on what I need to accomplish on a high level?

Cheers!

@Flarna
Copy link
Member

Flarna commented Oct 17, 2022

@AkselAllas Sorry but I don't understand your question. What else is missing besides the hacky AlwaysSameContextManager I sketched above to move forward?

@github-actions
Copy link

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 github-actions bot added the stale label Dec 19, 2022
@AkselAllas
Copy link
Contributor

AkselAllas commented Dec 31, 2022

Using AlwaysOnContextManager leads to some extremely long and complex traces.

Is there any span/trace viewer that allows filtering spans of a trace view using timestamp?

@github-actions github-actions bot removed the stale label Jan 2, 2023
@github-actions
Copy link

github-actions bot commented Mar 6, 2023

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 github-actions bot added the stale label Mar 6, 2023
@danopia
Copy link

danopia commented Mar 18, 2023

Given that single-page apps can be open and continuously interacting with server[s] for many hours, the huge traces would presumably cause UI issues in nearly any trace viewer tool.. I've definitely seen Datadog get squirrely when viewing a trace with ~30,0000 spans.

Perhaps a better abstraction is giving a randomized resource attribute to the browser SDK?

const provider = new WebTracerProvider({
  resource: new Resource({
    'service.name': 'my-single-page-app',
    'session.id': crypto.randomUUID(), // correlate all traces from this pageload
  }),
});

I don't see an attribute like this in the Resource Semantic Conventions, but I'm already using this strategy and find that trace viewers handle filtering quite well. Maybe the OpenTelemetry RUM effort will standardize a similar attribute.

@github-actions github-actions bot removed the stale label Mar 20, 2023
@github-actions
Copy link

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 github-actions bot added the stale label Jun 19, 2023
@github-actions
Copy link

github-actions bot commented Jul 3, 2023

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

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jul 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants