Sentry Source Maps #

什么是 Source Maps? #

Source Maps 是一种映射文件,将压缩/编译后的代码映射回原始源代码。

text
┌─────────────────────────────────────────────────────────────┐
│                    Source Maps 的作用                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  没有 Source Maps:                                          │
│                                                             │
│  Error at main.min.js:1:2345                                │
│  at a.min.js:2:1234                                         │
│  at b.min.js:3:5678                                         │
│                                                             │
│  → 无法定位问题,代码不可读                                  │
│                                                             │
│  有 Source Maps:                                            │
│                                                             │
│  Error at src/components/Button.tsx:45:12                   │
│  at handleClick (src/hooks/useClick.ts:23:8)                │
│  at onClick (src/App.tsx:102:15)                            │
│                                                             │
│  → 清晰看到源代码位置,快速定位问题                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

生成 Source Maps #

Webpack 配置 #

javascript
// webpack.config.js
const path = require("path");

module.exports = {
  mode: "production",
  
  entry: "./src/index.js",
  
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js",
    sourceMapFilename: "[name].[contenthash].js.map",
    
    // Source Map 路径
    devtoolModuleFilenameTemplate: (info) => {
      return `webpack:///${info.resourcePath}`;
    },
  },
  
  devtool: "hidden-source-map",  // 推荐:生成但不暴露给用户
  
  // 或者使用 source-map
  // devtool: "source-map",
};

Vite 配置 #

javascript
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  
  build: {
    sourcemap: "hidden",  // 或 true
    
    rollupOptions: {
      output: {
        sourcemap: true,
        sourcemapBaseUrl: "https://example.com/sourcemaps/",
      },
    },
  },
});

Next.js 配置 #

javascript
// next.config.js
const { withSentryConfig } = require("@sentry/nextjs");

/** @type {import('next').NextConfig} */
const nextConfig = {
  productionBrowserSourceMaps: true,  // 启用 Source Maps
};

const sentryWebpackPluginOptions = {
  silent: true,
  authToken: process.env.SENTRY_AUTH_TOKEN,
  org: "my-org",
  project: "my-project",
};

module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions);

Rollup 配置 #

javascript
// rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import terser from "@rollup/plugin-terser";

export default {
  input: "src/index.js",
  output: {
    file: "dist/bundle.js",
    format: "iife",
    sourcemap: "hidden",  // 或 true
  },
  plugins: [
    resolve(),
    commonjs(),
    terser({
      sourcemap: true,
    }),
  ],
};

TypeScript 配置 #

json
// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSources": true,
    "sourceRoot": "/"
  }
}

上传 Source Maps #

使用 Sentry CLI #

bash
# 安装 Sentry CLI
npm install -g @sentry/cli

# 上传 Source Maps
sentry-cli sourcemaps upload ./dist \
  --release my-app@1.0.0 \
  --dist ./dist \
  --url-prefix "~/static/js/"

# 或使用配置文件
sentry-cli sourcemaps upload --config .sentryclirc ./dist

使用 Webpack 插件 #

javascript
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

module.exports = {
  // ...其他配置
  
  plugins: [
    sentryWebpackPlugin({
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: "my-org",
      project: "my-project",
      release: {
        name: "my-app@1.0.0",
        create: true,
        finalize: true,
      },
      sourcemaps: {
        assets: "./dist/**/*",
        ignore: "./node_modules/**/*",
        urlPrefix: "~/static/js/",
      },
    }),
  ],
};

使用 Vite 插件 #

javascript
// vite.config.js
import { defineConfig } from "vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";

export default defineConfig({
  build: {
    sourcemap: true,
  },
  
  plugins: [
    sentryVitePlugin({
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: "my-org",
      project: "my-project",
      release: {
        name: "my-app@1.0.0",
      },
      sourcemaps: {
        assets: "./dist/**/*",
        ignore: ["./node_modules/**/*"],
        urlPrefix: "~/static/js/",
      },
    }),
  ],
});

使用 esbuild 插件 #

javascript
// build.js
const esbuild = require("esbuild");
const { sentryEsbuildPlugin } = require("@sentry/esbuild-plugin");

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  outdir: "dist",
  sourcemap: "hidden",
  
  plugins: [
    sentryEsbuildPlugin({
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: "my-org",
      project: "my-project",
      release: {
        name: "my-app@1.0.0",
      },
    }),
  ],
});

CI/CD 集成 #

yaml
# GitHub Actions
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Upload Source Maps
        uses: getsentry/action-release@v1
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
          SENTRY_ORG: my-org
          SENTRY_PROJECT: my-project
        with:
          release: my-app@${{ github.sha }}
          sourcemaps: ./dist
          url_prefix: "~/static/js/"

URL 前缀配置 #

理解 URL 前缀 #

text
┌─────────────────────────────────────────────────────────────┐
│                    URL 前缀匹配                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  错误堆栈中的文件路径:                                      │
│  https://example.com/static/js/main.abc123.js               │
│                                                             │
│  Source Map 文件中的 sources:                               │
│  ["webpack:///./src/index.js", "webpack:///./src/App.js"]   │
│                                                             │
│  需要配置 URL 前缀使它们匹配:                               │
│                                                             │
│  urlPrefix: "~/static/js/"                                  │
│                                                             │
│  匹配规则:                                                  │
│  ~/static/js/main.abc123.js                                 │
│  └── 对应上传的 Source Map 文件                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

