构建前端监控体系:Sentry私有化部署与项目集成实践

在现在复杂多变的前端环境中,错误追踪一直是个棘手难题;用户设备千差万别、生产环境问题是难以复现,传统日志更是力不从心;因此才能如何更好的追踪错误一直是一个问题;目前市面上有很多开源的工具,而Sentry无疑是其中功能丰富、支持率最高的,它让问题定位变得前所未有地清晰;本文将详细介绍如何为前端项目部署和接入Sentry,从基础配置到高级优化,助您构建更稳定的应用体验。

经常在生产环境写bug的同学都知道,面对用户突如其来的报错有多么无奈,本地运行一切正常,但是一到线上,就状况百出:莫名的白屏、交互的卡死、控制台的报错;更令人抓狂的是,用户的反馈永远只有一句:"页面打开就这样了啊!"你根本无法看到用户的操作轨迹,不知道他们点击了什么、输入了什么、遇到了什么异常。

这种没有监控的被动反馈就像是在黑夜中摸索着修理一台精密的仪器,你只能从仪器偶尔发出的异响来猜测问题出在哪,却看不清内部哪个齿轮卡住、哪根电路熔断,过程缓慢且无奈。

现在我们有了开源的Sentry,就像换装上了先进探测的氮化镓雷达系统,它能够捕获前端的每一次异常,记录用户操作的全链路轨迹,让生产环境的崩溃瞬间定格为清晰的诊断报告和监控录像,下面我们就来看一下如何部署Sentry。

私有化服务部署

Sentry英文翻译过来,意思是"哨兵",在开始集成Sentry之前,我们需要先给这位"忠诚"的哨兵建造一座坚实的"岗亭";是直接让它驻守在官方的公共云基地(SaaS服务),还是在你自己的网络领地内为它搭建一座专属的堡垒(私有化部署)?这个选择至关重要。

官方SaaS服务是"拎包入住"的快捷选择------注册即用、自动维护、功能即时更新,尤其适合大多数独立开发者和初创团队。免费套餐足以支撑前期的核心监控需求。

但是,当你的项目需要满足严格的内网合规要求时,将哨兵和所有监控数据都留在自己的城墙之内,就成为了必须。私有化部署赋予了你对数据、网络和版本的完全控制权,虽然部署和维护成本相应增加,但它带来的安全性和自主性,对于许多企业级应用而言是不可或缺的。

在向公司运维同事"软磨硬泡"后,我们成功申请到了一台如下配置专用服务器来部署Sentry:

  • CPU: 8核
  • 内存: 16G
  • 硬盘: 50G

这个配置部署Sentry,说实话,已经算是打了个"富裕仗"------毕竟官方推荐的最低配置是4核8G,我们这已经达到了中等生产环境的要求。充足的资源意味着我们可以更从容地配置缓存、启用更多工作进程,并为未来的数据增长留足空间。

在接下来的步骤中,我们将基于这台"富裕"的服务器来安装,使用Docker Compose的标准化部署;首先克隆sentry-self-hosted这个项目:

bash 复制代码
git clone https://github.com/getsentry/self-hosted.git sentry-self-hosted
cd sentry-self-hosted
./install.sh

然后就可以看到镜像咔咔地开始下载,下载到一半,我们会看到这样的提示:

vbnet 复制代码
Hey, so ... we would love to automatically find out about issues with your
Sentry instance so that we can improve the product. Turns out there is an app
for that, called Sentry. Would you be willing to let us automatically send data
about your instance upstream to Sentry for development and debugging purposes?

  y / yes / 1
  n / no / 0

(Btw, we send this to our own self-hosted Sentry instance, not to Sentry SaaS,
so that we can be in this together.)

Here's the info we may collect:

  - OS username
  - IP address
  - install log
  - runtime errors
  - performance data

Thirty (30) day retention. No marketing. Privacy policy at sentry.io/privacy.

这是一个可选的匿名数据贡献提示,用于帮助Sentry团队改进产品功能。我们可以根据自身需求和数据政策,自由选择同意或跳过。当所有必需的Docker镜像成功下载完成后,终端将显示类似下图的提示信息:

