> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flashduty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 源码映射与异常追踪

> 本文档详细介绍如何使用 RUM 进行源码映射管理，以及如何通过源码映射进行异常追踪和调试。

Flashduty 支持多平台的符号文件上传与源码映射，帮助开发者将混淆或压缩后的错误堆栈还原为可读的原始源代码。

* **Web（JavaScript）**：通过 [Flashduty CLI](https://github.com/flashcatcloud/flashcat-cli) 上传 `sourcemap` 文件
* **微信小程序**：通过 Flashduty CLI 上传 `miniprogram-ci get-dev-source-map` 生成的 `sourcemap.zip`
* **HarmonyOS**：通过 `@flashcatcloud/hvigor-plugin` 上传 ArkTS `sourceMaps.map`、可选 `nameCache.json` 和 Native `.so` 符号文件
* **Android**：通过 Gradle 插件自动上传 ProGuard/R8 mapping 文件和 NDK 符号文件
* **iOS**：通过 Flashduty CLI 上传 dSYM 符号文件

用户可在「应用管理」-「源码管理」菜单查看已上传的符号文件，并通过上传面板生成脚本在本地执行上传操作。

## 为什么需要源码映射？

在现代应用开发中，代码通常会被压缩、混淆或编译，以优化加载速度和性能。无论是 Web 端的 JavaScript 压缩、微信小程序的发布包转换、HarmonyOS 的 ArkTS 构建产物、Android 的 ProGuard/R8 混淆，还是 iOS 的编译优化，这些处理都会导致错误堆栈中的代码位置信息无法直接映射到原始源代码，增加了调试难度。

<CardGroup cols={3}>
  <Card title="映射压缩代码" icon="map">
    `SourceMap` 记录了压缩代码与原始代码之间的映射关系，允许开发者在调试时查看未压缩的源代码
  </Card>

  <Card title="精确定位错误" icon="crosshairs">
    通过 `SourceMap` 可以在异常追踪中直接定位到原始源代码中的具体位置
  </Card>

  <Card title="提升调试效率" icon="gauge-high">
    开发者无需手动解码压缩文件，节省排查问题的时间
  </Card>
</CardGroup>

## 生成 SourceMap

大多数现代构建工具（如 Webpack、Rollup 或 Vite）都支持生成 `SourceMap`。

<Tabs>
  <Tab title="Webpack">
    在 `webpack.config.js` 中启用 `SourceMap` 生成：

    ```javascript theme={null}
    module.exports = {
      mode: "production",
      devtool: "source-map", // 生成独立的 .map 文件
      output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "dist"),
      },
    };
    ```

    构建后，`dist` 目录中会生成 `bundle.js` 和对应的 `bundle.js.map` 文件。
  </Tab>

  <Tab title="Vite">
    在 `vite.config.js` 中配置：

    ```javascript theme={null}
    export default {
      build: {
        sourcemap: true, // 生成 sourcemap
      },
    };
    ```
  </Tab>

  <Tab title="Rollup">
    在 `rollup.config.js` 中配置：

    ```javascript theme={null}
    export default {
      output: {
        sourcemap: true,
      },
    };
    ```
  </Tab>
</Tabs>

## 上传 SourceMap

使用 Flashduty CLI 将 `sourcemap` 文件上传至 Flashduty 服务器。

<Steps>
  <Step title="安装 Flashduty CLI">
    确保已安装 Node.js，然后通过 npm 安装：

    ```bash theme={null}
    npm install -g @flashcatcloud/flashcat-cli
    ```
  </Step>

  <Step title="配置上传参数">
    <Frame>
      <img src="https://docs-cdn.flashcat.cloud/imges/png/69c1c53e5df18d5241d8e0fa17e56198.png" alt="上传源码配置" />
    </Frame>

    在「应用管理」-「源码管理」菜单中，点击「上传源码」面板，填写以下信息：

    <ParamField path="API Key" type="string" required>
      用于认证您的身份
    </ParamField>

    <ParamField path="服务名" type="string" required>
      应用的服务名（例如 `my-service`）
    </ParamField>

    <ParamField path="版本号" type="string" required>
      应用的发布版本（例如 `1.0.0`）
    </ParamField>

    <ParamField path="压缩文件前缀" type="string" required>
      压缩文件的路径前缀（例如 `/assets`）
    </ParamField>
  </Step>

  <Step title="执行上传">
    在项目根目录下运行生成的脚本：

    ```bash theme={null}
    flashcat-cli sourcemaps upload \
      --service my-service \
      --release-version 1.0.0 \
      --minified-path-prefix /assets \
      --api-key your-api-key \
      ./dist
    ```
  </Step>
</Steps>

<Warning>
  * 确保 `minified-path-prefix` 与实际部署的压缩文件路径一致
  * 上传成功后，可在「应用管理」-「源码管理」中查看已上传的 `sourcemap` 文件
</Warning>

## 上传微信小程序 SourceMap

微信小程序发布后，线上错误堆栈通常只包含转换后的文件路径和行列号。你可以先使用 `miniprogram-ci get-dev-source-map` 生成 `sourcemap.zip`，再通过 Flashduty CLI 上传。上传后，Flashduty 会按服务名、版本和文件路径匹配小程序错误堆栈，并在异常详情中展示还原后的源码位置。

<Steps>
  <Step title="生成小程序 SourceMap 压缩包">
    在小程序项目中运行 `miniprogram-ci get-dev-source-map`，生成可上传的 `sourcemap.zip` 文件。该文件路径会作为上传命令的 `--sourcemap-zip` 参数。

    `miniprogram-ci get-dev-source-map` 默认输出的 zip 结构即满足以下要求，通常无需手动调整：

    * 主包的 `.js.map` 条目放在 `__FULL__/` 目录下，例如 `__FULL__/app-service.js.map`
    * 分包的 `.js.map` 条目放在以分包名命名的目录下，例如 `subpkg-a/chunk_0.appservice.js.map`，目录名会作为分包标识写入元数据
    * 只有 `.js.map` 后缀的条目会被采纳，其他文件（README、source 文件等）会被忽略
    * 若 zip 中找不到任何 `.js.map` 条目，上传会返回错误 `No .js.map entries found in the sourcemap archive`

    <Note>
      单个 `.js.map` 解压后不超过 50 MB；整个 zip 解压后聚合不超过 500 MB；zip 内总条目数不超过 5000。超出限制时上传会返回 HTTP 413。
    </Note>
  </Step>

  <Step title="配置上传参数">
    在「应用管理」-「源码管理」菜单切换到「微信小程序」标签页，点击「上传源码」。上传面板会根据表单内容生成命令。

    <ParamField path="API Key" type="string" required>
      用于认证上传请求。页面会优先展示当前账户可访问的 API Key。
    </ParamField>

    <ParamField path="服务名" type="string" required>
      小程序应用的服务名，例如 `my-mp`。异常解析时会使用该值与错误事件中的 `service` 匹配。
    </ParamField>

    <ParamField path="发布版本" type="string" required>
      小程序发布版本，例如 `1.2.3`。异常解析时会使用该值与错误事件中的版本匹配。
    </ParamField>

    <ParamField path="SourceMap ZIP 文件路径" type="string" required>
      `miniprogram-ci get-dev-source-map` 输出的压缩包路径，例如 `./sourcemap.zip`。
    </ParamField>

    <ParamField path="AppID" type="string">
      微信小程序 appid，例如 `wxbad3e0a65782821c`。多小程序场景下用于区分不同小程序的同名 service + version 上传记录；只有一个小程序时可省略。
    </ParamField>
  </Step>

  <Step title="执行上传">
    在项目根目录下运行生成的命令；`--appid` 为可选参数，未填写时上传命令中不会出现这一行：

    ```bash theme={null}
    FLASHCAT_API_KEY=your-api-key flashcat-cli sourcemaps upload-miniprogram \
      --service my-mp \
      --release-version 1.2.3 \
      --sourcemap-zip ./sourcemap.zip \
      --appid wxbad3e0a65782821c
    ```
  </Step>
</Steps>

## 上传 HarmonyOS 符号文件

HarmonyOS 崩溃栈可能同时包含 **ArkTS / JS 帧** 和 **Native `.so` 帧**。要在控制台中同时还原这两类堆栈，请上传以下构建产物：

* `sourceMaps.map`：ArkTS sourcemap 主文件
* `nameCache.json`：可选，用于还原混淆后的标识符名称
* 未 strip 的 Native `.so`：用于符号化 C/C++ 崩溃栈

控制台的 **应用管理 → 源码管理 → HarmonyOS** 上传面板会要求你填写 **Service**、**Version** 和 **API Key**，并生成对应的 `hvigor` 配置和上传命令。`service` 与 `version` 必须和应用实际上报的值保持一致，否则服务端无法匹配到对应符号文件。

<Steps>
  <Step title="安装 hvigor 插件">
    在 HarmonyOS 工程中安装上传插件：

    ```bash theme={null}
    npm install -D @flashcatcloud/hvigor-plugin
    ```
  </Step>

  <Step title="在 hvigorfile.ts 中注册上传插件">
    把控制台面板生成的 `service`、`version` 与 `apiKey` 配置写入 `hvigorfile.ts`：

    ```ts hvigorfile.ts theme={null}
    import { hapTasks } from '@ohos/hvigor-ohos-plugin';
    import { flashcatSymbolUploadPlugin } from '@flashcatcloud/hvigor-plugin';

    export default {
      system: hapTasks,
      plugins: [
        flashcatSymbolUploadPlugin({
          apiKey: process.env.FLASHCAT_API_KEY ?? '',
          service: 'my-app',
          version: '1.0.0',
          enabled: process.env.FLASHCAT_UPLOAD === '1'
        })
      ]
    };
    ```
  </Step>

  <Step title="构建后执行上传">
    在构建产物生成后执行上传任务：

    ```bash theme={null}
    FLASHCAT_UPLOAD=1 FLASHCAT_API_KEY=your-api-key \
      hvigorw uploadFlashcatSymbols --mode module -p module=entry@default -p product=default
    ```
  </Step>
</Steps>

<Warning>
  * Native `.so` 需要保留 GNU build-id；HarmonyOS NDK 默认开启，如你的构建链路关闭了它，请显式添加 `-Wl,--build-id`
  * 更完整的 HarmonyOS 接入、符号上传和兼容性说明，请继续阅读 [HarmonyOS SDK 高级配置](/zh/rum/sdk/harmony/advanced-config)
</Warning>

## 上传 Android 符号文件

Android 应用使用 ProGuard/R8 进行代码混淆后，错误堆栈中的类名和方法名会被替换为无意义的短名称。通过上传 mapping 文件，Flashduty 可以将混淆后的堆栈还原为原始代码。

对于包含 NDK 原生代码的应用，还需要上传 NDK 符号文件以还原 C/C++ 层的堆栈。

<Steps>
  <Step title="添加 Gradle 插件">
    在应用模块的 `build.gradle` 中添加 Flashcat Android Gradle 插件：

    ```groovy theme={null}
    plugins {
        id("cloud.flashcat.android-gradle-plugin") version "1.2.0"
    }
    ```

    Kotlin DSL 使用相同的插件 ID：

    ```kotlin theme={null}
    plugins {
        id("cloud.flashcat.android-gradle-plugin") version "1.2.0"
    }
    ```
  </Step>

  <Step title="配置 API Key">
    通过 Gradle 属性、环境变量或项目配置文件提供 API Key。发布流水线中通常使用环境变量：

    ```bash theme={null}
    # 二选一
    export FC_API_KEY=your-api-key
    export FLASHCAT_API_KEY=your-api-key

    # 可选：私有化部署时指定自定义上传入口
    export FLASHCAT_SOURCEMAP_INTAKE_URL=https://rum.example.com
    ```

    也可以在项目根目录创建 `flashcat-ci.json`：

    ```json theme={null}
    {
      "apiKey": "your-api-key",
      "flashcatSite": "ci.flashcat.cloud",
      "sourcemapEndpoint": "https://rum.example.com"
    }
    ```

    <Tip>
      API Key 可在控制台的「API Key 管理」页面创建和管理。
    </Tip>
  </Step>

  <Step title="配置上传参数">
    在应用模块的 `build.gradle` 中添加 `flashcat` 配置块。未显式配置时，插件会从 Android 构建配置读取 `versionName`、`versionCode` 和应用包名。

    ```groovy theme={null}
    flashcat {
        versionName = "1.3.0"              // 可选，默认读取 Android 配置中的 versionName
        serviceName = "my-service"         // 可选，默认使用应用包名
        site = "CN"                        // 可选，可选值：CN、STAGING，默认 CN
        sourcemapEndpoint = "https://rum.example.com" // 可选，私有化自定义上传入口；未带 /sourcemap/upload 时会自动补全
        checkProjectDependencies = "none"  // 可选：none、warn、fail；默认不检查
        mappingFilePath = "path/to/mapping.txt" // 可选，自定义 mapping 文件路径
        nonDefaultObfuscation = false      // 可选，使用 DexGuard 等非默认混淆工具时设为 true
        ignoreFlashcatCiFileConfig = false // 可选，是否忽略 flashcat-ci.json
        additionalSymbolFilesLocations = ["/path/to/location/obj"] // 可选，额外 NDK 符号目录
    }
    ```

    <Note>
      默认情况下插件会根据 `site` 选择上传入口：`CN` 对应 `ci.flashcat.cloud`，`STAGING` 对应 `ci-dev.flashcat.cloud`。私有化部署可通过 `sourcemapEndpoint` 指定自定义上传入口（自 `1.2.0` 起支持）；该值未带 `/sourcemap/upload` 路径时会自动补全。三种配置方式的优先级为：`flashcat {}` 扩展配置 > `flashcat-ci.json` > `FLASHCAT_SOURCEMAP_INTAKE_URL` 环境变量。配置自定义入口后，`site` 仅用于回退，不再影响实际上传地址。
    </Note>
  </Step>

  <Step title="运行上传任务">
    在构建完成后，执行 Gradle 任务上传符号文件：

    ```bash theme={null}
    # 上传 ProGuard/R8 mapping 文件
    ./gradlew uploadMappingRelease

    # 如果项目包含 NDK 原生代码，上传 NDK 符号文件
    ./gradlew uploadNdkSymbolFilesRelease
    ```

    <Note>
      插件会为启用混淆的 variant 创建 `uploadMapping<Variant>` 任务。NDK 符号上传任务会在项目存在 native build，或配置了 `additionalSymbolFilesLocations` 时创建。若项目使用多个 flavor，请为每个发布 variant 分别执行对应任务。
    </Note>

    <Warning>
      **NDK 符号文件必须是未剥离（unstripped）的 ELF 文件，且包含 GNU build-id 节。**

      上传服务器会读取每个 `.so` 文件的 `.note.gnu.build-id` ELF 节以提取 build-id，并以此作为符号化寻址的唯一键。若文件缺少该节，上传立即返回 HTTP 400，错误信息为：

      ```
      libfoo.so: .note.gnu.build-id section not found. Build the .so with -Wl,--build-id and upload the unstripped file
      ```

      **常见原因及修复：**

      * **CMake Release 构建默认剥离符号**：CMake 在 `CMAKE_BUILD_TYPE=Release` 时会在链接后执行 `strip`，移除 `.note.gnu.build-id` 节。需在 `CMakeLists.txt` 中显式添加链接标志：

        ```cmake theme={null}
        target_link_options(mylib PRIVATE -Wl,--build-id)
        ```

        或对整个项目生效：

        ```cmake theme={null}
        add_link_options(-Wl,--build-id)
        ```

      * **手动 strip 脚本**：如果 CI 流水线在上传前对 `.so` 运行了 `strip` 命令，需确保上传的是 `obj/` 目录下的原始未剥离文件，而不是 `libs/` 目录下已剥离的发布产物。`additionalSymbolFilesLocations` 参数用于指定此路径。

      NDK 原生符号文件在 API 中以 `event.type=ndk_symbol_file` 标识，区别于 ProGuard mapping 文件的 `event.type=jvm_mapping_file`。自定义上传集成（不使用 Gradle 插件）时需传入正确的类型字段。
    </Warning>
  </Step>
</Steps>

## 上传 iOS dSYM 文件

iOS 应用在编译时会生成 dSYM（Debug Symbol）文件，其中包含将内存地址映射回源代码位置所需的调试符号信息。上传 dSYM 文件后，Flashduty 可以将崩溃堆栈中的地址还原为可读的函数名、文件名和行号。

<Steps>
  <Step title="安装 Flashduty CLI">
    确保已安装 Node.js，然后通过 npm 安装：

    ```bash theme={null}
    npm install -g @flashcatcloud/flashcat-cli
    ```
  </Step>

  <Step title="获取 dSYM 文件">
    dSYM 文件可从以下位置获取：

    * **Xcode 本地构建**：在 Xcode 的 Build Products 目录中找到 `.dSYM` 文件
    * **Xcode Archive**：通过 Organizer 窗口导出 dSYMs
    * **App Store Connect**：从 App Store Connect 下载 dSYMs（需要在构建设置中启用符号上传）
  </Step>

  <Step title="执行上传">
    使用 Flashduty CLI 上传 dSYM 文件：

    ```bash theme={null}
    FLASHCAT_API_KEY=your-api-key flashcat-cli dsyms upload ./app.dSYM
    ```

    <Tip>
      您也可以在控制台的「源码管理」面板中，填写参数后自动生成上传命令。
    </Tip>
  </Step>
</Steps>

## 符号文件管理

在 Flashduty 平台上，符号文件的管理通过「应用管理」-「源码管理」菜单完成：

| 功能         | 说明                                                                                                                                                                                 |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 查看已上传的符号文件 | 列出所有已上传的文件（SourceMap、小程序 SourceMap、ProGuard mapping 文件、dSYM、NDK 原生符号文件），包括路径、服务名、版本号、大小和上传时间。Android 标签页同时展示 ProGuard mapping 文件和 NDK 原生符号文件，NDK 行会显示 build\_id、arch 和 lib\_name 列 |
| 按平台筛选      | 在 Web、微信小程序、iOS 和 Android 标签页之间切换，查看不同平台的符号文件                                                                                                                                      |
| 版本管理       | 通过 `service` 和 `release-version` 参数为不同版本的应用分别管理                                                                                                                                    |
| 小程序维度      | 微信小程序列表会展示符号文件元数据中的 AppID（取自 `metadata.appid`）和分包（取自 `metadata.subpackage`）两列；主包没有分包标识时显示「主包」，AppID 未上传时显示 `-`                                                                     |
| 权限控制       | 通过 `API Key` 确保只有授权用户可以上传或管理                                                                                                                                                       |

## 在异常追踪中查看源码

RUM 异常追踪支持结合 Web `sourcemap`、微信小程序 SourceMap、Android mapping 文件和 iOS dSYM 还原错误堆栈，在异常详情中查看原始源码位置，精确定位问题。

<Steps>
  <Step title="捕获错误">
    RUM SDK 会自动捕获应用错误，并将错误堆栈信息发送至服务器。Web 场景通常包括 JavaScript 异常、Promise 拒绝和网络错误；Native 场景则包括崩溃、异常和符号化所需的堆栈信息。

    ```javascript theme={null}
    throw new Error("Something went wrong");
    ```
  </Step>

  <Step title="关联符号文件">
    当错误堆栈中的文件路径、行号或地址信息与已上传的 Web `sourcemap`、微信小程序 SourceMap、Android ProGuard mapping 文件、Android NDK 原生符号文件或 iOS dSYM 文件匹配时，Flashduty 会自动将压缩、混淆或编译后的错误位置映射到原始源代码。

    **压缩文件堆栈：**

    ```
    Error: Something went wrong
        at Object.<anonymous> (/assets/index-5e0391ac.js:1:123)
    ```

    **映射后的源代码：**

    ```
    Error: Something went wrong
        at App.render (src/components/App.js:45:10)
    ```
  </Step>

  <Step title="查看异常详情">
    在异常追踪模块中，点击具体的错误记录，可以查看：

    * **错误消息**：如 `Something went wrong`
    * **原始堆栈**：映射后的源代码文件路径、行号和列号
    * **上下文代码**：显示错误位置附近的源代码片段

    如果小程序错误堆栈尚未解析，异常详情中的提示会跳转到「源码管理」的小程序类型，并可直接打开上传面板补充对应版本的 `sourcemap.zip`。
  </Step>

  <Step title="调试与修复">
    根据映射后的源代码位置，直接在本地开发环境中找到对应代码，分析问题根因并修复。
  </Step>
</Steps>

## 最佳实践

<AccordionGroup>
  <Accordion title="规范化 SourceMap 上传">
    在 CI/CD 流水线中集成上传命令，确保每次发布时自动上传 Web `sourcemap` 或微信小程序 `sourcemap.zip`。

    **GitHub Actions 示例：**

    ```yaml theme={null}
    - name: Upload SourceMaps
      run: |
        flashcat-cli sourcemaps upload \
          --service my-service \
          --release-version ${{ github.sha }} \
          --minified-path-prefix /assets \
          --api-key ${{ secrets.FLASHCAT_API_KEY }} \
          ./dist
    ```
  </Accordion>

  <Accordion title="版本管理">
    使用 `--release-version` 参数与应用版本号保持一致，便于追踪特定版本的 `sourcemap`。
  </Accordion>

  <Accordion title="清理源码">
    在资源上传 CDN 之前删除 `sourcemap` 文件，避免将源码信息带入生产环境。
  </Accordion>

  <Accordion title="测试映射效果">
    上传 `sourcemap` 后，主动抛出测试错误，验证异常追踪模块是否能正确映射到源代码。
  </Accordion>
</AccordionGroup>

## 常见问题

<AccordionGroup>
  <Accordion title="为什么异常堆栈没有映射到源代码？">
    * 确认 `sourcemap` 是否成功上传，且 `minified-path-prefix` 与实际部署路径一致
    * 检查 `service` 和 `release-version` 是否与错误发生时的应用版本匹配
    * 如果是微信小程序，确认已上传对应版本的 `sourcemap.zip`
  </Accordion>

  <Accordion title="如何避免 SourceMap 泄露敏感信息？">
    * 确保 `sourcemap` 文件仅上传至 Flashduty 服务器，不直接暴露在公网
    * 在生产环境中，移除对 `sourcemap` 文件的直接访问（如通过 Nginx 配置）
  </Accordion>

  <Accordion title="上传 SourceMap 失败怎么办？">
    * 检查 `API Key` 是否有效
    * 确保网络连接正常，CLI 版本是最新的
  </Accordion>

  <Accordion title="小程序错误堆栈为空，但 message 中带有堆栈，能否参与 SourceMap 还原？">
    可以。对于早期版本 SDK 把堆栈塞进 message 字段的小程序错误，Flashduty 会自动从 message 中识别堆栈并参与 SourceMap 还原与异常分组。升级到新版 SDK 后，已存在的历史错误也会按这一规则重新归类，可能与升级前的分组不完全一致。
  </Accordion>
</AccordionGroup>

## 下一步

<CardGroup cols={2}>
  <Card title="异常聚合" icon="layer-group" href="./error-aggregation">
    了解异常聚合机制
  </Card>

  <Card title="Issue 状态" icon="circle-check" href="./issue-status">
    管理 Issue 状态流转
  </Card>
</CardGroup>
