/* * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import {EventDispatcher} from './event-dispatcher.js'; import {ExtensionError} from './extension-error.js'; /** * This class handles logging of messages to the console and triggering * an event for log calls. * @augments EventDispatcher */ export class Logger extends EventDispatcher { /** * Creates a new instance. */ constructor() { super(); /** @type {string} */ this._extensionName = 'Yomitan'; try { const {name, version} = chrome.runtime.getManifest(); this._extensionName = `${name} ${version}`; } catch (e) { // NOP } } /** * Logs a generic error. This will trigger the 'log' event with the same arguments as the function invocation. * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. * @param {import('log').LogLevel} level The level to log at. Values include `'info'`, `'debug'`, `'warn'`, and `'error'`. * Other values will be logged at a non-error level. * @param {?import('log').LogContext} [context] An optional context object for the error which should typically include a `url` field. */ log(error, level, context = null) { if (typeof context !== 'object' || context === null) { context = {url: location.href}; } let errorString; try { if (typeof error === 'string') { errorString = error; } else { errorString = ( typeof error === 'object' && error !== null ? error.toString() : `${error}` ); if (/^\[object \w+\]$/.test(errorString)) { errorString = JSON.stringify(error); } } } catch (e) { errorString = `${error}`; } let errorStack; try { errorStack = ( error instanceof Error ? (typeof error.stack === 'string' ? error.stack.trimEnd() : '') : '' ); } catch (e) { errorStack = ''; } let errorData; try { if (error instanceof ExtensionError) { errorData = error.data; } } catch (e) { // NOP } if (errorStack.startsWith(errorString)) { errorString = errorStack; } else if (errorStack.length > 0) { errorString += `\n${errorStack}`; } let message = `${this._extensionName} has encountered a problem.`; message += `\nOriginating URL: ${context.url}\n`; message += errorString; if (typeof errorData !== 'undefined') { message += `\nData: ${JSON.stringify(errorData, null, 4)}`; } message += '\n\nIssues can be reported at https://github.com/themoeway/yomitan/issues'; /* eslint-disable no-console */ switch (level) { case 'log': console.log(message); break; case 'info': console.info(message); break; case 'debug': console.debug(message); break; case 'warn': console.warn(message); break; case 'error': console.error(message); break; } /* eslint-enable no-console */ this.trigger('log', {error, level, context}); } /** * Logs a warning. This function invokes `log` internally. * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. */ warn(error, context = null) { this.log(error, 'warn', context); } /** * Logs an error. This function invokes `log` internally. * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. */ error(error, context = null) { this.log(error, 'error', context); } /** * @param {import('log').LogLevel} errorLevel * @returns {import('log').LogErrorLevelValue} */ getLogErrorLevelValue(errorLevel) { switch (errorLevel) { case 'log': case 'info': case 'debug': return 0; case 'warn': return 1; case 'error': return 2; } } } /** * This object is the default logger used by the runtime. */ export const log = new Logger();