接下来,我们按照提示命令以启动Sentry服务:

bash 复制代码
docker compose up --wait

服务启动后,在浏览器中访问:http://[你的服务器IP]:9000/,期待已久的Sentry管理界面便会展现在眼前。不过,如果因为我们是首次启动服务,系统会出于安全考虑,通常会显示一个关于CSRF(跨站请求伪造)保护的配置提示页面:

这个时候我们需要修改sentry目录下的sentry.conf.py文件:

bash 复制代码
vim sentry/sentry.conf.py

将我们的域名添加到CSRF_TRUSTED_ORIGINS中,一般添加内网的ip地址,以及部署sentry的域名即可:

ini 复制代码
CSRF_TRUSTED_ORIGINS = ["https://sentry.[your domain].com", "http://127.0.0.1:9000", "http://[your ip]:9000"]

经过上面的修改后,我们再次重启服务,相信Sentry就可以正常访问了。

bash 复制代码
docker compose down
docker compose up --wait

最后我们就可以看到这样的一个欢迎界面,这里根据提示填写网站的根路径和管理员邮箱地址:

Sentry默认会保留90天的日志数据。如果服务器存储资源紧张,可以通过修改.env文件中的SENTRY_EVENT_RETENTION_DAYS配置项来缩短数据保留周期,例如将其从90天调整为30或15天。这能有效释放存储压力,但需要注意的是,更短的数据保留周期会限制历史问题的回溯与分析能力。

邮件配置

邮件配置是Sentry部署后至关重要的一环,它直接关系到团队的协作效率和问题感知度,如果没有配置邮件,将不能邀请团队成员;我们需要在sentry/config.yml文件中正确设置SMTP邮件服务器的信息:

php 复制代码
# SMTP地址
mail.host: 'smtp.example.com'
# 端口
mail.port: 465
# 账号
mail.username: 'XXX@example.com.cn'
# 密码
mail.password: 'XXX'
# 是否使用SSL
mail.use-ssl: true
# 发送方
mail.from: 'XXX@example.com.cn'

一旦配置成功,Sentry的邮件系统将提供以下功能:

  • 邀请用户注册:允许用户通过邮件邀请加入组织。
  • 实时告警通知:当某个错误频率超过设定的阈值时,立即发送邮件通知用户。
  • 定期摘要:可配置每日/每周发送错误摘要报告。

设置成功,重启服务器后,打开控制台 => 管理 => Mail,看到刚才的配置了,还可以发送一封测试邮件:

项目接入

上面Sentry部署完成后,我们就可以开始愉快地将我们手上的项目来接入Sentry了;登录控制台,点击创建项目,然后选择Vue作为平台,输入项目名称(Project slug)

创建项目后,根据提示,我们会看到一个DSN码,这个DSN码是Sentry与Vue项目之间的通信桥梁,我们需要将这个DSN配置到Vue项目中。

Sentry提供了Vue的官方插件(支持Vue2或者Vue3),我们在Vue项目中可以很方便的接入Sentry;这里以Vue3项目为例,安装官方SDK插件:

bash 复制代码
pnpm add @sentry/vue

只需要在main.ts中引入并初始化即可:

typescript 复制代码
import * as Sentry from '@sentry/vue';
import pkg from '../package.json';

async function bootstrap() {
  const app = createApp(App);
  
  Sentry.init({
    app,
    dsn: '【your-dsn】',
    sendDefaultPii: true,
    // 环境
    environment: VITE_ENV,
    release: `[project slug]@${pkg.version}`
  });
  
  app.mount('#app');
}

这里属性比较多,我们逐个介绍每个参数的作用;我们将createApp创建后的Vue实例传入初始化函数,如果有多个实例,还支持数组格式:

typescript 复制代码
Sentry.init({
  app: [app1, app2, app3],
  dsn: '[your-dsn]',
})

