soiz1 commited on
Commit
170ea12
·
verified ·
1 Parent(s): b999624

Create src/lib/pm-log-capture.js

Browse files
Files changed (1) hide show
  1. src/lib/pm-log-capture.js +159 -0
src/lib/pm-log-capture.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * String.prototype.indexOf, but it returns NaN not -1 on failure
3
+ * @param {string} str The string to check in
4
+ * @param {string} key The vaue to search for
5
+ * @param {number?} offset The offset into the string to start from
6
+ * @returns {number} The index of the key, or NaN if no instances where found
7
+ */
8
+ const _lastIndexNaN = (str, key, offset = Infinity) => {
9
+ if (!str) return NaN;
10
+ const val = str.lastIndexOf(key, offset);
11
+ if (val === -1) return NaN;
12
+ return val;
13
+ };
14
+ const browserHasStack = !!new Error().stack;
15
+ /**
16
+ * @typedef {'anonymous'|'eval'|'Function'|'GeneratorFunction'|'AsyncFunction'|'AsyncGeneratorFunction'} EvalName
17
+ * Set to anonymous in any browser context that does not supply any of the other valid types
18
+ */
19
+ /**
20
+ * @typedef {'log'|'warn'|'error'|'debug'|'info'|'promiseError'} LogType
21
+ */
22
+ /**
23
+ * @typedef {Object} StackTrace
24
+ * @param {string} name Name/path of the function
25
+ * @param {string} url The url on which this error exists
26
+ * @param {[number,number]} evalOrigin The line/column inside an eval call.
27
+ * Is null for none-eval-shaped calls.
28
+ * @param {EvalName?} evalType The type of eval this was ran with, such as 'eval', null or 'Function'
29
+ * @param {[number,number]} origin The line/column that this call is from, any one of these
30
+ * values can be NaN to stand in for N/A.
31
+ */
32
+ /**
33
+ * @typedef {Object} LogEntry
34
+ * @prop {number} time The time stamp at which this log was pushed
35
+ * @prop {LogType} type The type of this error
36
+ * @prop {string|any[]} message The error message/log arguments
37
+ * @prop {StackTrace[]} trace The stack trace leading to this log
38
+ */
39
+ /** @type {LogEntry[]} */
40
+ const consoleLogs = [];
41
+
42
+ /**
43
+ * Pushes a message into the console log list
44
+ * @param {LogType} type The type of message to log
45
+ * @param {string} message The error, literally what else is there to say
46
+ * @param {StackTrace[]} trace The stack trace of this log
47
+ */
48
+ const push = (type, message, trace) => {
49
+ // try to keep logs temporaly relevant, as long lengths of run time could make this array over flow
50
+ while (consoleLogs.length > 10000)
51
+ consoleLogs.shift();
52
+ consoleLogs.push({
53
+ time: Date.now(),
54
+ type,
55
+ message,
56
+ trace
57
+ });
58
+ };
59
+ const _parseFirefoxStack = stack => stack.split('\n')
60
+ .map(line => {
61
+ const at = line.indexOf('@');
62
+ const secondCol = line.lastIndexOf(':');
63
+ const firstCol = line.lastIndexOf(':', secondCol -1);
64
+ const endLine = line.length;
65
+ const name = line.slice(0, at);
66
+ let url = line.slice(at +1, firstCol);
67
+ let evalType = null;
68
+ let origin = [
69
+ Number(line.slice(firstCol +1, secondCol)),
70
+ Number(line.slice(secondCol +1, endLine))
71
+ ];
72
+ let evalOrigin = null;
73
+ /** @type {RegExpMatchArray} */
74
+ let match;
75
+ if ((match = url.match(/^ line ([0-9]+) > /))) {
76
+ url = line.slice(at, match.index);
77
+ evalOrigin = origin;
78
+ evalType = line.slice(match.index + match[0].length, firstCol);
79
+ origin = [Number(match[1]), NaN];
80
+ }
81
+
82
+ return {
83
+ name,
84
+ url,
85
+ evalOrigin,
86
+ evalType,
87
+ origin
88
+ };
89
+ });
90
+ const _parseChromeStack = stack => stack.split('\n').slice(1)
91
+ .map(line => {
92
+ // we have no use for the human readable fluff
93
+ line = line.slice(7);
94
+ const firstOpenParen = line.indexOf('(');
95
+ const secondOpenParen = line.indexOf('(', firstOpenParen +1);
96
+ const firstCloseParen = line.indexOf(')');
97
+ const secondCloseParen = line.indexOf(')', firstCloseParen +1);
98
+ let fourthCol = line.lastIndexOf(':');
99
+ let thirdCol = line.lastIndexOf(':', (fourthCol || line.length) -1);
100
+ let secondCol = _lastIndexNaN(line, ':', (thirdCol || line.length) -1);
101
+ let firstCol = _lastIndexNaN(line, ':', (secondCol || line.length) -1);
102
+ if (secondOpenParen === -1) {
103
+ secondCol = fourthCol;
104
+ firstCol = thirdCol;
105
+ fourthCol = NaN;
106
+ thirdCol = NaN;
107
+ }
108
+ const name = line.slice(0, firstOpenParen -1);
109
+ const origin = [
110
+ Number(line.slice(firstCol +1, secondCol)),
111
+ Number(line.slice(secondCol +1, thirdCol || firstCloseParen))
112
+ ];
113
+ let url = line.slice(firstOpenParen +1, firstCol);
114
+ let evalType = null;
115
+ let evalOrigin = null;
116
+ if (secondOpenParen !== -1) {
117
+ url = line.slice(secondOpenParen +1, firstCol);
118
+ evalType = 'anonymous';
119
+ evalOrigin = [
120
+ Number(line.slice(thirdCol +1, fourthCol)),
121
+ Number(line.slice(fourthCol +1, secondCloseParen))
122
+ ];
123
+ }
124
+
125
+ return {
126
+ name,
127
+ url,
128
+ evalOrigin,
129
+ evalType,
130
+ origin
131
+ };
132
+ });
133
+ const parseStack = (stack, url, line, column) => {
134
+ if (!browserHasStack || !stack) {
135
+ return [{
136
+ name: '<inaccessible>',
137
+ url,
138
+ origin: [line, column]
139
+ }];
140
+ }
141
+ // firefox has a *completely* different style rule compared to chrome
142
+ if (stack.split('\n', 2)[0].includes('@')) return _parseFirefoxStack(stack);
143
+ return _parseChromeStack(stack);
144
+ };
145
+
146
+ window.addEventListener('error', e =>
147
+ push('error', e.message, parseStack(e.error.stack, e.filename, e.lineno, e.colno)));
148
+ window.addEventListener('unhandledrejection', e => push('promiseError', e.reason, []));
149
+ for (const name of ['log', 'warn', 'error', 'debug', 'info']) {
150
+ const item = window.console[name];
151
+ window.console[name] = (...args) => {
152
+ let stack = [];
153
+ if (browserHasStack) stack = parseStack(new Error().stack);
154
+ push(name, args, stack);
155
+ item(...args);
156
+ };
157
+ }
158
+
159
+ export { consoleLogs, parseStack, push };