常见 URL 前缀配置 #

javascript
// 静态资源在根目录
urlPrefix: "~/"

// 静态资源在 /static/js/ 目录
urlPrefix: "~/static/js/"

// 使用 CDN
urlPrefix: "https://cdn.example.com/static/js/"

// Webpack 默认
urlPrefix: "~/"

验证 URL 前缀 #

bash
# 使用 Sentry CLI 验证
sentry-cli sourcemaps explain \
  --org my-org \
  --project my-project \
  "https://example.com/static/js/main.abc123.js"

Source Map 类型 #

不同类型的 Source Map #

类型 描述 推荐场景
source-map 生成独立的 .map 文件 生产环境
hidden-source-map 生成但不引用 生产环境(推荐)
inline-source-map 内联到文件中 开发环境
eval-source-map 使用 eval 开发环境
nosources-source-map 不包含源代码 保护源码

推荐配置 #

javascript
// 生产环境
devtool: "hidden-source-map"

// 开发环境
devtool: "eval-source-map"

// 需要保护源码
devtool: "nosources-source-map"

验证 Source Maps #

检查上传状态 #

bash
# 列出 Release 的 Source Maps
sentry-cli releases files my-app@1.0.0 list

# 检查特定文件
sentry-cli releases files my-app@1.0.0 get "~/static/js/main.abc123.js.map"

在 Sentry 控制台验证 #

  1. 进入 SettingsProjects → 选择项目 → Source Maps
  2. 查看 Source Map 状态
  3. 检查是否有错误或警告

手动验证 #

bash
# 下载 Source Map 文件
curl -O https://example.com/static/js/main.abc123.js.map

# 检查 Source Map 内容
cat main.abc123.js.map | jq '.sources'

# 验证 Source Map 有效性
npx source-map-visualization main.abc123.js.map

故障排除 #

常见问题 #

text
┌─────────────────────────────────────────────────────────────┐
│                    常见 Source Map 问题                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. Source Map 未找到                                       │
│     原因:URL 前缀配置错误                                  │
│     解决:检查 urlPrefix 配置                               │
│                                                             │
│  2. Release 不匹配                                          │
│     原因:上传和运行时的 Release 不一致                     │
│     解决:确保 Release 名称一致                             │
│                                                             │
│  3. 文件路径不匹配                                          │
│     原因:构建配置问题                                      │
│     解决:检查 devtoolModuleFilenameTemplate                │
│                                                             │
│  4. Source Map 格式错误                                     │
│     原因:构建工具配置问题                                  │
│     解决:检查 sourcemap 配置                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试技巧 #

javascript
// 在 Sentry 初始化时启用调试
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  release: "my-app@1.0.0",
  
  // 启用调试
  debug: true,
  
  // 记录 Source Map 解析
  _experiments: {
    debugSymbols: true,
  },
});

检查清单 #

markdown
## Source Map 检查清单

- [ ] 构建时生成了 Source Map 文件
- [ ] Source Map 文件已上传到 Sentry
- [ ] Release 名称一致
- [ ] URL 前缀配置正确
- [ ] 文件路径匹配
- [ ] Source Map 格式有效
- [ ] 没有缓存问题

多环境配置 #

不同环境的 Source Maps #

javascript
// webpack.config.js
const isProduction = process.env.NODE_ENV === "production";

module.exports = {
  mode: isProduction ? "production" : "development",
  devtool: isProduction ? "hidden-source-map" : "eval-source-map",
  
  plugins: [
    isProduction && sentryWebpackPlugin({
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: "my-org",
      project: "my-project",
      release: {
        name: `my-app@${process.env.APP_VERSION}`,
      },
    }),
  ].filter(Boolean),
};

多 CDN 配置 #

javascript
// 不同 CDN 使用不同 URL 前缀
const cdnUrl = process.env.CDN_URL || "https://cdn.example.com";

sentry-cli sourcemaps upload ./dist \
  --release my-app@1.0.0 \
  --url-prefix "${cdnUrl}/static/js/"

Source Maps 最佳实践 #

1. 使用 hidden-source-map #

javascript
// ✅ 推荐
devtool: "hidden-source-map"

// ❌ 不推荐(会暴露给用户)
devtool: "source-map"

2. 自动化上传 #

yaml
# 在 CI/CD 中自动上传
- name: Upload Source Maps
  run: |
    npm run build
    sentry-cli sourcemaps upload ./dist \
      --release my-app@${{ github.sha }} \
      --url-prefix "~/static/js/"

3. 版本关联 #

javascript
// 确保 Release 名称一致
const release = `my-app@${process.env.APP_VERSION}`;

// 构建配置
Sentry.init({ release });

// 上传配置
sentry-cli releases files ${release} upload-sourcemaps ./dist

4. 保护源码 #

javascript
// 使用 nosources-source-map 保护源码
devtool: "nosources-source-map"

// 这样只会显示文件名和行号,不会显示源代码

5. 清理旧文件 #

bash
# 删除旧版本的 Source Maps
sentry-cli releases files my-app@0.9.0 delete --all

下一步 #

现在你已经掌握了 Source Maps 的知识,接下来学习 隐私与安全 了解如何保护用户隐私!

最后更新:2026-03-29