sendDefaultPii控制是否发送用户信息,默认为false,只收集最简单的信息,比如浏览器以及版本、操作系统;如果为true,则Sentry会发送更详细的用户信息,如:浏览器详细agent、操作系统、设备类型、IP地址等个人身份信息;开启前确认符合GDPR等隐私法规要求,更多关于GDPR的详情,请查看浏览网站时为什么老是要我接受Cookie文章。

environment字段根据名称我们也能猜到,标识当前环境,比如开发环境、测试环境、生产环境等,便于过滤和统计特定环境的错误报告;release字段则是项目的版本号,用于标识当前项目,方便后续查看,推荐为项目名@版本号格式,精确追踪特定版本的问题,这里我们直接从package.json自动获取版本号,确保版本号的一致性。

集成插件

Sentry支持将很多功能通过插件的形式集成进来;下面我们介绍几个常用的集成功能。首先就是浏览器追踪browserTracingIntegration集成,用于追踪浏览器中的错误和性能数据,比如页面加载时间、资源加载时间、用户行为等等;同时,如果我们使用了Vue Router,还可以将Router集成进来:

typescript 复制代码
Sentry.init({
  integrations: [
    Sentry.browserTracingIntegration({ 
      router,
      routeLabel: 'name'
    })
  ],
  tracesSampleRate: 0.8,
})

routeLabel参数决定了Sentry如何标识你的路由页面,默认为path,Sentry中路由就会显示具体路径,比如/user/:id/profile;而修改为name,Sentry中路由就会显示路由名称,比如UserProfile,更为简洁。

tracesSampleRate参数控制着用户在使用时,性能数据被上报的概率,这是一个需要在数据质量和系统开销之间找到平衡点的重要配置项;最大值为1,表示100%上报,这里我们设置为0.8,意味着在用户使用时,有20%的概率会上报性能数据。

在开发和调试阶段,可以设置为较高的值,比如1.0,这样可以让所有的性能数据都上报,方便调试;当项目发布到生产环境时,可以设置一个较低的值,比如0.1,这样只有10%的用户使用,性能数据才会上报,从而提高服务器的承载力。

对于复杂场景,Sentry支持更智能的动态采样:

typescript 复制代码
Sentry.init({
  tracesSampleRate: 0.1,
  tracesSampler: (samplingContext) => {
    const { name } = samplingContext;

    // 测试环境
    if (process.env.NODE_ENV === "test") {
      return 1.0;
    }

    // 支付页面
    if (name.includes("/pay")) {
      return 0.8;
    }

    // 登录页面
    if (name.includes("/login")) {
      return 0.6;
    }

    return 0.1;
  },
})

比如我们在测试环境下,我们希望所有的数据都上报,支付页面和登陆页面,我们希望有80%和60%的概率上报,其他页面,我们希望有10%的概率上报。

另一个比较重要的集成插件就是replayIntegration会话回放集成,它允许我们录制并回放用户在网站上的真实操作过程;传统的监控模式只能体现报错堆栈的内容,但是用户点击了什么才导致这个错误?点击前页面是什么状态?对于这些情况我们一概都不清楚。通过集成replayIntegration插件后,我们可以在管理后台查看错误发生前几分钟的用户操作录像,从而更容易帮助我们定位问题。

typescript 复制代码
Sentry.init({
  integrations: [
    Sentry.replayIntegration(),
  ],
  // 10% 会话录制
  replaysSessionSampleRate: 0.1,
  // 100% 错误时录制
  replaysOnErrorSampleRate: 1.0,
})

这里的replaysSessionSampleRate用于设置常规用户会话录制的采样率,而replaysOnErrorSampleRate则专用于对发生错误的会话进行录制的采样率,一般设为100%,表示所有错误都进行录制。

这里我们在界面上有一些文字和输入框,在回放界面上都被用**给隐藏了;这也是因为GDPR政策的限制,默认情况下,Sentry会隐藏用户输入的文本,防止敏感信息泄露;我们可以通过下面三个参数设为false,来显示所有内容:

typescript 复制代码
Sentry.replayIntegration({
  // 隐藏所有的文本
  maskAllText: false,
  // 隐藏所有的输入框
  maskAllInputs: false,
  // 屏蔽所有的img、canvas等媒体
  blockAllMedia: false,
})

