toucan-js icon indicating copy to clipboard operation
toucan-js copied to clipboard

SvelteKit

Open jokull opened this issue 1 year ago • 6 comments

Has anyone got Toucan to work with SvelteKit on Cloudflare Pages?

EDIT

For now I just hooked Toucan up in src/hooks.server.ts

import type { HandleServerError } from '@sveltejs/kit';
import { Toucan } from 'toucan-js';

// use handleError to report errors during server-side data loading
export const handleError: HandleServerError = ({ error, event }) => {
	const sentry = new Toucan({
		dsn: '<DSN>',
		request: event.request
	});

	sentry.captureException(error);

	return {
		message: error instanceof Error && 'message' in error ? error.message : String(error)
	};
};

jokull avatar Jan 20 '23 16:01 jokull

Yep – right now i'm struggling with getting sourcemaps for releases to get uploaded, that part doesn't quite work yet, stay tuned (I need to check in with the Vite-Toucan example right here to see if I need to change anything)

Throughout here i'm using fake environment checks a la $DEV_CHECK, because you may have some way of doing env/dev checks you prefer – for me I create env-file/Pages settings var booleans and use them to determine whether I'm doing stuff locally, or am in a preview branch, that sort of thing. There are many ways to do those

First thing is add the plugin to the Vite config – I'm assuming here that you have a non-public env var, SENTRY_AUTH_TOKEN, and you don't mind using the Pages commit hash as your release (this works great for me, but you may want some other version name/number) – process exists here and has vars, so leveraging that, no need to get more complicated (though you will have to fake CF_PAGES_COMMIT_SHA='' in your local env file)

// vite.config.js

import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { sentryVitePlugin } from '@sentry/vite-plugin';

// https://vitejs.dev/config/#conditional-config

/** @type {import('vite').defineConfig} */
export default defineConfig(({ mode }) => {
	if ('development' === mode) {
		// local dev:
		return { plugins: [sveltekit()] };
	} else {
		// production (including 'preview' deployments):
		return {
			build: {
				sourcemap: true, // for Sentry
			},
			plugins: [
				sveltekit(),
				sentryVitePlugin({
					org: '$YOUR_ORG_NAME',
					project: '$YOUR_PROJECT_NAME',
					include: './.svelte-kit/cloudflare',
					authToken: process?.env?.SENTRY_AUTH_TOKEN,
					release: process?.env?.CF_PAGES_COMMIT_SHA,
				}),
			],
		};
	}
});

then I have a helper abstraction so I'm not calling Toucan everywhere – this assumes you have an env var for PUBLIC_SENTRY_DSN:

// $lib/helpers.js

import { Toucan } from 'toucan-js';
import { version } from '$app/environment';
import { PUBLIC_SENTRY_DSN } from '$env/static/public';

/**
 * @param {import('@sveltejs/kit').RequestEvent} event
 * @param {Object|string} error
 */
export function notifySentry(event, error) {
	if ($IS_LOCAL_DEV) return;

	let sentryConfig = {
		dsn: PUBLIC_SENTRY_DSN,
		context: event, // includes waitUntil and request which Sentry needs
		allowedHeaders: ['user-agent'],
		allowedSearchParams: /(.*)/,
		environment: $IS_PREVIEW_ENV ? 'dev' : 'prod',
		release: version,
	};
	// we want to look at 404s, but we don't want to be deluged with them in production
	// because in my case hoooo boy clients request all sorts of things
	// https://docs.sentry.io/platforms/javascript/configuration/sampling/
	if (!event.route.id) {
		sentryConfig.tracesSampleRate = $IS_PREVIEW_ENV ? 1 : 0.1;
	}
	const sentry = new Toucan(sentryConfig);
	sentry.captureException(error);
}

and of course making sure version is the same Pages commit hash:

// svelte.config.js

import adapter from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').KitConfig} */
const config = {
	kit: {
		adapter: adapter(),
		version: {
			name: process?.env?.CF_PAGES_COMMIT_SHA ?? Date.now().toString(),
			pollInterval: 60000, // 1 min
		},
	},
	preprocess: [vitePreprocess()],
};

export default config;

colinhowells avatar Feb 15 '23 20:02 colinhowells

By default the sourcemap'll be at ./svelte-kit/cloudflare/_worker.js.map, so i'm assuming there's some way to tell the Vite plugin that's what to upload. Not much success so far. What's frustrating is the Sentry Vite plugin will report uploading a file which the Cloudflare build log reports, but there is no file and no 'artifact' in Sentry for the release:

[sentry-vite-plugin] Info: Successfully created release.
[sentry-vite-plugin] Info: Uploading Sourcemaps.
> Rewriting sources
> Adding source map references
> Bundled 0 files for upload <-- Note the 0!
> Uploaded release files to Sentry
> File upload complete (processing pending on server)

So I'm telling the Sentry plugin there's a map, but not telling it where the map is in a way it understands.

colinhowells avatar Feb 15 '23 20:02 colinhowells

Looks like the sourcemap story with SvelteKit is pretty convoluted at the moment – the Sentry plugin is firing before the map exists? I think?

That said what I posted upthread minus the sourcemap bits will at least get you releases and error reporting

colinhowells avatar Feb 15 '23 22:02 colinhowells

context: event, // includes waitUntil and request which Sentry needs

I'm trying to send context to Sentry but it keeps saying that event doesn't have waitUntil. I'm using Typescript so, doesn't even compile to try

// hooks.server.ts
import '@sentry/tracing';
import type {HandleServerError} from '@sveltejs/kit';
import {Toucan} from 'toucan-js';
import type {RequestEvent} from '@sveltejs/kit';

// Unexpected server errors
// These are only shown in the console log and Sentry
export const handleError: HandleServerError = ({error, event}) => {
    const errorId = crypto.randomUUID();

    const sentryConfig = {
        dsn: import.meta.env.VITE_SENTRY_DSN,
        release: `braindump@${import.meta.env.VITE_SENTRY_RELEASE}`,
        environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
        tracesSampleRate: import.meta.env.VITE_TRACES_SAMPLE_RATE,
        allowedHeaders: ['user-agent'],
        allowedSearchParams: /(.*)/,
        context: event 
    };

    const sentry = new Toucan(sentryConfig); // Error is here saying `event` doesn't have `waitUntil`

    sentry.setTag('svelteKit', 'server');

    sentry.captureException(error);

    return { message: "An unexpected error occurred. We're working on it.", errorId };
};

jerriclynsjohn avatar Feb 28 '23 11:02 jerriclynsjohn

@colinhowells I was able to get the sourcemaps to work, but only for the frontend. The worker part is still missing the sourcemap even though I've uploaded the _worker.js.map as part of the build process.

Here's what I did, followed everything as mentioned in this discussion + Docs

// svelte.config.js

import adapter from '@sveltejs/adapter-cloudflare';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    compilerOptions: {
        enableSourcemap: true
    },
    preprocess: [
        preprocess({
            sourceMap: true,
            postcss: true
        })
    ],

    kit: {
        adapter: adapter(),
    }
};

export default config;

// vite.config.ts

import {sveltekit} from '@sveltejs/kit/vite';
import type {UserConfig} from 'vite';
import {sentryVitePlugin} from '@sentry/vite-plugin';

const sentryPlugin = sentryVitePlugin({
    org: process.env.SENTRY_ORG,
    project: process.env.SENTRY_PROJECT,
    authToken: process.env.SENTRY_AUTH_TOKEN,
    include: [
        {
            paths: ['.svelte-kit/cloudflare']
        }
    ],
    release: process.env.VITE_SENTRY_RELEASE,
    cleanArtifacts: true
});

const config: UserConfig = {
    plugins: [
        sveltekit(),
        {
            name: 'sentry-vite-custom',
            apply: 'build',
            buildStart: sentryPlugin.buildStart,
            resolveId: sentryPlugin.resolveId,
            load: sentryPlugin.load,
            transform: sentryPlugin.transform,
            // Most important part
            closeBundle: sentryPlugin.writeBundle
        }
    ],
};

export default config;

Irrespective of the double uploading of sourcemaps, as mentioned in the GH discussions, Sentry pretty much discards the duplicates so it really doesn't matter. Having sourcemaps in Sentry is literal heaven.

Once the Sourcemap was uploaded there was still a code mapping problem, for which I was able to use the sentry-cli to identify the main problem using this command

sentry-cli sourcemaps explain <event-id-from-issue> -o <org> -p <project>

and I specifically made a code mapping with my new branch and "Stack Trace Mapping" as "../../../../../.."

Now I'm able to see the code where things broke

jerriclynsjohn avatar Feb 28 '23 11:02 jerriclynsjohn

That's cool, you got pretty far! For myself I'm going to wait for something official, but this'll be really helpful for folks.

By the way svelte-preprocess was dropped for vitePreprocess, I'm not sure what the sourcemaps options are there - I think that's something that gets set up in the Vite config (build: { sourcemap: true }) instead now.

colinhowells avatar Feb 28 '23 15:02 colinhowells