前端监控系统 SourceMap 自动化解析方案:从手动上传到智能解析

一步一个脚印一个坑 11小时前 ⋅ 0 阅读
ad

     在前端监控系统中,SourceMap 解析是定位线上代码错误的核心功能。然而,传统的手动上传 SourceMap 文件的方式存在诸多问题。因为涉及到生成,管理,上传等多个步骤,只要有一步对不上,基本上很难解析出来,所以我们增加webpack和vite自动上传sourceMap的插件,在打包的时候上传map文件,查看的时候精准锁定源文件,省时省力。

 

一、整体架构设计

我们设计了一套自动化的 SourceMap 管理和解析方案,包含三个核心组件:

核心优势

✅ **自动化**:集成到 Webpack 构建流程,无需人工干预
✅ **集中管理**:所有项目的 SourceMap 统一存储在 ClickHouse
✅ **高压缩**:Gzip + Base64 编码,压缩率约 70%
✅ **版本控制**:按项目和版本组织,支持多版本共存
✅ **分布式友好**:基于 ClickHouse,天然支持分布式部署
✅ **降级方案**:file_server 不可用时自动降级到本地文件
✅ **智能解析**:显示 100 行上下文,支持滚动查看
 

二、文件服务设计方案

传统的sourceMap解析方案,是需要用户去维护map文件版本,想办法把错误代码和源文件对齐,说实话,这一步真的十分繁琐。为了能够解决这一痛点,打造一个自动化的解析方案,我们需要在整个系统中内容一个文件系统服务,用于上报和下载map文件

2.1 数据库表设计

// file_server_clickhouse/schema/sourceMapFile.js
const Columns = {
  tableName: 'SourceMapFile',
  structure: {
    id: {
      type: DataTypes.UUID,
      field: 'id'
    },
    project_id: {
      type: DataTypes.STRING,
      allowNull: false,
      field: 'project_id'
    },
    project_name: {
      type: DataTypes.STRING,
      field: 'project_name'
    },
    file_name: {
      type: DataTypes.STRING,
      allowNull: false,
      field: 'file_name'
    },
    release: {
      type: DataTypes.STRING,
      allowNull: false,
      field: 'release'
    },
    file_content: {
      type: DataTypes.STRING, // Base64 + Gzip 压缩
      allowNull: false,
      field: 'file_content'
    },
    file_size: {
      type: DataTypes.INT(32),
      field: 'file_size'
    },
    compressed_size: {
      type: DataTypes.INT(32),
      field: 'compressed_size'
    },
    upload_time: {
      type: DataTypes.DATE_TIME,
      field: "upload_time"
    },
    createdAt: {
      type: DataTypes.DATE_TIME,
      field: "createdAt"
    }
  },
  engine: "ENGINE MergeTree()",
  partition: "PARTITION BY toYYYYMM(createdAt)",
  orderBy: "ORDER BY (project_id, release, file_name, createdAt)"
}

2.2 文件压缩策略

// 上传时压缩
const fileContent = fs.readFileSync(filePath, 'utf-8')
const compressedBuffer = zlib.gzipSync(Buffer.from(fileContent))
const base64Content = compressedBuffer.toString('base64')

// 存储到 ClickHouse
await SourceMapFile.create({
  file_content: base64Content,
  file_size: fileContent.length,
  compressed_size: compressedBuffer.length
})

// 查询时解压
const compressed = Buffer.from(file.file_content, 'base64')
const decompressed = zlib.gunzipSync(compressed)
const fileContent = decompressed.toString('utf-8')​
**压缩效果**:平均压缩率约 70%(例如:46KB → 13.6KB)

三、Webpack 插件实现

3.1 webfunny-webpack-plugin - 自动上传工具实现逻辑

// webfunny-webpack-plugin/src/WebfunnyWebpackPlugin.js
class WebfunnyWebpackPlugin {
  constructor(options = {}) {
    this.options = {
      url: options.url,
      projectId: options.projectId,
      release: options.release,
      projectName: options.projectName || '',
      urlPrefix: options.urlPrefix || '',
      deleteAfterUpload: options.deleteAfterUpload !== false,
      silent: options.silent || false
    };
  }

  apply(compiler) {
    // 在文件输出后执行
    compiler.hooks.afterEmit.tapPromise('WebfunnyWebpackPlugin', async (compilation) => {
      const sourceMapFiles = this.getSourceMapFiles(compilation);
      
      for (const file of sourceMapFiles) {
        await this.uploadFile(file);
        
        // 上传成功后删除 .map 文件(可选)
        if (this.options.deleteAfterUpload) {
          fs.unlinkSync(file.path);
        }
      }
    });
  }
}
 

3.2 webpack.config.js 插件配置示例

const WebfunnyWebpackPlugin = require('webfunny-webpack-plugin');
const pkg = require('./package.json');

module.exports = {
  mode: 'production',
  devtool: 'source-map', // 必须开启
  
  plugins: [
    new WebfunnyWebpackPlugin({
      url: 'http://localhost:8033/wfFile/api/sourceMapFile/upload',
      projectId: 'webfunny_20251209_022537_pro', // ⚠️ 必须与前端SDK的webMonitorId一致
      release: `${pkg.version}-${Date.now()}`,    // 自动生成版本号
      projectName: pkg.name,
      deleteAfterUpload: process.env.NODE_ENV === 'production' // 生产环境删除
    })
  ]
};
 

3.3 自动版本号生成

// 格式:{version}-{YYYYMMDD}-{HHmm}
// 例如:1.0.0-20251209-0846

const getVersion = () => {
  if (process.env.APP_VERSION) {
    return process.env.APP_VERSION;
  }
  
  const now = new Date();
  const date = now.toISOString().slice(0, 10).replace(/-/g, '');
  const time = now.toISOString().slice(11, 16).replace(/:/g, '');
  return `${pkg.version}-${date}-${time}`;
};
 
 
 
 
 
 
 
 
 
三、
 

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 0   收藏 0
  • 一步一个脚印一个坑
    共发布134篇文章 获得4个收藏
全部评论: 0