我们还可以通过unblockmaskunmask等字段精确控制文本的显示和隐藏;例如我们通过设置mask字段,针对密码输入框等特殊内容进行遮挡屏蔽。

还有一个vue项目中可以用的集成插件就是vueIntegration,用于追踪所有Vue组件的错误和性能数据,比如在组件加载mounted、更新update、销毁unmount的生命周期中进行追踪。

vueIntegration插件有一个tracingOptions参数,可以控制我们需要追踪的组件,默认trackComponents为false,表示不追踪组件,设置为true,表示追踪所有组件。

typescript 复制代码
Sentry.init({
  integrations: [
    Sentry.vueIntegration({
      tracingOptions: {
        // 追踪所有组件
        trackComponents: true,
        // OR 特定组件
        trackComponents: [
          "App",
          "RwvHeader",
          "RwvFooter",
          "RwvArticleList",
          "Pagination",
        ],
      },
    }),
  ],
});

除此之外,我们还可以更精确的控制,组件的哪些生命周期会进行追踪;例如我们只想要在组件被销毁时进行追踪,那么可以设置:

typescript 复制代码
Sentry.vueIntegration({
  tracingOptions: {
    trackComponents: true
    hooks: ["mount", "update", "unmount"],
  }
})

默认追踪的hooks是['activate', 'mount', 'update']
Vue2项目中追踪组件销毁使用destroy,而不是unmount

另一个Vue项目可以添加的集成插件是createSentryPiniaPlugin,是Sentry为Pinia状态管理库提供的插件,用于追踪store中的异常和状态变化;它的集成方式和上面的有所差异,代码如下:

typescript 复制代码
import { createPinia } from "pinia";
import { createSentryPiniaPlugin } from "@sentry/vue";

const pinia = createPinia();
pinia.use(createSentryPiniaPlugin());

createSentryPiniaPlugin会将最后状态作为附件上传到报错信息中,我们可以在管理后台查看:

我们可以通过设置attachPiniaState为false,来关闭Pinia状态的附件上传:

typescript 复制代码
createSentryPiniaPlugin({
  attachPiniaState: false,
})

最后一个集成插件是captureConsoleIntegration,它的作用是将控制台打印的信息全部上报给Sentry:

typescript 复制代码
Sentry.init({
  integrations: [
    Sentry.captureConsoleIntegration({
      // 默认是所有日志级别
      levels: ['log', 'info', 'warn', 'error', 'debug', 'assert']
    }),
  ]
})

我们还可以通过levels字段设置上报的日志级别;上报后,在管理后台中我们在对应的事务中就能看到日志信息:

自定义上报

在错误追踪时,知道"哪个用户遇到了问题"往往比知道"出现了什么问题"更有价值;Sentry的setUser函数正是连接错误与用户的桥梁,它能将当前用户的信息关联到后续上报的错误和性能事务中。当错误发生时,你不再只是看到一个冰冷的堆栈跟踪,而是能清晰地知道:

  • 哪个用户遇到了这个错误
  • 这个用户的基本属性
  • 用户的使用环境信息

而用户登录页面正是设置用户基本属性的最佳时机:

typescript 复制代码
import * as Sentry from "@sentry/vue";

const handleLogin = async () => {
  const res = await loginAPI(params);
  const user = res.data.user;
  Sentry.setUser({
    id: user.id,
    email: user.email,
    username: user.username,
    // 自定义字段
    // 租户id
    tenantId: user.tenantId,
    // 国家
    country: user.country,
    // 部门
    department: user.department,
    // 其他字段...
  });

  // 可选:设置用户标签便于筛选
  Sentry.setTag('user.tier', user.tier);
  Sentry.setTag('user.plan', user.plan);
}

在用户退出登录时,我们也需要通过setUser清理信息,否则会导致出现用户信息"张冠李戴"的情况。

typescript 复制代码
import * as Sentry from "@sentry/vue";
const handleLogout = async () => {
  Sentry.setUser(null);
  Sentry.setTag('user.tier', null);
  Sentry.setTag('user.plan', null);
}

