前端监控之sourcemap精准定位和还原错误源码

devtools/2024/12/25 13:32:26/

一、概述

前端开发中,监控和错误追踪是确保应用稳定性和用户体验的重要环节。

随着前端应用的复杂性增加,JavaScript错误监控变得尤为重要。在生产环境中,为了优化加载速度和性能,前端代码通常会被压缩和混淆。这虽然提升了性能,但也给错误追踪带来了挑战,因为错误报告中显示的代码位置和实际代码不一致,使得开发者难以定位和修复问题。

Sourcemap技术应运而生,它是一种将压缩后的代码映射回原始源代码的工具。通过sourcemap,开发者可以准确地从错误报告中找到原始代码中的问题所在,即使这些代码已经被压缩或混淆。这种映射关系使得错误追踪变得更加精确和高效。

在这篇文章中,将探讨如何在前端项目中集成sourcemap,以及如何利用sourcemap进行错误监控和代码还原。我将介绍sourcemap的生成、部署和使用,以及它在前端监控中的最佳实践。通过这些内容,同学们将能够理解sourcemap在前端错误监控中的重要性,并学会如何利用这一工具提高开发效率和应用质量。

二、实践

通过sourcemap技术实现对前端代码中错误位置的精确定位和还原。

2.1、Sourcemap结构

Sourcemap文件通常包含以下字段:

  • version:Sourcemap的版本,目前常用的是版本3。
  • file:转换后的文件名。
  • sourceRoot:源文件的根目录路径,用于重新定位源文件。
  • sources:源文件列表,可能包含多个文件。
  • sourcesContent:源文件的内容列表(可选)。
  • names:转换前的变量名和属性名列表。
  • mappings:一个编码的字符串,记录了源代码和转换后代码之间的详细映射关系。

2.2、初始化项目

使用我自己实现的前端action-cli脚手架,从自己GitHub仓库拉取模板并初始化一个Vite+Vue3+TS项目,安装依赖运行项目。

更多关于action-cli可以查看这两篇文章

实现一个自定义前端脚手架_前端自定义脚手架-CSDN博客

重构Action-cli前端脚手架-CSDN博客

添加下面两个路由

const routes=[{path: '/errorView',name: 'ErrorView',component: () => import('@/views/sourcemap/ErrorView.vue'),meta: {title: '生产错误'}},{path: '/errorList',name: 'ErrorList',component: () => import('@/views/sourcemap/ErrorList.vue'),meta: {title: '错误列表'}}
];// https://github.com/Topskys/admin-pro/tree/monitor

2.3、生产Js错误

创建./views/sourcemap/ErrorView.vue页面,通过throw模拟抛出Js代码错误。

// ./views/sourcemap/ErrorView.vue
<template><div><h1>Error</h1><button @click="triggerTypeError()">触发TypeError</button><button @click="triggerReferenceError">触发ReferenceError</button><button @click="triggerSyntaxError">触发SyntaxError</button></div>
</template>
<script setup lang="ts">
const triggerTypeError = () => {throw new TypeError('This is a type error');
};const triggerReferenceError = () => {throw new ReferenceError('This is a reference error');
};const triggerSyntaxError = () => {throw new SyntaxError('This is a syntax error');
};
</script>

配置vite.config.ts,将该页面打包成单个文件,并且需要开启sourcemap,方便测试。

