diff --git a/frontend/src/lib/gql/gql-client.ts b/frontend/src/lib/gql/gql-client.ts index 402356732..26f10cf2e 100644 --- a/frontend/src/lib/gql/gql-client.ts +++ b/frontend/src/lib/gql/gql-client.ts @@ -1,5 +1,5 @@ import {browser} from '$app/environment'; -import {tracingExchange} from '$lib/otel'; +import {tracingExchange, tryCopyTraceContext} from '$lib/otel'; import type {LexAuthUser} from '$lib/user'; import {isRedirect} from '@sveltejs/kit'; import {devtoolsExchange} from '@urql/devtools'; @@ -7,6 +7,7 @@ import {cacheExchange} from '@urql/exchange-graphcache'; import { type AnyVariables, type Client, + type CombinedError, createClient, fetchExchange, type OperationContext, @@ -280,15 +281,8 @@ class GqlClient { private throwAnyUnexpectedErrors>(result: T, delayThrow: boolean = false): void { if (!result.error) return; - const error = - // Various status codes are handled in the fetch hooks (see hooks.shared.ts). - // throws there (e.g. SvelteKit redirects and 500's) turn into networkErrors that land here - result.error?.networkError ?? - // These are errors from urql. urql doesn't throw errors, it just sticks them on the result. - // An error's stacktrace points to where it was instantiated (i.e. in urql), - // but it's far more interesting (particularly when debugging how errors affect our app) to know when and where errors are getting thrown, namely HERE. - // So, we new up our own error to get the more useful stacktrace. - new AggregateError(result.error.graphQLErrors, result.error.message ?? result.error.cause); + + const error = this.squashErrors(result.error); if (delayThrow && !isRedirect(error)) { // SvelteKit handles Redirects, so we don't want to delay them // We can't throw errors here, because errors thrown in wonka/an exchange kill the frontend. @@ -298,6 +292,20 @@ class GqlClient { } } + private squashErrors(error: CombinedError): Error { + const squashedError = + // Various status codes are handled in the fetch hooks (see hooks.shared.ts). + // throws there (e.g. SvelteKit redirects and 500's) turn into networkErrors that land here + error.networkError ?? + // These are errors from urql. urql doesn't throw errors, it just sticks them on the result. + // An error's stacktrace points to where it was instantiated (i.e. in urql), + // but it's far more interesting (particularly when debugging how errors affect our app) to know when and where errors are getting thrown, namely HERE. + // So, we new up our own error to get the more useful stacktrace. + new AggregateError(error.graphQLErrors, error.message ?? error.cause); + tryCopyTraceContext(error, squashedError); + return squashedError; + } + private findInputErrors({data}: OperationResult): LexGqlError> | undefined { const errors: GqlInputError>[] = []; if (isObject(data)) { diff --git a/frontend/src/lib/otel/types.ts b/frontend/src/lib/otel/types.ts index 93061f657..53c8bc65f 100644 --- a/frontend/src/lib/otel/types.ts +++ b/frontend/src/lib/otel/types.ts @@ -44,3 +44,9 @@ export function traceIt(traceable: Traceable, spanContext: SpanContext, tracer: throw new TraceItError(traceable, `spanContext not writeable.`); } } + +export function tryCopyTraceContext(from: unknown, to: unknown): void { + if (!isTraced(from)) return; + if (!isTraceable(to)) return; + traceIt(to, from.spanContext, from.tracer); +}