美文网首页
日志服务利用sourceMap作错误栈解析

日志服务利用sourceMap作错误栈解析

作者: 2林子易2 | 来源:发表于2020-05-27 14:03 被阅读0次

一般我们上传日志里面的错误都应该是经过混淆的,这样无法直观的看到错误栈的信息,故有此次需求。
前景提要:
react-native代码混淆后上报错误日志无法看到错误日志的错误栈信息,对我们根据日志进行错误定位有一定影响,考虑一下几个处理手段:

  1. 不混淆react native的bundle文件,看是否能正确定位
  2. 根据sourceMap翻译错误栈内容

RN打包后的bundle文件即使不混淆也是一整个文件,无法按照源码的位置进行错误描述。故使用sourceMap进行翻译,考虑后将其做到服务端进行翻译。

服务端配置:
node的express服务,利用pm2集群模式多开进程增强并发访问处理。

错误栈解析代码如下:

const fs = require("fs")
const sourceMap = require('source-map');
const _ = require("lodash")
var LruCache = require("lru-cache")
const {HttpCore} = require("http-core")
const path = require('path')
const sourceMapPath = "../../sourcemap";
let sourceMapCache = new LruCache(20);
let http = new HttpCore({
  withCredentials: false,
  timeout: 30000
});

function getFileNameByUrl(url) {
  return url.replace(/[^a-z0-9.]+|\./gi, "_")+'.bundle.map'
}

function getSourceMapObjByFileName(fileName) {
  return new Promise((resolve, reject) => {
    let filePath = path.join(__dirname, sourceMapPath, fileName);

    try {
      fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) {
          reject(err)
        }
        
        resolve(new sourceMap.SourceMapConsumer(JSON.parse(data)))
      });
    } catch(err) {
      reject(err);
    }
  })
}

function getSourceMapObjByUrl(url) {
  return http.get(url, {}).then(data => {
    let fileName = getFileNameByUrl(url);
    let filePath = path.join(__dirname, sourceMapPath, fileName);

    try {
      let fileContent = JSON.stringify(data)
      fs.writeFile(filePath, fileContent, (err) => {
        err && console.log("getSourceMapObjByUrl error", err)
      });
    } catch(err) {
      console.log("getSourceMapObjByUrl JSON stringify error", err)
    }

    return new sourceMap.SourceMapConsumer(data)
  }).catch(err => {
    throw(err)
  })
}

function getRealStackLine(sourceMapObj, stackLine) {
  // 期望格式: filepath:line:column
  let stackLineBlockArr = stackLine.split(":");

  // 符合期望格式
  if (stackLineBlockArr.length >= 3 && _.isNumber(Number(stackLineBlockArr[1])) && _.isNumber(Number(stackLineBlockArr[2]))) {
    let {source, line, column, name} = sourceMapObj.originalPositionFor({
      line: Number(stackLineBlockArr[1]),
      column: Number(stackLineBlockArr[2])
    });

    if (source && line && column) {
      return [source, name, line, column].join(":")
    } else {
      return stackLine;
    }
  } else {
    return stackLine;
  }
}

function getRealStack(sourceMapObj, stack) {
    let stackArr = stack.split("\n");
    return stackArr.map(stackLine => getRealStackLine(sourceMapObj, stackLine)).join("\n");
}

function errorStackParser(url, log) {
  let fileName = getFileNameByUrl(url);
  let sourceMapObj = sourceMapCache.get(fileName);

  return new Promise(resolve => {
    // 如果缓存存在
    if (sourceMapObj) {
      resolve(sourceMapObj);

    // 如果存在本地文件
    } else if (fs.existsSync(path.join(__dirname, sourceMapPath, fileName))) {
      resolve(getSourceMapObjByFileName(fileName))

    // 如果只能从url获取
    } else {
      resolve(getSourceMapObjByUrl(url))
    }
  }).then((sourceMapObj) => {
    // 更新缓存
    sourceMapCache.set(fileName, sourceMapObj)
    // 更新错误栈
    log.data.stack = getRealStack(sourceMapObj, log.data.stack);
    return log;
  }).catch(err => {
    // 如果发生错误,则返回原本的log日志,保证日志记录无误
    console.log("errorStackParser error", err);
    return log;
  })
}

module.exports = errorStackParser;

为了避免频繁的读磁盘,使用内存LRU缓存来sourceMapObj对象的存储。但是这样做有个问题,在pm2多进程的情况下会导致每个进程中都有缓存,造成内存的极大浪费。(这里我们的sourceMap文件有10M大小)。尝试过使用redis作为缓存数据库存储sourceMap对象,但发现在多并发的情况下内存持续增长(rss持续增长,heaptotal和heapUsed微量增长)。遂进行了一次内存监控。以下:

这里有两个工具推荐一下:memeyeheapdump
起初我利用memeye进行单个进程的内存监控,发现服务在接收到错误日志的时候内存会有一次暴增,如下图:

初次接收到错误日志

PS: memeye的链接,真的很方便!

这里有一点让我很在意,明明heaptotal增长很快就下来了,但是rss却持续居高不下,不明白为什么会导致rss过高。

通过pm2在本地多开进程发现,多进程与单进程内存情况一致。

通过heapdump进行各执行步骤的内存打点,将其载入chrome进行分析发现,有大量的buffer分配且占用过高(在sourcemap库的解析中),了解到rss暴涨的部分属于V8的堆外内存。


sourcemap的内存占用

后通过查看sourcemap源码和了解sourcemap的解析规则发现需要位运算所以会有大量的buffer存在。

这里采用的因多进程导致内存过高处理办法是利用pm2新开一个进程(不同端口),然后将其他日志服务进程的错误日志重定向到该进程服务,这样单独一个进程处理错误日志。

相关文章

  • 日志服务利用sourceMap作错误栈解析

    一般我们上传日志里面的错误都应该是经过混淆的,这样无法直观的看到错误栈的信息,故有此次需求。前景提要:react-...

  • 前端异常监控

    实现思路 前端 js报错事件监听+上报错误信息 后端 提供接口收集报错 根据前端提供的sourcemap文件解析前...

  • iOS 上传错误日志

    公司服务器错误日志 & 微软日志 上传 一. 公司服务器错误日志 简介上传自己公司的服务器,需要的流程大概为: 上...

  • 友盟 Application received signal S

    转自解析iOS崩溃日志(crash Log) 最近在解析umeng错误分析日志上有了重大突破! 很显然,我们的应用...

  • ANR分析

    ANR日志分析 获取日志 trace文件解析 trace文件例子 关注主线程调用栈,断点调试若干次就可搞定了。 基...

  • CentOS7安装CDH5.12.0报错:could not c

    描述:DNS反向解析错误,不能正确解析Cloudera Manager Server主机名。如下日志: 解决方案:...

  • webpack基础(八) webpack 打包优化

    sourcemap sourcemap表示是否生成和源码相对应的代码。这个在开发阶段能够很好的帮助我们定位错误。配...

  • SourceMap mappings解析

    源映射格式 所有地址可以是 http:// 开头的网址或者是本地文件地址。 下面着重介绍mappings mapp...

  • MySQL数据库物理文件架构组成各种工具详解

    MySQL物理文件组成——日志文件——错误日志(Error Log) 错误日志记录了MyQL服务器运行过程中所有较...

  • 03 hadoop 日志收集

    0 目标 收集系统 docker 137个服务日志中错误日志, 根据服务名,跟日期显示 1 flume 收集日志到...

网友评论

      本文标题:日志服务利用sourceMap作错误栈解析

      本文链接:https://www.haomeiwen.com/subject/fmdwahtx.html