关于前端脚本异常监控的思考

栏目: 编程语言 · 发布时间: 5年前

内容简介:这里讲的是如何高效合理的捕捉与定位问题,不涉及 pv、uv、埋点之类的业务监控首先我们要明白一点,前端如何捕获错误,在代码中我们可以经常使用所以

这里讲的是如何高效合理的捕捉与定位问题,不涉及 pv、uv、埋点之类的业务监控

首先我们要明白一点,前端如何捕获错误,在代码中我们可以经常使用 try...catch 来捕获错误,但是 try...catch 无法捕获语法错误和异步错误,如下

关于前端脚本异常监控的思考
关于前端脚本异常监控的思考

所以 try...catch 不适合做全局的异常监听,当然对于已知的可能会发生的错误,这个时候主动上报还是有用的

这个时候我们要想到在 window 上监听 error 事件,监听 error 事件可以返回相应的错误信息、脚本的url、行号、列号、error对象,如下(具体可参考 developer.mozilla.org/zh-CN/docs/… ):

window.onerror = (message, source, line, column, error) => {
     console.log(message)
     console.log(source)
     console.log(line)
     console.log(column)
     console.log(error)
}

const a = { b: 2 }

console.log(a.c.length + 1)
复制代码
关于前端脚本异常监控的思考

接下来就是上报异常信息了,这里当然可以通过 ajax 上报,这是肯定不会错的,不过如果你细心点看下自己公司的上报页面数据,包括监控我觉得大部分都会使用动态创建 img 标签来上报的,这样的好处在于不用处理跨域的问题,如下

const img = new Image()
img.url = `http://minitor.example.com?message=${msg}`
复制代码

关于 sourcemap

现在的前端基本上都会使用 webpack 打包js,按照上方的这种简单的上报,上报的都是压缩后的代码,那么即使线上有错误看到这样的消息基本上也很难快速定位到问题,

生成 sourcemap 后的文件会在底部显示 sourcemap 的链接,如下:

关于前端脚本异常监控的思考
浏览器就会根据这个链接去拉取代码的sourcemap,这样就是可以使用sourcemap了,如果你的浏览器可以生成拉取到 sourcemap 那么error 事件的 error

对象中就可以看到相应的源码的行数,这样也能快速定位问题,如下:

关于前端脚本异常监控的思考

如何我们修改 sourcemap 的 url 地址,这样导致浏览器拉取不到sourcemap,报错信息就会像下面这样:

关于前端脚本异常监控的思考

那么这样不就可以了吗,我们只要把 sourcemap 也暴露出去就行了呀,这里得区分下开发人员和用户了,对于开发人员来说,当监控告诉我线上有问题的时候,我当然希望自己能够浮现这个问题,这个时候暴露sourcemap给开发人员那当然是有助于及时定位问题的,但是对于普通用户来说,首先没法确认普通用户的浏览器是否支持sourcemap并且开启了sourcemap,还有一个问题就是有了sourcemap,就等于是把你的源代码给了别人看,这是否合适是值得商榷的。

综合以上两点,得出结论,sourcemap 希望开发人员能够得到,但是不希望普通用户看到源码。

sourcemap 不希望用户得到,这个很简单只要在用户的网络环境不响应 sourcemap 的请求即可,这个可以在 Nginx 上配置,或者说可以放在内网中,这样开发人员通过 VPN 也能直接在线上看到源码的问题,以 webpack 为例,将 sourcemap 放在不同域中,你需要关闭 devtool 中的 sourcemap,使用 source-map-dev-tool-plugin ,如下:

const webpack = require('webpack')
const UglifyJsPlugin = require ('uglifyjs-webpack-plugin');


module.exports = {
    // other config
    mode: 'production',
    devtool: false,
    optimization: {
        noEmitOnErrors: true,
        minimizer: [new UglifyJsPlugin({
                sourceMap: true,
            })
        ]
    },
    plugins: [
      new webpack.SourceMapDevToolPlugin ({
        publicPath: 'http://localhost:8002/',
        filename: '[file].map',
      })
    ]
}
复制代码

服务端解析信息

接下来就是考虑监控系统了,监控系统是用来收集前端的异常信息,并在达到一定阈值后向自动告诉开发人员(虽然这得让开发人员出于 on call 状态),上面我们说到了用户上报过来的信息是压缩后的行、列号信息,这样的信息本质上对于开发人员来说意义是不大的,因此需要在服务端将行列号解析一遍,这个工作看起来貌似没法完成呀,不过感谢 mozilla 开源的 source-map ,这可以让这个工作变得异常简单,只要读取生成的 sourcemap,将行列号信息作为参数传递即可

const map = fs.readFileSync(path.resolve(__dirname, 'sourcemaps', `dist/${source.split('/').pop()}.map`), 'utf8').toString()
    const consumer = await new SourceMap.SourceMapConsumer(JSON.parse(map));
    const result = consumer.originalPositionFor({
        line,
        column,
    })
复制代码

一个包含简单的解析的微型监控系统如下:

const Koa = require('koa')
const router = require('koa-router')()
const SourceMap = require('source-map')
const app = new Koa()
const path = require('path')
const fs = require('fs')
const chalk = require('chalk')

router.get('/log.gif', async (ctx, next) => {
    const source = ctx.query.source
    const line = +ctx.query.l
    const column = +ctx.query.c
    const msg = ctx.query.msg
    const err = ctx.query.err

    const map = fs.readFileSync(path.resolve(__dirname, 'sourcemaps', `dist/${source.split('/').pop()}.map`), 'utf8').toString()
    const consumer = await new SourceMap.SourceMapConsumer(JSON.parse(map));
    const result = consumer.originalPositionFor({
        line,
        column,
    })
    console.log(chalk.red('原始上报的脚本异常信息:'), '\n')
    console.log(chalk.red(`行号:${line}`), '\n')
    console.log(chalk.red(`列号:${column}`), '\n')
    console.log(chalk.red(`文件:${source}`), '\n')
    console.log(chalk.red(`信息:${msg}`), '\n')
    console.log(chalk.red(`error对象:${err}`), '\n')

    console.log(chalk.green(`解析后源码对应的信息:`), '\n')
    console.log(chalk.green(`行号:${result.line}`), '\n')
    console.log(chalk.green(`列号:${result.column}`), '\n')
    console.log(chalk.green(`文件:${result.source}`), '\n')
    console.log(chalk.green(`name:${result.name}`), '\n')

    console.log(result)
    consumer.destroy();
    ctx.body = ''
})

app
    .use(router.routes())
    .use(router.allowedMethods())

app.listen('8002', () => {
    console.log('monitro server is listening port 8002')
})
复制代码

我们运行一遍,如下:

关于前端脚本异常监控的思考

这样我们就完成了我们的一个简单的获取原始信息的简单监控系统。

关于如何获取原始代码的堆栈信息,在上报信息、解析信息的进阶知识,欢迎期待下一篇文章。

整个系统的代码你可以在这个仓库找到: github.com/huruji/mini…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

学习JavaScript数据结构与算法

学习JavaScript数据结构与算法

[巴西] 格罗纳(Loiane Groner) / 孙晓博、邓钢、吴双、陈迪、袁源 / 人民邮电出版社 / 2015-10-1 / 39.00

本书首先介绍了JavaScript语言的基础知识,接下来讨论了数组、栈、队列、链表、集合、字典、散列表、树、图等数据结构,之后探讨了各种排序和搜索算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、顺序搜索、二分搜索,还介绍了动态规划和贪心算法等常用的高级算法及相关知识。一起来看看 《学习JavaScript数据结构与算法》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换