记录一下 Nuxt3 接入 Sentry 的过程和踩过的坑
Sentry for Nuxt 的官方包 @nuxtjs/sentry
目前只支持 Nuxt2,它的开发者表示没有动力为 Nuxt3 开发这个模块。
在我自己为 Nuxt3 接入 Sentry 时还是踩了不少坑的,前后花的时间至少有 5 个小时,这里直接贴出最后可用的代码。
Sentry Module
我把 sentry 相关的代码都封装到了 Nuxt Module 中,不过没有花心思设计配置项,都是直接读取 public runtime config,所以有些地方需要按自己的情况去修改,需要修改的地方都已标注。
import { defineNuxtModule, createResolver, addPlugin, addServerPlugin, addComponentsDir, addImports, addTypeTemplate,} from "@nuxt/kit";
export default defineNuxtModule({ setup(options, nuxt) { const { resolve } = createResolver(import.meta.url);
addPlugin({ mode: "client", src: resolve("./runtime/plugins/nuxt"), });
addServerPlugin(resolve("./runtime/plugins/nitro"));
addComponentsDir({ path: resolve("runtime/components"), });
addImports({ name: "useSentry", from: resolve("runtime/composables"), });
addTypeTemplate({ filename: "types/sentry-module.d.ts", getContents: () => `// Generated by sentry-module import * as SentryVue from '@sentry/vue'; import * as SentryNode from '@sentry/node';
declare module '#app' { interface NuxtApp { $sentry?: Pick<typeof SentryVue, 'captureException' | 'captureMessage' | 'withScope'>; } }
declare module 'vue' { interface ComponentCustomProperties { $sentry?: Pick<typeof SentryVue, 'captureException' | 'captureMessage' | 'withScope'>; } }
declare module 'h3' { interface H3EventContext { $sentry?: typeof SentryNode; } }
export {};`, }); },});
import * as Sentry from "@sentry/vue";
export default defineNuxtPlugin({ name: "sentry", setup(nuxtApp) { // TODO: 需要改为你自己的配置项 const { public: publicConfig } = useRuntimeConfig(); if (!publicConfig.sentry.dsn) { return; }
Sentry.init({ app: nuxtApp.vueApp,
// TODO: 需要改为你自己的配置项 dsn: publicConfig.sentry.dsn, release: publicConfig.appVersion, environment: publicConfig.env,
integrations: [ new Sentry.BrowserTracing({ routingInstrumentation: Sentry.vueRouterInstrumentation(useRouter()), }), ],
// Sample rate for performance monitoring. tracesSampleRate: 0.1,
// Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled // TODO: 需要改为你自己的配置项 tracePropagationTargets: ["localhost", /^https:\/\/example\.com\/api/],
replaysSessionSampleRate: 0, replaysOnErrorSampleRate: 0.5,
beforeSend: (event, hint) => { // TODO: 需要改为你自己的配置项 // 上报用户信息 const user = useAuthUser(); if (user.value) { event.user = event.user || {}; event.user.id = user.value.id; } return event; }, });
// 只导出部分接口,可以减小 entry.js 体积 nuxtApp.provide("sentry", { captureException: Sentry.captureException, captureMessage: Sentry.captureMessage, withScope: Sentry.withScope, }); },});
import * as Sentry from "@sentry/node";import { H3Error } from "h3";
export default defineNitroPlugin(nitroApp => { // TODO: 需要改为你自己的配置项 const { public: publicConfig } = useRuntimeConfig(); if (!publicConfig.sentry.dsn) { return; }
Sentry.init({ // TODO: 需要改为你自己的配置项 dsn: publicConfig.sentry.dsn, release: publicConfig.appVersion, environment: publicConfig.env,
// Sample rate for performance monitoring. tracesSampleRate: 0.1,
// Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled // TODO: 需要改为你自己的配置项 tracePropagationTargets: ["localhost", /^https:\/\/example\.com\/api/], });
nitroApp.hooks.hook("request", event => { event.context.$sentry = Sentry; });
nitroApp.hooks.hook("error", async err => { // Do not handle 404s if (err instanceof H3Error) { if (err.statusCode === 404) { return; } }
Sentry.captureException(err); });
nitroApp.hooks.hookOnce("close", async () => { await Sentry.close(2000); });});
然后在 Nuxt 中添加 sentry 配置,启动服务时设置环境变量 SENTRY_DSN
或 NUXT_PUBLIC_SENTRY_DSN
即可。
export default defineNuxtConfig({ runtimeConfig: { public: { sentry: { dsn: process.env.SENTRY_DSN || "", }, }, },});
其他工具类:
type Sentry = Exclude<ReturnType<typeof useNuxtApp>["$sentry"], undefined>;
export const useSentry = (scope: (sentry: Sentry) => void) => { const { $sentry } = useNuxtApp(); if ($sentry) { scope($sentry); }};
Sentry Replay
懒加载 Sentry Replay 插件
<script setup lang="ts">onMounted(async () => { try { // lazy load sentry replay const { addIntegration, Replay } = await import("@sentry/vue"); addIntegration( new Replay({ beforeErrorSampling: event => { // 这里加了一个自定义 tag,来过滤有些错误不需要记录 replay if (event.tags?.["no-sentry-replay"]) { return false; } return true; }, }) ); } catch { // 重复加载 sentry replay 插件会抛出异常 // 正常情况下不会出现这个问题,在开发环境热加载有可能出现 // 这个集成并不是很重要,只是作为 debug 的辅助手段,所以加载失败也无所谓 }});</script>
<template></template>
这里单独写一个 Sentry.vue
组件来懒加载 replay 插件是处于性能考虑,不用 hooks 实现的原因是我试过了 app:mounted
等 hook 懒加载时,控制台会出现大量警告。
在 app.vue
中引入这个组件:
<template> <ClientOnly> <Sentry /> </ClientOnly> <NuxtLayout> <NuxtPage /> </NuxtLayout></template>
手动上报 Sentry
Sentry 会默认监控 vue 组件异常并上报,手动上报时只需要用 useSentry
这个 composable
<script setup lang="ts">onMounted(() => { useSentry(sentry => sentry.captureException(new Error("Something went wrong")));});</script>
Nitro Server 的报错也会自动上报,手动上报的话可以用 event.context.$sentry
:
export default defineEventHandler(async event => { try { throw new Error("Something went wrong"); } catch (err) { event.context.$sentry?.captureException(err); }});