-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Allow instrumentation of client-side fetch
via a handleFetch
hook
#9530
Comments
To clarify, would // +page.js
export function load({ fetch }) {
fetch(..)
} ...or also for these // +page.js
export function load() {
fetch(..); // this is the global fetch
} <script>
function fetchSomething() {
fetch(..); // this is the global fetch
}
</script> ? |
For our use case it's enough that it runs for SvelteKit's If you think that it should also run for the global |
In addition to my +1 to this feature suggestion, I'd like to share my use case forwarding headers to My application uses header auth, and I want to forward auth headers into every I currently retrieve auth headers from export async function load ({ fetch, locals }) {
const headers = new Headers({
accept: 'application/json'
})
if (locals.user.name) {
headers.set('x-saml-displayname', locals.user.name)
}
if (locals.user.email) {
headers.set('x-saml-email', locals.user.email)
}
if (locals.user.username) {
headers.set('x-saml-username', locals.user.username)
}
const res = await fetch('/api/path', {
headers
})
} It would be nice to easily forward headers onto the server-side Thanks for your consideration, please let me know if this needs its own issue. |
We were just notified that our current
I believe this would yet be another good reason to expose a clean way to intercept and add headers to fetch requests on the client side. I imagine we can hardly be the only ones who'd like to automatically add headers to all outgoing requests and this could mess up caching in more use cases than ours. The alternative solution for us would be to preemptively create these script selectors in our patched fetch instrumentation and check if there would be a cache hit (basically, recreate a good part of
I'm definitely open for other ideas how to get around the cache misses here but IMHO this needs to be solved in the framework in the long-run. Also, I'm more than happy to remove our current This reproduction example shows the current problem and how/when cache-misses occur |
For what it's worth, this is how we're currently instrumenting SvelteKit's fetch using OTEL: In const _fetch = window.fetch;
window.fetchProxy = (...args) => _fetch(...args);
window.fetch = (...args) => window.fetchProxy(...args); Some utils for cleanly instrumenting the proxy: // Wraps fetch with the provided handler
export const handleFetch = (fetchHandler: (input: { fetch: typeof fetch, args: Parameters<typeof fetch> }) => Promise<Response>): void => {
instrumentGlobalFetch(() => { // wrapping simply abstracts away our proxy
const currProxy = window.fetch;
window.fetch = (...args) => fetchHandler({ fetch: currProxy, args });
});
};
// Runs instrumentation that operates on the global fetch
export const instrumentGlobalFetch = (instrument: () => void): void => {
const currFetch = window.fetch;
window.fetch = window.fetchProxy; // Put our proxy in place to be instrumented
try {
instrument();
window.fetchProxy = window.fetch; // Put our (now) instrumented proxy back into place
} finally {
window.fetch = currFetch;
}
} Which allows us to make our own handleFetch(async ({ fetch, args }) => {
const response = await traceFetch(() => fetch(...args));
if (response.status === 401) {
throw redirect(307, '/logout');
}
return response;
}); And OTEL can do it's auto-instrumentation too: instrumentGlobalFetch(() => {
registerInstrumentations({
instrumentations: [getWebAutoInstrumentations()],
});
}); |
@myieye this is pretty neat! I just played around with your idea and I think it could work for Sentry. Thanks a lot for sharing 🙏 |
…fault instrumentation (#8802) Remove our custom SvelteKit client fetch instrumentation which we created when we initially worked on the SDK. Back then I didn't think that it's possible to use our default fetch instrumentation from `BrowserTracing`, due to timing issues where Kit would store away `window.fetch` (and use the stored version in `load` functions) before our SDK was initialized. After receiving some [hints](sveltejs/kit#9530 (comment)) how it might be possible, we now have a way to instrument `fetch` everywhere on the client (including the one in `load`) functions. This works in two parts: 1. During the initial page load request, our server-side `handle` wrapper injects a script into the returned HTML that wraps `window.fetch` and adds a proxy handler (`window._sentryFetchProxy`) that at this time just forwards the fetch call to the original fetch. After this script is evaluated by the browser, at some point, SvelteKit loads its initial client-side bundle that stores away `window.fetch`. Kit also patches `window.fetch` itself at this time. Sometime later, the code from the `hooks.client.js` file is evaluated in the browser, including our `Sentry.init` call: 2. During `Sentry.init` we now swap `window.fetch` with `window._sentryFetchProxy` which will make our `BrowserTracing` integration patch our proxy with our default fetch instrumentation. After the init, we swap the two fetches back and we're done.
FWIW that solution requires you to add <script type="text/javascript" src="https://cdn.bitskistatic.com/js/telemetry-fetch.js"></script> I really agree this feature is very important for generic instrumentation. Currently we have a lot of these same issues, and the hacks around it are really quite annoying. @Lms24 I see your potential solution to this problem, we may switch to something like that, but ideally we would just add the |
@pzuraq yup, CSP is a problem (see getsentry/sentry-javascript#8925). I suggested a workaround to the solution for Sentry users but still waiting on feedback to implement. The more I think about it, the more I come to the conclusion that the cleanest way forward to permit fetch instrumentation would just be my alternative suggestion no. 2 (i.e. make Kit not store away the fetch version it uses in its loaders). I took a stab at this in #10009 but seems like there hasn't been much movement lately. To be clear, this doesn't provide the same functionality as a dedicated |
Yeah I agree, that solution would be the most "be like the platform" one, and also would make sure that client-side load can work with any arbitrary tooling that happens to be out there that patches |
The
import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch = (async ({ request, fetch }) => {
...
return fetch(request);
}) satisfies HandleFetch; If it is server, it calls J |
sveltekit needs client side handlefetch which is interceptor in axios. Svelte makes our life easier, but not this time. It's very simple with axios interceptors. We should have some control over client fetchs including page loads. |
This would be a really nice feature - on frontend only we can use the proxy of nginx or caddy or whatever the website is hosted with. |
Describe the problem
TL;DR: We (Sentry) want to instrument client-side
fetch
requests and we currently cannot instrument some of them. We propose ahandleFetch
hook on the client side as a solution.Hi, I'm an SDK engineer at Sentry and we're currently working on a dedicated SvelteKit SDK. Things are progressing nicely but we just discovered a potential problem:
Based on my findings, SvelteKit stores
window.fetch
in thenative_fetch
variable, monkey patches it and only uses this one for client-side fetch requests:kit/packages/kit/src/runtime/client/fetcher.js
Line 6 in 5191e16
This is problematic for our use case because it means that our SDK, which we'll most probably instruct users to initialize in the client and server hooks files comes in too late to apply our fetch instrumentation to
window.fetch
.We already wrap
load
functions to create spans for our performance monitoring product. So we have a way to instrument thefetch
function that is passed to the client-side universal load function in theLoadEvent
.However, for a page that has a server-only
load
function, Kit makes aroute/path/__data.json
fetch request to invoke this server-only load function. Up to this point, we haven't found a way to instrument thisfetch
call. We would really like to instrument the call though, so that we can attach our Http headers for distributed tracing (see Additinal Information below). Without this, we cannot connect the client side with the server side and our users don't get the full picture what happened during a client-side navigation.Please find our proposed solution(s) below. We're more than happy to open a PR or to work with you in any way to help finding a good solution here. Also, if it happens that we missed something, please let us know :)
Describe the proposed solution
To solve this, we propose to add support for a
handleFetch
hook in thehooks.client.js
file. This hook would be invoked every time afetch
call is made on the client side, be it by a user callingfetch
inside aload
function as well as by the framework, when it makes afetch
call to invoke a server-onlyload
function.We believe this would be the cleanest approach to open up Kit's
fetch
to SDKs like ours. It would also make it easier for Kit users to e.g. attach custom Http headers or other data to outgoingfetch
requests.Since I've already spent some time reading Kit source code and I already played around with hooks, I'm more than happy to open a PR for this change :)
Alternatives considered
We have considered a few alternatives that we believe would also work but are potentially less clean or might require more SvelteKit-internal changes:
native_fetch
(or betterinitial_fetch
andsubsequent_fetch
) patchable for SDKs in another way than via a client-sidehandleFetch
hook. We're definitely open to other suggestions but right now, everything we hook into in SvelteKit would reside in the hooks files which means great DX for our users as hooks seem to be the most SvelteKit-native way.window.fetch
directly without storing it away in variables. This way, our instrumentation can wrap the correctfetch
without any additional code on our end. Given how Kit currently handles fetch, I believe this is unlikely to happen but it would nevertheless be an option.fetch
calls during runtime, we'll need to inject some code at build-time, probably via a Vite plugin to be able to accessnative_fetch
to instrument it. We've done this for other framework SDKs before but would like to avoid it if at all possible. It's a rather brittle solution and has caused a lot of bugs in said framework SDKs in the past. That being said, we'll go down this route if there's no other way.Importance
would make my life easier; very important for SvelteKit Sentry users
Additional Information
A little more background on why we need to instrument
fetch
:sentry-trace
as well as the W3C standardizedbaggage
headers) to outgoing fetch (and XHR) requests. With the help of these headers we can connect transactions and spans in our performance monitoring product, giving our users the full picture of what happened on the client and on the server (+ additional backend services).fetch
requests to give our users clues what happened e.g. before an error was reported to Sentry.Thank you for considering this issue!
The text was updated successfully, but these errors were encountered: