souce-map-js + Vue 还原生产环境报错-实战

前言

上一篇文章讲了怎么捕获生产环境的错误,以及如何还原。

这一次呢给大家带怎么把捕获到的bug 渲染到页面。

souce-map-js + Vue 还原生成环境报错,让JS报错无所遁形

开工

install element-ui

修改下main.js

……
Vue.config.errorHandler = async (err, vm) => {
  const jsError = {
    stack_frames: ErrorStackParser.parse(err),
    message: err.message,
    stack: err.stack,
    error_name: err.name
  }
  vm.$message.error(`您触发了一个${err.name} 错误`);
  localStorage.setItem("jsErrorList", JSON.stringify(jsError))  
}
……
复制代码

考虑到一些朋友可能不会后端,索性就将报错存储本地。(其实懒……不想写……)

views目录下新建一个 Trigger.vue

<template>
  <div class="about">
    <p @click="triggerTypeError()">触发TypeError</p>
    <el-divider></el-divider>
    <p @click="triggerReferenceError()">触发ReferenceError</p>
    <el-divider></el-divider>
    <p @click="triggerSyntaxError()">触发SyntaxError</p>
  </div>
</template>
<script>
export default {
  name: "ErrorTrigger",
  methods: {
    triggerTypeError() {
        if (this.typeError.length > 0) {
            console.log("err!");
        }
    },
    triggerReferenceError() {
        throw new ReferenceError('Hello', 'someFile.js', 10);
    },
    triggerSyntaxError () {
        throw new SyntaxError('Hello', 'someFile.js', 10);
    }
  }
}
</script>
复制代码

用来触发js报错,这里触发报错的时候会被main.js拦截到,并存储至本地。

App.vue 一个用来查看一个用来触发js报错。

  <div id="app">
    <el-row>
      <el-button @click="routerPush('/')">
          查看Js异常
      </el-button>
      <el-button type="primary"  @click="routerPush('/trigger')">
          触发Js异常
      </el-button>
    </el-row>
    <el-divider></el-divider>
    <router-view/>
  </div>
复制代码

再来修改Home.vue


  <div v-if="js_error">
      <pre>
        {{js_error.stack}}
      </pre>
      <el-collapse v-model="activeName" accordion>
        <el-collapse-item v-for="(item, index) in js_error.stack_frames" :key="index" :title="substrSourceTitle(item.source)" :name="index">
          <el-row :gutter="20">
            <el-col :span="20">
                <div >{{item.fileName}}</div>
            </el-col>
            <el-col :span="4">
              <el-button size="small" type="primary" @click="oepnDialog(item, index)">映射源码</el-button>
            </el-col>
          </el-row>
        </el-collapse-item>
      </el-collapse>
 </div>
……
created() {
  try {
    const js_error = localStorage.getItem("jsErrorList");
    if (js_error) {
      this.js_error = JSON.parse(js_error)
      console.log(this.js_error);
    }
  } catch(err) {
    console.log(err);
  }
},
methods: {
    substrSourceTitle(str) {   // 修剪下标题……不然太长了。不用担心,在生产环境不会那么长……
        return  str.substr(0, 100) + '...'
    },
}
……
复制代码

看下效果……还不错。

image.png

还原报错

报错还原这个其实跟上一篇文章差不多,我这里做了一些调整。

Home.vue

...
  <el-dialog
      title="sourceMap映射"
      :visible.sync="visible"
      width="500px"
      :before-close="handleClose">
      <el-tabs v-model="activeTabName">
        <el-tab-pane label="本地上传" name="local">
          <el-upload
            style="width: 100%"
            class="upload-demo"
            drag
            action=""
            :before-upload="sourceMapUpload">
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
          </el-upload>
        </el-tab-pane>
        <el-tab-pane label="远程加载" name="request">远程加载</el-tab-pane>
    </el-tabs>
  </el-dialog>