// ...
build: {sourcemap: true,rollupOptions: {input: {index: fileURLToPath(new URL('./index.html', import.meta.url))},output: {// 将依赖单独打包manualChunks: (id: string) => {if (id.includes('node_modules')) {return 'vendor';}if (id.includes('src/views/sourcemap/ErrorView')) {return 'errorView';}return 'index';},}}
},

2.4、捕捉Js错误

这里需要安装两个依赖包,分别用于解析错误和解析sourcemap文件。

pnpm i error-stack-parser source-map-js

Vue3提供了一个捕捉全局错误的回调函数app.config.errorHandler,去main.ts实现这个方法的回调函数。

// main.ts
// ...// 捕捉错误
app.config.errorHandler = (err: any, vm, info) => {console.log('err', err, '
vm', vm, '
info', info);const errorStack = ErrorStackParser.parse(err as Error);console.log('errorStack', errorStack);
}

点击触发TypeError,errorHandler函数就会捕捉到错误,并打印出错误和error-stack-parser处理err后的效果

把错误信息存储到localStorage,方便其他页面展示错误列表信息。

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import ErrorStackParser from 'error-stack-parser';const app = createApp(App);
app.use(router);// 捕捉错误
app.config.errorHandler = (err: any, vm, info) => {const errorStack = ErrorStackParser.parse(err as Error);const jsError = {stack_frames: errorStack,message: err.message,stack: err.stack,error_name: err.name};console.error(`触发一个${err.name}错误`);localStorage.setItem('jsErrorList', JSON.stringify(jsError));
}app.mount('#app');

2.5、展示错误列表

新建./views/sourcemap/ErrorList.vue错误列表页面,用于展示错误信息和映射到源码操作。

<template><div v-if="isErr"><pre>{{ js_error.stack }}</pre><el-collapse v-model="activeNames" @change="handleChange"><el-collapse-item v-for="(item, index) in js_error.stack_frames" :key="index" :title="item.source" :name="index"><el-row :gutter="20"><el-col :span="20">{{ item.fileName }}</el-col><el-col :span="4"><el-button type="primary" size="small" @click="openDialog(item, index)">映射源码</el-button></el-col></el-row><el-row :gutter="20"><template v-if="item.originalSource">{{ item.originalSource }}<!-- <Preview :origin="item.originalSource" /> --></template><template v-else>{{ item.functionName }}</template></el-row></el-collapse-item></el-collapse><el-dialog v-model="dialogVisible" title="SourceMap源码映射" width="500"><el-tabs v-model="tabActiveName"><el-tab-pane label="本地上传" name="local"><el-upload drag :before-upload="sourcemapUpload"><i class="el-icon-upload"></i><div>将文件拖拽到此处,或者<em>点击上传</em></div></el-upload></el-tab-pane><el-tab-pane label="远程加载" name="remote"> </el-tab-pane></el-tabs></el-dialog></div>
</template>
<script setup lang="ts">
// https://github.com/Topskys/admin-pro/tree/monitor/src/views/sourcemap
</script>

从localStorage获取错误数据初始化列表

const getErrorList = () => {
? try {
? ? const errorString = localStorage.getItem('jsErrorList');
? ? if (!errorString) return;
? ? js_error.value = JSON.parse(errorString);
? ? isErr.value = true;
? } catch (error: any) {
? ? console.log('获取错误列表失败', error);
? }
};onMounted(() => {
? getErrorList();
});

弹窗用于上传本地打包后的sourcemap文件。

// 打开弹窗
const openDialog = (item: any, index: number) => {
? dialogVisible.value = true;
? stackFrameObj = {
? ? line: item.lineNumber, // 错误代码行号
? ? column: item.columnNumber, // 错误代码列号
? ? index: index // 列表需序号
? };
};

错误列表

处理sourcemap上传事件和解析

const sourcemapUpload = async (file: any) => {
? if (file.name.substring(file.name.lastIndexOf('.') + 1) !== 'map') {
? ? ElMessage({
? ? ? message: '请上传正确的sourcemap文件',
? ? ? type: 'error'
? ? });
? ? return;
? }
? const reader = new FileReader();
? reader.readAsText(file);
? reader.onload = async function (e: any) {
? ? const code = await getSource(e?.target?.result, stackFrameObj.line, stackFrameObj.column);
? ? js_error.value.stack_frames[stackFrameObj.index].originalSource = code;
? ? dialogVisible.value = false;
? };
? return false; // 不写这一行会报错
};

获取报错ErrorView.vue页面的sourcemap文件,一般真实项目会把打包后的源码和sourcemap文件分别部署到不同的服务器,来提升安全性。 注意在本地不能还原,上传sourcemap后会报错404,因此需要把项目部署到github 静态资源服务器。

// 请求并解析sourcemap文件
const getSource = async (sourcemap: any, line: number, column: number) => {
? try {
? ? const consumer = await new sourceMap.SourceMapConsumer(JSON.parse(sourcemap));
? ? const originalPosition = consumer.originalPositionFor({ line, column });
? ? const source = consumer.sourceContentFor(originalPosition.source);
? ? // console.log('本地报错404source', source);
? ? return {
? ? ? source: source,
? ? ? line: originalPosition.line,
? ? ? column: originalPosition.column
? ? };
? } catch (e) {
? ? ElMessage.error('sourcemap解析失败');
? }
};

2.6、错误源代码展示

新建./views/sourcemap/Preview.vue组件,用于处理和展示还原后的源代码。

<template><div class="pre-code"><div class="error-detail"><pre class="error-code" v-html="preLine()"></pre></div></div>
</template>
// js部分可参考github仓库monitor分支
// https://github.com/Topskys/admin-pro/tree/monitor

2.7、构建CI/CD

基于github actions构建ci/cd流水线,这里就不详细赘述了,不知道的同学可以阅读这篇文章基于Github Actions实现前端CI/CD持续集成与部署_github cicd-CSDN博客

把触发流水线的分支改成monitor分支,因为当前是在monitor发开并测试sourcemap映射源代码。

三、效果

3.1、触发ci/cd

运行打包命令

提交代码到monitor分支触发流水线自动化部署。

3.2、测试还原

测试sourcemap还原错误源代码,访问https://topskys.github.io/admin-pro/#/生产错误页面,点击触发错误按钮触发错误。

打开错误列表页面,可以看到错误信息生成。展开列表第一个错误,点击映射源码。

选择上传errorView.vue报错页面对应的map文件(真正项目线上环境从其它服务器获取map文件),以解析出源代码。

上传之后,就能看到错误列表中已经还原出精准的行错误源代码(标红行)了,行号和源代码都一致。

具体代码实现过程可前往github仓库的monitor分支查看

admin-pro/src/views/sourcemap at monitor · Topskys/admin-pro[这里是图片012]https://github.com/Topskys/admin-pro/tree/monitor/src/views/sourcemap

四、参考

基于Github Actions实现前端CI/CD持续集成与部署_github cicd-CSDN博客

重构Action-cli前端脚手架-CSDN博客

实现一个自定义前端脚手架_前端自定义脚手架-CSDN博客


http://www.ppmy.cn/devtools/145270.html

相关文章

力扣-图论-18【算法学习day.68】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

14_HTML5 input类型 --[HTML5 API 学习之旅]

HTML5 引入了许多新的 <input> 类型&#xff0c;这些类型提供了更专业的数据输入控件&#xff0c;并且可以在支持的浏览器中提供更好的用户体验和输入验证。以下是一些 HTML5 中引入的 <input> 类型&#xff1a; 1.color: 打开颜色选择器&#xff0c;允许用户选择…

相机雷达外参标定综述“Automatic targetless LiDAR–camera calibration: a survey“

相机雷达外参标定综述--Automatic targetless LiDAR–camera calibration: a survey 前言1 Introduction2 Background3 Automatic targetless LiDAR–camera calibration3.1 Information theory based method(信息论方法)3.1.1 Pairs of point cloud and image attributes(属性…

APHAL平台 一二三章

1.类和对象的关系是 抽象和 具体 的关系。类是创建对象的具体&#xff0c;对象是类的实例。 2.Java应用程序的主类必须是public类。 错&#xff0c;文件名是public类如 public class HAPPY{ } 那么文件名为HAPPY.java 3.一个文件可以有多个类&#xff0c;但是值可以有一个…

《Cocos Creator游戏实战》非固定摇杆实现原理

为什么要使用非固定摇杆 许多同学在开发摇杆功能时&#xff0c;会将摇杆固定在屏幕左下某一位置&#xff0c;不会让其随着大拇指触摸点改变&#xff0c;而且玩家只有按在了摇杆上才能移动人物&#xff08;触摸监听事件在摇杆精灵上)。然而&#xff0c;不同玩家的大拇指长度不同…

力扣238. 除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…

YOLOv9-0.1部分代码阅读笔记-dataloaders.py

dataloaders.py utils\dataloaders.py 目录 dataloaders.py 1.所需的库和模块 2.def get_hash(paths): 3.def exif_size(img): 4.def exif_transpose(image): 5.def seed_worker(worker_id): 6.def create_dataloader(path, imgsz, batch_size, stride, single_cl…

Apache RocketMQ 5.1.3安装部署文档

官方文档不好使&#xff0c;可以说是一坨… 关键词&#xff1a;Apache RocketMQ 5.0 JDK 17 废话少说&#xff0c;开整。 1.版本 官网地址&#xff0c;版本如下。 https://rocketmq.apache.org/download2.配置文件 2.1namesrv端口 在ROCKETMQ_HOME/conf下 新增namesrv.pro…