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

一般我们上传日志里面的错误都应该是经过混淆的,这样无法直观的看到错误栈的信息,故有此次需求。
前景提要:
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新开一个进程(不同端口),然后将其他日志服务进程的错误日志重定向到该进程服务,这样单独一个进程处理错误日志。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345