...
    methods: {
      oepnDialog(stackFrame, index) {
        this.stackFrame = {
          line: stackFrame.lineNumber,
          cloum: stackFrame.columnNumber,
          index: index,
        };
        this.visible = true;
      },
      substrSourceTitle(str) {
        return  str.substr(0, 100) + '...'
      },
      handleClose(done) {
        done();
      },
      sourceMapUpload(file) {
        if (file.name.substring(file.name.lastIndexOf('.') + 1) !== 'map') {
          this.$message.error(`请上传.js.map 文件!`);
          return
        }
        const reader = new FileReader()
        reader.readAsText(file, 'UTF-8')
        reader.onload = event => {
          const look_source = this.lookSource(event.target.result, this.stackFrame.line, this.stackFrame.cloum)
          this.js_error.stack_frames[this.stackFrame.index].origin = look_source

          this.visible = false;
        }
        return false
      },
      lookSource(source_map, line, column)  {
        try {
          const consumer = new sourceMap.SourceMapConsumer(source_map)
          const lookUpRes = consumer.originalPositionFor({
            line: line,
            column: column
          })
          const source = consumer.sourceContentFor(lookUpRes.source)
          return {
            source,
            column: lookUpRes.column,
            line: lookUpRes.line
          }
        } catch(e) {
          this.$message.error(`未能解析出sourceMap!`);
          console.log(e);
          return null
        }
      }
   }
复制代码

这样就可以弹出个Dialog 上传.js.map文件,我们通过解析这个 .js.map文件获取到源码。

image.png

渲染报错信息

源码解析出来了,那么报错的信息怎么渲染?

新建组件 PreCode.vue

<template>
  <div class="pre_code">
    <div class="errdetail">
      <pre class="errCode" v-html="preLineStartEnd()"></pre>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PreCode',
  props: {
    orgin: Object,
  },
  methods: {
    preLineStartEnd() {
      // 先获取源码有多少行
      const transformationLine = this.orgin.source.split('\n')
      const len = transformationLine.length - 1
      const start = this.orgin.line - 3 >= 0 ? this.orgin.line - 3 : 0
      const end = start + 5 >= len ? len : start + 5 // 最多展示6行
      const line = this.orgin.line
      let newLines = [];
      for(var i = start; i <= end; i++) {
         const content = i + 1 + '.    ' + this.encodeHTML(transformationLine[i])
        newLines.push(`<div class='code-line ${i + 1 == line ? 'heightlight' : ''}'>${content}</div>`);
      }
      return newLines.join("")
    },
    encodeHTML(str) {
      if (!str || str.length == 0) return ''
      return str.replace(/&/g, '&#38;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/'/g, '&#39;')
    }
  }
}
</script>

<style>

.errCode {
  padding: 10px;
  overflow: hidden;
  font-family: consolas,monospace;
  word-wrap: normal;
}
.code-line {
  padding: 4px;
}

.heightlight {
  color: #fff;
  background-color: #6c5fc7;
}
</style>

复制代码

Home.vue使用组件

...
<el-col :span="20">
  <template v-if="item.origin">
    <PreCode v-if="item.origin" :orgin="item.origin"></PreCode>
  </template>
  <template v-if="!item.origin">
    <div >{{item.fileName}}</div>
  </template>
</el-col>
...
import PreCode from "../components/PreCode"
...
components: {
  PreCode
}, 
复制代码

image.png

解析成功!

完结

大概花了俩月的业余时间从前端到后端写了一个监控系统,从中收获还是挺多的。

因为报表非常的多,报表数据用orm也不知道怎么查,索性我全部用sql来写,也是加强下sql这块弱点。

对于go的理解更加深了不少,没有泛型,error机制也非常恶心……但好在简单易懂,很轻松就能上手。满推荐大家去尝试下的。

监控系统我最满意的应该是 source-map-js解析异常和 用户行为路径, 可能是图标多显得好看……

image.png

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享