在Sentry仪表盘,进行项目,在每个用户路由跟踪的上面,我们就能看到用户信息:

除此之外,Sentry还提供了丰富灵活的各种自定义上报函数,让我们不仅能够捕获错误,还能主动上报各种事件消息;其中最核心、最常用的上报函数就是captureException,它可以用来上报任意类型的Javascript错误:

typescript 复制代码
import * as Sentry from "@sentry/vue";

Sentry.captureException(new Error("Something went wrong!"));

captureMessage是Sentry中用于上报自定义消息的核心函数。不同于captureException专门处理异常,captureMessage 更像是监控版的console.log,专门用于记录应用状态、用户行为、性能指标等非异常信息。

typescript 复制代码
import * as Sentry from "@sentry/vue";

// 基本用法:上报文本消息
Sentry.captureMessage('用户登录成功');
Sentry.captureMessage('订单创建完成');
Sentry.captureMessage('页面加载时间过长');

// 带级别的消息
Sentry.captureMessage('调试信息', 'debug');     // 调试级别
Sentry.captureMessage('用户操作记录', 'info');    // 信息级别(默认)
Sentry.captureMessage('内存使用率超过80%', 'warning'); // 警告级别
Sentry.captureMessage('API响应时间超过5秒', 'error');  // 错误级别
Sentry.captureMessage('数据库连接失败', 'fatal');     // 致命级别

sourcemap上传

在未启用Source Map的情况下,生产环境的错误堆栈就像一团加密后的乱码,你只能看到类似a1b2c3.min.js:1:23456 这样令人困惑的定位,几乎无法追溯到引发问题的原始业务逻辑。:

而Source Map的作用,正是为这团"乱码"提供了一把精准的解码钥匙;通过正确配置并上传,错误堆栈将还原为清晰可读的源码路径,例如 src/components/Checkout.vue:42:8。此时,有意义的变量名、熟悉的函数名和完整的代码上下文都将一览无余,让定位和排查问题的过程从"大海捞针"变为"按图索骥",效率得到质的提升。

获取三个重要参数

在配置之前,我们需要通过管理后台获取三个重要的参数信息,首先是org-slug,表示组织名称,进入设置 => 常规设置 => Organization Slug就可以获取:

然后是project-slug,代表是项目的名称,在Sentry中进入具体项目后,可以查看:

最后一个,也是最关键的authToken,代表授权令牌,进入设置 => 开发设置 => Organization Tokens中创建一个Token:

我们在开发设着中会看到两种类型的Token,一种是Personal Access Token,另一种是Organization Token,它们之间主要区别如下表:

特性 Personal Tokens Organization Tokens
归属主体 个人用户账户 组织
权限范围 继承用户的权限 继承组织权限
创建者 用户自己 组织管理员或 Owner
生命周期 用户离职后失效 与用户解耦,持续有效
使用场景 个人开发、调试 团队协作、CI/CD、自动化
可见性 仅创建者可见 组织管理员可见

需要注意的是,authToken不应直接写在项目代码里,以免意外泄露。推荐的实践是配置在持续集成环境(如 Jenkins)的构建参数或保密变量中,然后在构建脚本中通过命令来调用,例如我们在Jenkins构建脚本中添加环境变量:

arduino 复制代码
export VITE_SENTRY_AUTH_TOKEN="【your-auth-token】"
npm run build

需要注意的是:配置Linux的环境变量,等号两边不能有空格,否则会报错。

或者在构建服务器上创建.env.production.local的配置文件,一般git忽略.env.*.local的文件;这样在构建时,会自动添加到环境变量中:

ini 复制代码
# .env.production.local
VITE_SENTRY_AUTH_TOKEN = sntrys_org_xxx_yyy_zzz

构建工具配置

上面我们上传sourcemap所需要的三个参数信息都有了,下面,我们就需要在构建工具中进行配置上传;如果是webpack项目,修改配置文件:

typescript 复制代码
const { sentryWebpackPlugin } = require('@sentry/webpack-plugin')

