Skip to content

Commit

Permalink
Merge pull request #98 from jill64/issues-97
Browse files Browse the repository at this point in the history
minor: support @sentry/sveltekit routing instrumentation
  • Loading branch information
ghost-merge[bot] committed Oct 22, 2023
2 parents 6ab9e00 + 261b748 commit a65c2c9
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 31 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jill64/sentry-sveltekit-cloudflare",
"version": "0.4.32",
"version": "0.5.0",
"description": "Unofficial Sentry Integration for SvelteKit Cloudflare Adapter",
"type": "module",
"main": "dist/index.js",
Expand Down Expand Up @@ -46,19 +46,20 @@
},
"devDependencies": {
"@playwright/test": "1.39.0",
"@sentry/types": "^7.74.1",
"@typescript-eslint/eslint-plugin": "6.8.0",
"@typescript-eslint/parser": "6.8.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-svelte": "2.34.0",
"prettier-plugin-svelte": "3.0.3",
"typescript": "5.2.2"
},
"dependencies": {
"typescript": "5.2.2",
"@sentry/core": "7.74.1",
"@sentry/svelte": "7.74.1",
"@sentry/utils": "7.74.1",
"@sveltejs/kit": "1.26.0",
"esm-env": "1.0.0",
"toucan-js": "3.3.0"
},
"dependencies": {
"esm-env": "1.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/lib/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ build({
outfile: '../../dist/index.js',
minify: true,
format: 'esm',
external: ['esm-env']
external: ['esm-env', '$app/stores']
})
24 changes: 24 additions & 0 deletions packages/lib/src/client/addClientIntegrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { hasTracingEnabled } from '@sentry/core'
import { BrowserOptions, BrowserTracing } from '@sentry/svelte'
import { addOrUpdateIntegration } from '@sentry/utils'
import { svelteKitRoutingInstrumentation } from './router.js'

export const addClientIntegrations = (options: BrowserOptions) => {
let integrations = options.integrations || []

if (hasTracingEnabled(options)) {
const defaultBrowserTracingIntegration = new BrowserTracing({
routingInstrumentation: svelteKitRoutingInstrumentation
})

integrations = addOrUpdateIntegration(
defaultBrowserTracingIntegration,
integrations,
{
'options.routingInstrumentation': svelteKitRoutingInstrumentation
}
)
}

options.integrations = integrations
}
16 changes: 16 additions & 0 deletions packages/lib/src/client/applySdkMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SDK_VERSION } from '@sentry/core'
import { BrowserOptions } from '@sentry/svelte'

const PACKAGE_NAME_PREFIX = 'npm:@sentry/'

export const applySdkMetadata = (options: BrowserOptions, names: string[]) => {
options._metadata = options._metadata || {}
options._metadata.sdk = options._metadata.sdk || {
name: 'sentry.javascript.sveltekit',
packages: names.map((name) => ({
name: `${PACKAGE_NAME_PREFIX}${name}`,
version: SDK_VERSION
})),
version: SDK_VERSION
}
}
5 changes: 3 additions & 2 deletions packages/lib/src/client/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { addExceptionMechanism } from '@sentry/utils'
import type { HandleClientError } from '@sveltejs/kit'
import { DEV } from 'esm-env'
import { defaultErrorHandler } from './defaultErrorHandler'
import { sentryInit } from './sentryInit'

