#315: improve logger errors serialization

This commit is contained in:
SleepWalker 2017-03-29 08:46:20 +03:00
parent 25b60c1fac
commit 2b879d428e
3 changed files with 161 additions and 0 deletions

View File

@ -0,0 +1,154 @@
const STRING_MAX_LENGTH = 128*1024;
/**
* Create a copy of any object without non-serializable elements to make result safe for JSON.stringify().
* Guaranteed to never throw.
*
* @param {any} obj Any data structure
* @param {object} options
* @param {function} options.filter - callback that is called on every object's key with (key,value) and should return
* value to use (may return undefined to remove unwanted keys). See nodeFilter and browserFilter.
* @param {number} options.depth - maximum recursion depth. Elements deeper than that are stringified with util.inspect()
* @param {number} options.maxSize - roughly maximum allowed size of data after JSON serialisation (but it's not guaranteed
* that it won't exceed the limit)
*
* @see https://github.com/ftlabs/js-abbreviate
*/
function abbreviate(obj, options) {
if (!options) options = {};
var filter = options.filter || function(k,v){return v;};
var maxDepth = options.depth || 10;
var maxSize = options.maxSize || 1*1024*1024;
return abbreviateRecursive(undefined, obj, filter, {sizeLeft: maxSize}, maxDepth);
}
function limitStringLength(str) {
if (str.length > STRING_MAX_LENGTH) {
return str.substring(0, STRING_MAX_LENGTH/2) + ' … ' + str.substring(str.length - STRING_MAX_LENGTH/2);
}
return str;
}
function abbreviateRecursive(key, obj, filter, state, maxDepth) {
if (state.sizeLeft < 0) {
return '**skipped**';
}
state.sizeLeft -= 5; // rough approximation of JSON overhead
obj = filter(key, obj);
try {
switch(typeof obj) {
case 'object':
if (null === obj) {
return null;
}
if (maxDepth < 0) {
break; // fall back to stringification
}
var newobj = Array.isArray(obj) ? [] : {};
for(var i in obj) {
newobj[i] = abbreviateRecursive(i, obj[i], filter, state, maxDepth-1);
if (state.sizeLeft < 0) break;
}
return newobj;
case 'string':
obj = limitStringLength(obj);
state.sizeLeft -= obj.length;
return obj;
case 'number':
case 'boolean':
case 'undefined':
return obj;
}
} catch(e) {/* fall back to inspect*/}
try {
obj = limitStringLength('' + obj);
state.sizeLeft -= obj.length;
return obj;
} catch(e) {
return "**non-serializable**";
}
}
function commonFilter(key, val) {
if ('function' === typeof val) {
return undefined;
}
if (val instanceof Date) {
return "**Date** " + val;
}
if (val instanceof Error) {
var err = {
// These properties are implemented as magical getters and don't show up in for in
stack: val.stack,
message: val.message,
name: val.name,
};
for(var i in val) {
err[i] = val[i];
}
return err;
}
return val;
}
function nodeFilter(key, val) {
// domain objects are huge and have circular references
if (key === 'domain' && 'object' === typeof val && val._events) {
return "**domain ignored**";
}
if (key === 'domainEmitter') {
return "**domainEmitter ignored**";
}
if (val === global) {
return "**global**";
}
return commonFilter(key, val);
}
function browserFilter(key, val) {
if (val === window) {
return "**window**";
}
if (val === document) {
return "**document**";
}
if (val instanceof HTMLElement) {
var outerHTML = val.outerHTML;
if ('undefined' != typeof outerHTML) {
return "**HTMLElement** " + outerHTML;
}
}
return commonFilter(key, val);
}
export {
abbreviate,
nodeFilter,
browserFilter
};
export default function(obj) {
return abbreviate(obj, {
filter: browserFilter
})
};

View File

@ -0,0 +1,3 @@
import logger from './logger';
export default logger;

View File

@ -1,5 +1,7 @@
import Raven from 'raven-js';
import abbreviate from './abbreviate';
const isTest = process.env.__TEST__; // eslint-disable-line
const isProduction = process.env.__PROD__; // eslint-disable-line
@ -72,6 +74,8 @@ const logger = {
};
}
context = abbreviate(context); // prepare data for JSON.stringify
console[method](message, context); // eslint-disable-line
Raven.captureException(message, {