UNPKG

4.75 kBJavaScriptView Raw
1function Truncator(level) {
2 if (level === null || level === undefined || level < 0) {
3 level = 0;
4 }
5
6 this.maxStringLength = 1024;
7 this.maxObjectLength = 128;
8 this.maxArrayLength = 32;
9 this.maxDepth = 8;
10
11 this.keys = [];
12 this.seen = [];
13
14 for (var i = 0; i < level; i++) {
15 if (this.maxStringLength > 1) {
16 this.maxStringLength /= 2;
17 }
18 if (this.maxObjectLength > 1) {
19 this.maxObjectLength /= 2;
20 }
21 if (this.maxArrayLength > 1) {
22 this.maxArrayLength /= 2;
23 }
24 if (this.maxDepth > 1) {
25 this.maxDepth /= 2;
26 }
27 }
28}
29
30Truncator.prototype.truncate = function(value, key, depth) {
31 if (value === null || value === undefined) {
32 return value;
33 }
34 if (key === null || key === undefined) {
35 key = '';
36 }
37 if (depth === null || depth === undefined) {
38 depth = 0;
39 }
40
41 switch (typeof value) {
42 case 'boolean':
43 case 'number':
44 case 'function':
45 return value;
46 case 'string':
47 return this.truncateString(value);
48 case 'object':
49 break;
50 default:
51 return String(value);
52 }
53
54 if (value instanceof String) {
55 return this.truncateString(value.toString());
56 }
57
58 if (value instanceof Boolean ||
59 value instanceof Number ||
60 value instanceof Date ||
61 value instanceof RegExp) {
62 return value;
63 }
64
65 if (value instanceof Error) {
66 return value.toString();
67 }
68
69 if (this.seen.indexOf(value) >= 0) {
70 return '[Circular ' + this.getPath(value) + ']';
71 }
72
73 var type = this.objectType(value);
74
75 depth++;
76 if (depth > this.maxDepth) {
77 return '[Truncated ' + type + ']';
78 }
79
80 this.keys.push(key);
81 this.seen.push(value);
82
83 switch (type) {
84 case 'Array':
85 return this.truncateArray(value, depth);
86 case 'Object':
87 return this.truncateObject(value, depth);
88 default:
89 var saved = this.maxDepth;
90 this.maxDepth = 0;
91
92 var obj = this.truncateObject(value, depth);
93 obj.__type = type;
94
95 this.maxDepth = saved;
96
97 return obj;
98 }
99};
100
101Truncator.prototype.getPath = function(value) {
102 var index = this.seen.indexOf(value);
103 var path = [this.keys[index]];
104 for (var i = index; i >= 0; i--) {
105 var sub = this.seen[i];
106 if (sub && sub[path[0]] === value) {
107 value = sub;
108 path.unshift(this.keys[i]);
109 }
110 }
111
112 return '~' + path.join('.');
113};
114
115Truncator.prototype.truncateString = function(s) {
116 if (s.length > this.maxStringLength) {
117 return s.slice(0, this.maxStringLength) + '...';
118 }
119
120 return s;
121};
122
123Truncator.prototype.truncateArray = function(arr, depth) {
124 var length = 0;
125 var dst = [];
126
127 for (var i = 0; i < arr.length; i++) {
128 var el = arr[i];
129
130 length++;
131 if (length >= this.maxArrayLength) {
132 break;
133 }
134
135 dst.push(this.truncate(el, i, depth));
136 }
137
138 return dst;
139};
140
141
142Truncator.prototype.truncateObject = function(obj, depth) {
143 var length = 0;
144 var dst = {};
145
146 for (var attr in obj) {
147 if (Object.prototype.hasOwnProperty.call(obj, attr)) {
148 var value = obj[attr];
149
150 if (value === undefined || typeof value === 'function') {
151 continue;
152 }
153
154 length++;
155 if (length >= this.maxObjectLength) {
156 break;
157 }
158
159 dst[attr] = this.truncate(value, attr, depth);
160 }
161 }
162
163 return dst;
164};
165
166Truncator.prototype.objectType = function(obj) {
167 var s = Object.prototype.toString.apply(obj);
168 return s.slice('[object '.length, -1);
169};
170
171// truncateObj truncates each key in the object separately, which is
172// useful for handling circular references.
173function truncateObj(obj, level) {
174 var dst = {};
175 for (var attr in obj) {
176 if (Object.prototype.hasOwnProperty.call(obj, attr)) {
177 dst[attr] = module.exports.truncate(obj[attr], level);
178 }
179 }
180
181 return dst;
182}
183
184
185module.exports.truncate = function truncate(value, level) {
186 var t = new Truncator(level);
187 return t.truncate(value);
188};
189
190// jsonifyNotice serializes notice to JSON and truncates params,
191// environment and session keys.
192module.exports.jsonifyNotice = function jsonifyNotice(notice, maxLength) {
193 if (maxLength === null || maxLength === undefined) {
194 maxLength = 64000;
195 }
196
197 var s = '';
198 for (var level = 0; level < 8; level++) {
199 notice.context = truncateObj(notice.context, level);
200 notice.params = truncateObj(notice.params, level);
201 notice.environment = truncateObj(notice.environment, level);
202 notice.session = truncateObj(notice.session, level);
203
204 s = JSON.stringify(notice);
205 if (s.length < maxLength) {
206 return s;
207 }
208 }
209
210 var err = new Error(
211 'node-airbrake: cannot jsonify notice (length=' + s.length + ' maxLength=' +
212 maxLength + ')'
213 );
214 err.params = {
215 json: s.slice(0, Math.floor(maxLength / 2)) + '...'
216 };
217 throw err;
218};