export const init = (
dsn: string,
Expand All @@ -18,12 +19,12 @@ export const init = (
return (handleError = defaultErrorHandler) => handleError
}

Sentry.init({
sentryInit({
dsn,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
integrations: [new Sentry.Replay()],
...sentryOptions
})

Expand Down
11 changes: 11 additions & 0 deletions packages/lib/src/client/restoreFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { WINDOW } from '@sentry/svelte'

export const restoreFetch = (actualFetch: typeof fetch) => {
const globalWithSentryFetchProxy = WINDOW

// @ts-expect-error TODO: fix this
globalWithSentryFetchProxy._sentryFetchProxy =
globalWithSentryFetchProxy.fetch

globalWithSentryFetchProxy.fetch = actualFetch
}
123 changes: 123 additions & 0 deletions packages/lib/src/client/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { navigating, page } from '$app/stores'
import { getActiveTransaction } from '@sentry/core'
import { WINDOW } from '@sentry/svelte'
import type { Transaction, TransactionContext } from '@sentry/types'
import type { Span } from '@sentry/types'

const DEFAULT_TAGS = {
'routing.instrumentation': '@sentry/sveltekit'
}

export const svelteKitRoutingInstrumentation = <T extends Transaction>(
startTransactionFn: (context: TransactionContext) => T | undefined,
startTransactionOnPageLoad?: boolean,
startTransactionOnLocationChange?: boolean
) => {
if (startTransactionOnPageLoad) {
instrumentPageload(startTransactionFn)
}

if (startTransactionOnLocationChange) {
instrumentNavigations(startTransactionFn)
}
}

const instrumentPageload = <T extends Transaction>(
startTransactionFn: (context: TransactionContext) => T | undefined
) => {
const initialPath = WINDOW && WINDOW.location && WINDOW.location.pathname

const pageloadTransaction = startTransactionFn({
name: initialPath,
op: 'pageload',
origin: 'auto.pageload.sveltekit',
description: initialPath,
tags: {
...DEFAULT_TAGS
},
metadata: {
source: 'url'
}
})

page.subscribe((page) => {
if (!page) {
return
}

const routeId = page.route && page.route.id

if (pageloadTransaction && routeId) {
pageloadTransaction.setName(routeId, 'route')
}
})
}

/**
* Use the `navigating` store to start a transaction on navigations.
*/
const instrumentNavigations = <T extends Transaction>(
startTransactionFn: (context: TransactionContext) => T | undefined
) => {
let routingSpan: Span | undefined = undefined
let activeTransaction

navigating.subscribe((navigation) => {
if (!navigation) {
// `navigating` emits a 'null' value when the navigation is completed.
// So in this case, we can finish the routing span. If the transaction was an IdleTransaction,
// it will finish automatically and if it was user-created users also need to finish it.
if (routingSpan) {
routingSpan.finish()
routingSpan = undefined
}
return
}

const from = navigation.from
const to = navigation.to

// for the origin we can fall back to window.location.pathname because in this emission, it still is set to the origin path
const rawRouteOrigin =
(from && from.url.pathname) ||
(WINDOW && WINDOW.location && WINDOW.location.pathname)

const rawRouteDestination = to && to.url.pathname

// We don't want to create transactions for navigations of same origin and destination.
// We need to look at the raw URL here because parameterized routes can still differ in their raw parameters.
if (rawRouteOrigin === rawRouteDestination) {
return
}

const parameterizedRouteOrigin = from && from.route.id
const parameterizedRouteDestination = to && to.route.id

activeTransaction = getActiveTransaction()

if (!activeTransaction) {
activeTransaction = startTransactionFn({
name: parameterizedRouteDestination || rawRouteDestination || 'unknown',
op: 'navigation',
origin: 'auto.navigation.sveltekit',
metadata: { source: parameterizedRouteDestination ? 'route' : 'url' },
tags: {
...DEFAULT_TAGS
}
})
}

if (activeTransaction) {
if (routingSpan) {
// If a routing span is still open from a previous navigation, we finish it.
routingSpan.finish()
}
routingSpan = activeTransaction.startChild({
op: 'ui.sveltekit.routing',
description: 'SvelteKit Route Change',
origin: 'auto.ui.sveltekit'
})
activeTransaction.setTag('from', parameterizedRouteOrigin)
}
})
}
26 changes: 26 additions & 0 deletions packages/lib/src/client/sentryInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BrowserOptions, configureScope, init } from '@sentry/svelte'
import { addClientIntegrations } from './addClientIntegrations.js'
import { applySdkMetadata } from './applySdkMetadata.js'
import { restoreFetch } from './restoreFetch.js'
import { switchToFetchProxy } from './switchToFetchProxy.js'

export const sentryInit = (options: BrowserOptions) => {
applySdkMetadata(options, ['sveltekit', 'svelte'])

addClientIntegrations(options)

// 1. Switch window.fetch to our fetch proxy we injected earlier
const actualFetch = switchToFetchProxy()

// 2. Initialize the SDK which will instrument our proxy
init(options)

// 3. Restore the original fetch now that our proxy is instrumented
if (actualFetch) {
restoreFetch(actualFetch)
}

configureScope((scope) => {
scope.setTag('runtime', 'browser')
})
}
17 changes: 17 additions & 0 deletions packages/lib/src/client/switchToFetchProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { WINDOW } from '@sentry/svelte'

export function switchToFetchProxy() {
const globalWithSentryFetchProxy = WINDOW

const actualFetch = globalWithSentryFetchProxy.fetch

// @ts-expect-error TODO: fix this
if (globalWithSentryFetchProxy._sentryFetchProxy && actualFetch) {
globalWithSentryFetchProxy.fetch =
// @ts-expect-error TODO: fix this
globalWithSentryFetchProxy._sentryFetchProxy
return actualFetch
}

return undefined
}
Loading

0 comments on commit a65c2c9

Please sign in to comment.