-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Describe the problem
We are currently trying to setup up client-side instrumentation for our application using OpenTelemetry. For context, Otel generalizes a notion of a span (think like the callstack span you might see in the JS debugger) and allows you to create custom spans that can contain other spans across services. So in the browser, for instance, it is common to wrap fetch
in a function that creates a span and passes it along to the server, so you can see all the different things that fetch
request triggered and handled.
Instrumenting fetch
on the client is it's own can of worms that's being discussed in #9530 and #10009, and those would help a lot with allowing apps to do this kind of instrumentation. However, there is another issue we've been running into, specifically around the load
hooks of pages and the preloading feature.
Ideally, we would create a span to wrap each load hook that is called. This way we can see the collection of load requests that went into every single page's render, and also gather metrics such as duration. We started building a solution to this based on onNavigate
(which is not ideal because it's tied to a component lifecycle rather than being global, but that's a separate issue), but we found that our spans created in onNavigate
were never being associated with the fetch
requests that the load function was making. After some head scratching, we realized this was due to the preloading functionality SvelteKit exposes.
When the user hovers over a link, SvelteKit starts preloading the data for the route it is going to by default. This is great behavior for optimizing UX out of the box, but currently there doesn't seem to be a way to instrument this behavior and group it together logically into a single notion of a "page load". This means we can't accurately track the impact it has on users, whether or not prefetching is erroring, and what the overall performance of these preload calls is.
We considered disabling preloading altogether, but there doesn't appear to be a way to do this globally in an app at the moment. Moreover, the issue is not with the functionality itself, but with our inability to observe it.
Describe the proposed solution
I think this could be handled a few different ways:
- Add
handle
to client side hooks, which would run for all client sideload
calls. This feels like it would be ideal from an API perspective if it would work, because then you are just conceptually mirroring the server functionality, but I could see this being tricky for a number of reasons on the client (different signatures, different notion of what an "event" is, etc). - Add client-side
handleLoad
and/orhandlePreload
hooks, or alternativelyhandleNavigate
(though, likeonNavigate
, that would imply it only runs on actual user navigation). This would be a more targeted version of adding hooks, which means the API is less generalized and not as much of a commitment. - Add an
onPreload
function to thenavigation
module, which allows us to hook into the preload event much like a navigation event. This seems less ideal to me because preloading doesn't really seem to relate to a component as much, but it would solve the issue for us.
Alternatives considered
We've considered wrapping every single load hook with an instrumentLoad
helper function, but this has a number of issues:
- It makes type annotations a pain to write overall, a lot more boilerplate per module
- Mistakes were very common, devs would forget to add the instrumentation all the time
- It runs on the server as well, where we don't have this issue because we can wrap all events in a request in a span using the
handle
hook
Importance
i cannot use SvelteKit without it
Additional Information
Long term we really need this functionality, I'm exploring ways I could have our build-system add instrumentLoad
helpers to each page load via a Vite plugin but ideally this would be pluggable within the framework.
Also, happy to implement this if it is accepted.