248 lines
5.7 KiB
JavaScript
Executable File
248 lines
5.7 KiB
JavaScript
Executable File
'use strict';
|
|
|
|
class EventLogger {
|
|
disposers = [];
|
|
format = '{eventSource}#{eventName}:';
|
|
formatters = {
|
|
app: {
|
|
'certificate-error': ({ args }) => {
|
|
return this.arrayToObject(args.slice(1, 4), [
|
|
'url',
|
|
'error',
|
|
'certificate',
|
|
]);
|
|
},
|
|
'child-process-gone': ({ args }) => {
|
|
return args.length === 1 ? args[0] : args;
|
|
},
|
|
'render-process-gone': ({ args: [webContents, details] }) => {
|
|
return details && typeof details === 'object'
|
|
? { ...details, ...this.getWebContentsDetails(webContents) }
|
|
: [];
|
|
},
|
|
},
|
|
|
|
webContents: {
|
|
'console-message': ({ args: [level, message, line, sourceId] }) => {
|
|
// 0: debug, 1: info, 2: warning, 3: error
|
|
if (level < 3) {
|
|
return undefined;
|
|
}
|
|
|
|
return { message, source: `${sourceId}:${line}` };
|
|
},
|
|
'did-fail-load': ({ args }) => {
|
|
return this.arrayToObject(args, [
|
|
'errorCode',
|
|
'errorDescription',
|
|
'validatedURL',
|
|
'isMainFrame',
|
|
'frameProcessId',
|
|
'frameRoutingId',
|
|
]);
|
|
},
|
|
'did-fail-provisional-load': ({ args }) => {
|
|
return this.arrayToObject(args, [
|
|
'errorCode',
|
|
'errorDescription',
|
|
'validatedURL',
|
|
'isMainFrame',
|
|
'frameProcessId',
|
|
'frameRoutingId',
|
|
]);
|
|
},
|
|
'plugin-crashed': ({ args }) => {
|
|
return this.arrayToObject(args, ['name', 'version']);
|
|
},
|
|
'preload-error': ({ args }) => {
|
|
return this.arrayToObject(args, ['preloadPath', 'error']);
|
|
},
|
|
},
|
|
};
|
|
|
|
events = {
|
|
app: {
|
|
'certificate-error': true,
|
|
'child-process-gone': true,
|
|
'render-process-gone': true,
|
|
},
|
|
|
|
webContents: {
|
|
// 'console-message': true,
|
|
'did-fail-load': true,
|
|
'did-fail-provisional-load': true,
|
|
'plugin-crashed': true,
|
|
'preload-error': true,
|
|
'unresponsive': true,
|
|
},
|
|
};
|
|
|
|
externalApi = undefined;
|
|
level = 'error';
|
|
scope = '';
|
|
|
|
constructor(options = {}) {
|
|
this.setOptions(options);
|
|
}
|
|
|
|
setOptions({
|
|
events,
|
|
externalApi,
|
|
level,
|
|
logger,
|
|
format,
|
|
formatters,
|
|
scope,
|
|
}) {
|
|
if (typeof events === 'object') {
|
|
this.events = events;
|
|
}
|
|
|
|
if (typeof externalApi === 'object') {
|
|
this.externalApi = externalApi;
|
|
}
|
|
|
|
if (typeof level === 'string') {
|
|
this.level = level;
|
|
}
|
|
|
|
if (typeof logger === 'object') {
|
|
this.logger = logger;
|
|
}
|
|
|
|
if (typeof format === 'string' || typeof format === 'function') {
|
|
this.format = format;
|
|
}
|
|
|
|
if (typeof formatters === 'object') {
|
|
this.formatters = formatters;
|
|
}
|
|
|
|
if (typeof scope === 'string') {
|
|
this.scope = scope;
|
|
}
|
|
}
|
|
|
|
startLogging(options = {}) {
|
|
this.setOptions(options);
|
|
|
|
this.disposeListeners();
|
|
|
|
for (const eventName of this.getEventNames(this.events.app)) {
|
|
this.disposers.push(
|
|
this.externalApi.onAppEvent(eventName, (...handlerArgs) => {
|
|
this.handleEvent({ eventSource: 'app', eventName, handlerArgs });
|
|
}),
|
|
);
|
|
}
|
|
|
|
for (const eventName of this.getEventNames(this.events.webContents)) {
|
|
this.disposers.push(
|
|
this.externalApi.onEveryWebContentsEvent(
|
|
eventName,
|
|
(...handlerArgs) => {
|
|
this.handleEvent(
|
|
{ eventSource: 'webContents', eventName, handlerArgs },
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
stopLogging() {
|
|
this.disposeListeners();
|
|
}
|
|
|
|
arrayToObject(array, fieldNames) {
|
|
const obj = {};
|
|
|
|
fieldNames.forEach((fieldName, index) => {
|
|
obj[fieldName] = array[index];
|
|
});
|
|
|
|
if (array.length > fieldNames.length) {
|
|
obj.unknownArgs = array.slice(fieldNames.length);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
disposeListeners() {
|
|
this.disposers.forEach((disposer) => disposer());
|
|
this.disposers = [];
|
|
}
|
|
|
|
formatEventLog({ eventName, eventSource, handlerArgs }) {
|
|
const [event, ...args] = handlerArgs;
|
|
if (typeof this.format === 'function') {
|
|
return this.format({ args, event, eventName, eventSource });
|
|
}
|
|
|
|
const formatter = this.formatters[eventSource]?.[eventName];
|
|
let formattedArgs = args;
|
|
if (typeof formatter === 'function') {
|
|
formattedArgs = formatter({ args, event, eventName, eventSource });
|
|
}
|
|
|
|
if (!formattedArgs) {
|
|
return undefined;
|
|
}
|
|
|
|
const eventData = {};
|
|
|
|
if (Array.isArray(formattedArgs)) {
|
|
eventData.args = formattedArgs;
|
|
} else if (typeof formattedArgs === 'object') {
|
|
Object.assign(eventData, formattedArgs);
|
|
}
|
|
|
|
if (eventSource === 'webContents') {
|
|
Object.assign(eventData, this.getWebContentsDetails(event?.sender));
|
|
}
|
|
|
|
const title = this.format
|
|
.replace('{eventSource}', eventSource === 'app' ? 'App' : 'WebContents')
|
|
.replace('{eventName}', eventName);
|
|
|
|
return [title, eventData];
|
|
}
|
|
|
|
getEventNames(eventMap) {
|
|
if (!eventMap || typeof eventMap !== 'object') {
|
|
return [];
|
|
}
|
|
|
|
return Object.entries(eventMap)
|
|
.filter(([_, listen]) => listen)
|
|
.map(([eventName]) => eventName);
|
|
}
|
|
|
|
getWebContentsDetails(webContents) {
|
|
if (!webContents?.loadURL) {
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
return {
|
|
webContents: {
|
|
id: webContents.id,
|
|
url: webContents.getURL(),
|
|
},
|
|
};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
handleEvent({ eventName, eventSource, handlerArgs }) {
|
|
const log = this.formatEventLog({ eventName, eventSource, handlerArgs });
|
|
if (log) {
|
|
const logFns = this.scope ? this.logger.scope(this.scope) : this.logger;
|
|
logFns?.[this.level]?.(...log);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EventLogger;
|