module.exports = {
  devtool: "hidden-source-map",
  plugins: [
    sentryWebpackPlugin({
      url: '【your-url】',
      org: '【org-slug】',
      project: '【project-slug】',
      authToken: process.env.VUE_SENTRY_AUTH_TOKEN,
      release: {
        name: `【project-slug】@【version】`,
      },
      sourcemaps: {
        filesToDeleteAfterUpload: [
          "./dist/**/*.map",
        ],
      },
    }),
  ],
}

如果是vite项目,编辑vite.config.ts文件:

typescript 复制代码
import { sentryVitePlugin } from '@sentry/vite-plugin';
export default {
  build: {
    sourcemap: "hidden",
  },
  plugins: [
    sentryVitePlugin({
      url: '【your-url】',
      org: '【org-slug】',
      project: '【project-slug】',
      authToken: VITE_SENTRY_AUTH_TOKEN || '',
      release: {
        name: `【project-slug】@【version】`,
      },
      sourcemaps: {
        filesToDeleteAfterUpload: [
          './dist/assets/*.map'
        ],
      },
    }),
  ]
}

url需要改为自己服务器的地址,如果不写默认会传到Sentry的官方服务器。

这里的filesToDeleteAfterUpload字段,都是用来在上传成功后,删除本地的sourcemap文件,防止sourcemap文件泄漏;而release字段,则是用来指定上传的版本号,需要和上面Sentry.init中的release配置一致。

如果后续再次出现报错,我们可以在Sentry中很方便的定位到报错源码位置:

总结

🎉 恭喜你!跟着本文指南走完,相信你已经成功为你的前端项目请来了一位 "7 * 24小时不眨眼的全能哨兵" ------Sentry!整个部署过程从Sentry私有化服务搭建开始,我们通过Docker Compose在独立服务器上建立了专属监控中心,确保数据安全可控。这就像是给项目配备了一个24小时不眨眼的数字哨兵。

在项目集成环节,我们从基础SDK配置入手,逐步实现了错误捕获、用户追踪、性能监控等核心功能。特别针对Vue生态,我们配置了路由追踪、Pinia状态监控、组件生命周期追踪等针对性方案,让监控覆盖到应用的每个角落。

关键技术配置方面,我们重点解决了生产环境调试的两大难题:一是通过Source Map自动上传机制,将压缩代码的报错信息还原为可读的源码位置;二是通过智能采样策略,在监控精度与系统开销之间找到最佳平衡点。

最终,我们构建了一个从前端异常发生到开发团队响应的完整闭环。现在当线上问题出现时,不再需要依赖用户模糊的描述,而是可以直接查看错误详情、用户操作回放、性能数据等多维度信息,大大缩短了问题排查时间。从此,线上bug不再是"薛定谔的猫",让每个线上问题都无处遁形,显著提升了应用的稳定性和可维护性。

如果觉得写得还不错,请关注我的掘金主页。更多文章请访问谢小飞的博客

相关推荐
qq_4061761414 小时前
什么是模块化
开发语言·前端·javascript·ajax·html5
周小码14 小时前
CodeEdit:Electron编辑器的原生替代品?
javascript·electron·编辑器
菩提祖师_14 小时前
量子计算在网络安全中的应用
开发语言·javascript·爬虫·flutter
技术钱14 小时前
vue3 + element plus实现表头拖拽数组进行汇总
前端·javascript·vue.js
柒@宝儿姐14 小时前
vue3中使用element-plus的el-scrollbar实现滚动触底加载更多
前端·javascript·vue.js
蜗牛攻城狮14 小时前
深入理解 Vue.js 中的「运行时」与「编译时」:从模板到虚拟 DOM 的全过程
前端·javascript·vue.js
亮子AI14 小时前
【JavaScript】forEach 是按数组顺序执行吗?
开发语言·javascript·ecmascript
daqinzl14 小时前
JavaScript loop & sleep
javascript·loop 循环·sleep 睡眠
菩提祖师_14 小时前
基于Docker的微服务自动化部署系统
开发语言·javascript·flutter·docker