feat: implement a logger service and make it log uncaught exceptions, make unhandled rejections throw an exception, fix spectron test

This commit is contained in:
Xymorot 2020-08-05 22:48:00 +02:00
parent 28c26ff258
commit 9672c9b5ed
17 changed files with 572 additions and 36 deletions

View File

@ -12,6 +12,7 @@ include:
- 'src/**'
exclude:
- 'src/**/*.spec.*'
- 'src/**/*.mock.*'
- 'src/main/entities/**'
watermarks:
statements: [80, 95]

View File

@ -160,12 +160,14 @@ There are 2 ways in which mocks are defined/used:
Mocha does [not have a separate tagging feature](https://github.com/mochajs/mocha/wiki/Tagging), but it can filter via title. Use the following tags in your test titles:
- `@slow` when the test is particularly slow
| tag | usage when |
| ----------- | ------------------------- |
| `@slow` | test is particularly slow |
| `@spectron` | test uses spectron |
#### Coverage
Code coverage is provided by [nyc](https://github.com/istanbuljs/nyc). The detailed code coverage can be found under `.nyc_output/coverage/index.html` after running the coverage script `npm run coverage` (open in browser). The coverage script is separate because it does not allow simple debugging.\
The code coverage does not work with Spectron since that runs in its own node process.
Code coverage is provided by [nyc](https://github.com/istanbuljs/nyc). The detailed code coverage can be found under `.nyc_output/coverage/index.html` (open in browser) after running the coverage script `npm run coverage`. The coverage script is separate because it does not allow simple debugging.
### Updating Dependencies

99
package-lock.json generated
View File

@ -847,6 +847,16 @@
"integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==",
"dev": true
},
"@types/chai-fs": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/chai-fs/-/chai-fs-2.0.2.tgz",
"integrity": "sha512-nS385nRPNvi9UjSUCDE7f84IXZnIzooPh7Ky88kdRfSFk72juvQENqrqyrKkJeXJsN9bMG9/5zVGr06GcBRB5g==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/node": "*"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -1766,6 +1776,16 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
"array-events": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/array-events/-/array-events-0.2.0.tgz",
"integrity": "sha1-/0KsU+ZvSF1viDI0wyJSvCKGEw4=",
"dev": true,
"requires": {
"async-arrays": "*",
"extended-emitter": "*"
}
},
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@ -1921,6 +1941,15 @@
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==",
"dev": true
},
"async-arrays": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-arrays/-/async-arrays-1.0.1.tgz",
"integrity": "sha1-NHrytw8qeldnotVnnMQrvxwiD9k=",
"dev": true,
"requires": {
"sift": "*"
}
},
"async-each": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
@ -2055,6 +2084,15 @@
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
"dev": true
},
"bit-mask": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bit-mask/-/bit-mask-1.0.2.tgz",
"integrity": "sha512-UGtq08LSiazxL4zVmBzrhdCWnT4RWx3JhhD/3crhfv8xxjnVHxf/WoVjEstjSUaZeZRP7kZrWNqup1VvUClCaQ==",
"dev": true,
"requires": {
"array-events": "^0.2.0"
}
},
"bl": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
@ -2388,6 +2426,12 @@
"write-file-atomic": "^3.0.0"
}
},
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -2436,6 +2480,16 @@
"type-detect": "^4.0.5"
}
},
"chai-fs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chai-fs/-/chai-fs-2.0.0.tgz",
"integrity": "sha1-Na4Dn7uwcQ9RIqrhf6uh6PQRB8Y=",
"dev": true,
"requires": {
"bit-mask": "^1.0.1",
"readdir-enhanced": "^1.4.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -4193,6 +4247,12 @@
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@ -4795,6 +4855,16 @@
}
}
},
"extended-emitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/extended-emitter/-/extended-emitter-1.0.4.tgz",
"integrity": "sha512-QBGuIo+pCXnYNeLUObaH/IKrCrzWzm4KhQNvA/mwNTs7/wzFylmA765zxh0WwWqpX1skQGXvzcRMHScc87Om/g==",
"dev": true,
"requires": {
"sift": "*",
"wolfy87-eventemitter": "*"
}
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@ -5460,6 +5530,12 @@
"is-glob": "^4.0.1"
}
},
"glob-to-regexp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
"integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
"dev": true
},
"global-agent": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz",
@ -9336,6 +9412,17 @@
}
}
},
"readdir-enhanced": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz",
"integrity": "sha1-YUYwSGkKxqRVt1ti+nioj43IXlM=",
"dev": true,
"requires": {
"call-me-maybe": "^1.0.1",
"es6-promise": "^4.1.0",
"glob-to-regexp": "^0.3.0"
}
},
"readdirp": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
@ -9854,6 +9941,12 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"sift": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-13.2.0.tgz",
"integrity": "sha512-tngIupMS8j5oxmd+dwAUMwX1NkHTkYTJFin9qf8hIZV1Euz5p4iclRDmoEIrEI6OsNdVswORbKOe9P6XZgco0A==",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@ -12075,6 +12168,12 @@
"wipe-node-cache": "^2.1.0"
}
},
"wolfy87-eventemitter": {
"version": "5.2.9",
"resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.9.tgz",
"integrity": "sha512-P+6vtWyuDw+MB01X7UeF8TaHBvbCovf4HPEMF/SV7BdDc1SMTiBy13SRD71lQh4ExFTG1d/WNzDGDCyOKSMblw==",
"dev": true
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View File

@ -15,7 +15,7 @@
},
"scripts": {
"postinstall": "npm run rebuild",
"start": "electron . --enable-logging --dev",
"start": "electron . --enable-logging --env=dev",
"rebuild": "electron-rebuild -f -b -t prod,dev,optional",
"electron-version": "electron electron-version.js",
"typeorm:migrate": "npm run typeorm:migrate:library && npm run typeorm:migrate:store",
@ -29,7 +29,7 @@
"watch:ts": "tsc -w --pretty --preserveWatchOutput",
"build": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run build:webpack\" \"npm run build:index\" \"npm run build:ts\"",
"watch": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run watch:webpack\" \"npm run watch:index\" \"npm run watch:ts\"",
"test:fast": "mocha --grep @slow --invert",
"test:fast": "mocha --grep \"@(slow|spectron)\" --invert",
"test": "mocha",
"coverage:fast": "nyc npm run test:fast",
"coverage": "nyc npm run test",
@ -60,6 +60,7 @@
"@electron-forge/cli": "^6.0.0-beta.52",
"@electron-forge/maker-squirrel": "^6.0.0-beta.52",
"@types/chai": "^4.2.12",
"@types/chai-fs": "^2.0.2",
"@types/fs-extra": "^9.0.1",
"@types/jsdom": "^16.2.3",
"@types/minimist": "^1.2.0",
@ -72,6 +73,7 @@
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"chai": "^4.2.0",
"chai-fs": "^2.0.0",
"chokidar": "^3.4.1",
"concurrently": "^5.2.0",
"electron": "^9.1.1",

View File

@ -5,40 +5,36 @@ import rewiremock from 'rewiremock';
import 'mocha';
import { Application } from 'spectron';
import packageJson from '../package.json';
rewiremock.disable();
describe('Application @slow', function () {
describe('Application @spectron', function () {
this.timeout(20000);
interface IApplicationContext extends Context {
app: Application;
app?: Application;
}
before(function (this, done): void {
const context = this as IApplicationContext;
context.app = new Application({
// @ts-ignore this does give the path to electron executable when this script is running outside of electron (which it does in the test files)
// spectron writes its electron files into a temporary directory, the local app installation should not be compromised
before(function (this: IApplicationContext) {
this.app = new Application({
path: ((electron as unknown) as { default: string }).default,
args: [packageJson.main],
args: ['.', '--enable-logging'],
});
context.app
.start()
.then(() => done())
.catch((reason) => done(reason));
return this.app.start();
});
after(function (this) {
const context = this as IApplicationContext;
if (context.app && context.app.isRunning()) {
return context.app.stop();
after(function (this: IApplicationContext) {
if (this.app && this.app.isRunning()) {
return this.app.stop();
}
});
it('shows an initial window', function (this: Context) {
const context = this as IApplicationContext;
return context.app.client.getWindowCount().then((count: number) => {
it('shows an initial window', function (this: IApplicationContext) {
if (!this.app) {
throw Error('this.app is falsy');
}
return this.app.client.getWindowCount().then((count: number) => {
expect(count).to.be.gte(1);
});
});

View File

@ -3,10 +3,27 @@ import { container } from './main/core/container';
import './main/core/install';
import { app } from 'electron';
import { isDev } from './main/core/dev';
import { isDev } from './main/core/env';
import { IAppWindow } from './main/modules/app-window/i-app-window';
import { ILogger } from './main/modules/logger/i-logger';
import { ISession } from './main/modules/session/i-session';
/**
* have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26
* https://nodejs.org/api/process.html#process_event_unhandledrejection
*/
process.on('unhandledRejection', (reason) => {
const logger: ILogger = container.get(Symbol.for('logger'));
void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`);
throw reason;
});
process.on('uncaughtException', (error) => {
const logger: ILogger = container.get(Symbol.for('logger'));
void logger.exception(error);
throw error;
});
async function createWindow(): Promise<void> {
const session: ISession = container.get(Symbol.for('session'));
session.setHeaders();

View File

@ -1,6 +1,6 @@
import { app } from 'electron';
import path from 'path';
import packageJson from '../../../package.json';
import { isDev } from './dev';
import { isDev } from './env';
export const appPath = path.resolve(app.getPath('userData'), `${packageJson.version}${isDev() ? '-dev' : ''}`);

View File

@ -1,6 +1,7 @@
import 'reflect-metadata';
import { Container } from 'inversify';
import { MainAppWindow } from '../modules/app-window/main-app-window';
import { Logger } from '../modules/logger/logger';
import { NhentaiApi } from '../modules/nhentai/nhentai-api';
import '../modules/nhentai/nhentai-ipc-controller';
import { Session } from '../modules/session/session';
@ -18,3 +19,5 @@ container.bind(Symbol.for('nhentai-api')).to(NhentaiApi);
container.bind(Symbol.for('app-window-main')).to(MainAppWindow);
container.bind(Symbol.for('session')).to(Session);
container.bind(Symbol.for('logger')).to(Logger);

View File

@ -1,5 +0,0 @@
import minimist from 'minimist';
export function isDev(): boolean {
return !!minimist(process.argv).dev;
}

View File

@ -3,9 +3,17 @@ import '../../../mocks/electron';
import { expect } from 'chai';
import 'mocha';
import { isDev } from './dev';
import { isDev } from './env';
describe('Development Mode Service', () => {
export function setDev(dev = true): void {
if (dev) {
process.argv.push('--env=dev');
} else {
process.argv = process.argv.filter((value) => value !== '--env=dev');
}
}
describe('Environment Service', () => {
before(() => {
rewiremock.enable();
});
@ -15,9 +23,9 @@ describe('Development Mode Service', () => {
});
it('correctly identifies the development process argument', () => {
process.argv.push('--dev');
setDev();
expect(isDev()).to.be.true;
process.argv = process.argv.filter((value) => value !== '--dev');
setDev(false);
expect(isDev()).to.be.false;
});
});

20
src/main/core/env.ts Normal file
View File

@ -0,0 +1,20 @@
import minimist from 'minimist';
const enum Environment {
DEV = 'dev',
PROD = 'prod',
}
function getEnv(): Environment {
switch (minimist(process.argv).env) {
case 'd':
case 'dev':
return Environment.DEV;
default:
return Environment.PROD;
}
}
export function isDev(): boolean {
return getEnv() === Environment.DEV;
}

View File

@ -0,0 +1,63 @@
/**
* A Logger provides methods to save a developer message somewhere it can be retrieved
* @see ILogger.getLogFile
*/
export interface ILogger {
/**
* default logging method, the logging level needs to be specified
* @see LogLevel
*/
log(level: number, message: string): Promise<void>;
/**
* log with level 'fatal'
* @see LogLevel.fatal
*/
fatal(message: string): Promise<void>;
/**
* log with level 'error'
* @see LogLevel.error
*/
error(message: string): Promise<void>;
/**
* log with level 'warning'
* @see LogLevel.warning
*/
warning(message: string): Promise<void>;
/**
* log with level 'notice'
* @see LogLevel.notice
*/
notice(message: string): Promise<void>;
/**
* log with level 'info'
* @see LogLevel.info
*/
info(message: string): Promise<void>;
/**
* log with level 'debug'
* @see LogLevel.debug
*/
debug(message: string): Promise<void>;
/**
* logs the error, preferably with stack trace
* @see ILogger.getExceptionsLogFile
*/
exception(error: Error): Promise<void>;
/**
* @return path to the default log file
*/
getLogFile(): string;
/**
* @return path to the exceptions log file
*/
getExceptionsLogFile(): string;
}

View File

@ -0,0 +1,34 @@
/**
* This enum contains the different logging levels of this application
*/
export enum LogLevel {
/**
* something so horrible happened that the application is about to bite the dust
*/
fatal,
/**
* something happened which should not have happened, but is recoverable
*/
error,
/**
* something bad happened, as it be sometimes
*/
warning,
/**
* the developer wants to say something important, not necessarily bad
*/
notice,
/**
* information only for the interested
*/
info,
/**
* meant for development purposes
*/
debug,
}

View File

@ -0,0 +1,48 @@
import { injectable } from 'inversify';
import { ILogger } from './i-logger';
/**
* Mock of a logger, does not log anywhere
*/
@injectable()
export class LoggerMock implements ILogger {
public getLogFile(): string {
return '';
}
public getExceptionsLogFile(): string {
return '';
}
public log(): Promise<void> {
return Promise.resolve();
}
public fatal(): Promise<void> {
return this.log();
}
public error(): Promise<void> {
return this.log();
}
public warning(): Promise<void> {
return this.log();
}
public notice(): Promise<void> {
return this.log();
}
public info(): Promise<void> {
return this.log();
}
public debug(): Promise<void> {
return this.log();
}
public exception(): Promise<void> {
return Promise.resolve();
}
}

View File

@ -0,0 +1,141 @@
import rewiremock from 'rewiremock';
import '../../../../mocks/electron';
import chai, { expect } from 'chai';
import 'mocha';
import { container } from '../../core/container';
import { setDev } from '../../core/env.spec';
import { ILogger } from './i-logger';
import fs from 'fs-extra';
import { createInterface, Interface } from 'readline';
import fc from 'fast-check';
import { LogLevel } from './log-level';
import chaiFs from 'chai-fs';
chai.use(chaiFs);
describe('Logger Service', () => {
function getLastLine(rl: Interface): Promise<string> {
return new Promise((resolve) => {
let lastLine = '';
rl.on('line', (line) => {
lastLine = line;
});
rl.on('close', () => {
resolve(lastLine);
});
});
}
function getDefaultLogReadLineInterface(logger: ILogger) {
return createInterface(fs.createReadStream(logger.getLogFile()));
}
// no debug because it gets tested separately
const logLevels = ['fatal', 'error', 'warning', 'notice', 'info'];
const logLevelsNumber = [0, 1, 2, 3, 4];
// hard-coded because it is hardcoded in the logger as well and therefore can be tested to be this way, in byte
const maxLogSize = 50000;
type LogLevelArbitrary = fc.Arbitrary<'fatal' | 'error' | 'warning' | 'notice' | 'info'>;
const logLevelArbitrary = fc.constantFrom(...logLevels);
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
before(() => {
rewiremock.enable();
});
after(() => {
rewiremock.disable();
});
it('creates log files', () => {
const logger: ILogger = container.get(Symbol.for('logger'));
expect(logger.getLogFile()).path('log file is not created');
expect(logger.getExceptionsLogFile()).path('exception log file is not created');
});
it('logs exceptions', async () => {
const logger: ILogger = container.get(Symbol.for('logger'));
await logger.exception(new Error('this is an error'));
});
it("default log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get(Symbol.for('logger'));
for (let i = 0; i < maxLogSize; i++) {
await logger.log(4, 'your waifu is trash');
}
const logFileStats = await fs.stat(logger.getLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
}).timeout(15000);
it("exception log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get(Symbol.for('logger'));
for (let i = 0; i < maxLogSize; ++i) {
await logger.exception(new Error('your waifu is trash'));
}
const logFileStats = await fs.stat(logger.getLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
}).timeout(15000);
it('logs different levels directly', () => {
const logger: ILogger = container.get(Symbol.for('logger'));
return fc.assert(
fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => {
await logger[logLevel](message);
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains(message, 'the log line does not contain the message');
expect(lastLine).contains(logLevel, `the log line does not contain the '${logLevel}' keyword`);
}),
{
numRuns: 50,
}
);
});
it('logs different levels indirectly via the generic log function', () => {
const logger: ILogger = container.get(Symbol.for('logger'));
return fc.assert(
fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
await logger.log(logLevelNumber, message);
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains(message, 'the log line does not contain the message');
expect(lastLine).contains(
logLevels[logLevelNumber],
`the log line does not contain the '${logLevels[logLevelNumber]}' keyword`
);
}),
{
numRuns: 50,
}
);
});
it('logs debug only in dev mode', async () => {
const logger: ILogger = container.get(Symbol.for('logger'));
setDev();
await logger.debug('this is a development message');
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains('this is a development message', 'the dev log line does not contain the message');
expect(lastLine).contains('debug', `the dev log line does not contain the 'debug' keyword`);
setDev(false);
await logger.log(LogLevel.warning, 'this is a warning');
await logger.debug('this is a second development message, should not be here');
expect(lastLine).not.contain(
'this is a second development message, should not be here',
'debug is logged even in non-dev mode'
);
});
});

View File

@ -0,0 +1,107 @@
import path from 'path';
import { Readable } from 'stream';
import * as fs from 'fs-extra';
import { injectable } from 'inversify';
import { appPath } from '../../core/app-path';
import { isDev } from '../../core/env';
import { ILogger } from './i-logger';
import { LogLevel } from './log-level';
const loggingDir = path.resolve(appPath, 'logs');
const logFile = path.resolve(loggingDir, 'default.log');
const exceptionLogFile = path.resolve(loggingDir, 'exception.log');
const maxLogSize = 50000;
/**
* A logger using winston to log to files in the appPath.
*/
@injectable()
export class Logger implements ILogger {
public constructor() {
fs.createFileSync(logFile);
fs.createFileSync(exceptionLogFile);
}
private static writeStream(stream: NodeJS.ReadableStream, filePath: string): Promise<void> {
return new Promise((resolve) => {
let buffer = fs.readFileSync(filePath, {
flag: 'r',
});
stream.on('data', (chunk) => {
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
});
stream.on('end', () => {
const diff = buffer.byteLength - maxLogSize;
if (diff > 0) {
buffer = buffer.slice(diff);
const firstLineBreakIndex = buffer.findIndex((value) => String.fromCharCode(value) === '\n');
buffer = buffer.slice(firstLineBreakIndex + 1);
}
fs.writeFileSync(filePath, buffer);
resolve();
});
});
}
private static writeLine(line: string): Promise<void> {
return Logger.writeStream(Readable.from(`${line}\n`), logFile);
}
private static formatLine(level: LogLevel, message: string): string {
return `[${new Date().toISOString()}] ${LogLevel[level]}: ${message}`;
}
public getLogFile(): string {
return logFile;
}
public getExceptionsLogFile(): string {
return exceptionLogFile;
}
public exception(error: Error): Promise<void> {
return Logger.writeStream(
Readable.from(
`[${new Date().toISOString()}] ${(error as NodeJS.ErrnoException).code ?? 'Error'}: ${error.message}
${error.stack ?? 'no stack trace'}\n`
),
exceptionLogFile
);
}
public log(level: LogLevel, message: string): Promise<void> {
if (!isDev() && level === LogLevel.debug) {
return Promise.resolve();
}
return Logger.writeLine(Logger.formatLine(level, message)).catch((error) => {
// eslint-disable-next-line no-console -- I don't want logging to be a source of a fatal error, but i want to log it somewhere
console.error(error);
});
}
public fatal(message: string): Promise<void> {
return this.log(LogLevel.fatal, message);
}
public error(message: string): Promise<void> {
return this.log(LogLevel.error, message);
}
public warning(message: string): Promise<void> {
return this.log(LogLevel.warning, message);
}
public notice(message: string): Promise<void> {
return this.log(LogLevel.notice, message);
}
public info(message: string): Promise<void> {
return this.log(LogLevel.info, message);
}
public debug(message: string): Promise<void> {
return this.log(LogLevel.debug, message);
}
}

View File

@ -1,6 +1,6 @@
import { session } from 'electron';
import { injectable } from 'inversify';
import { isDev } from '../../core/dev';
import { isDev } from '../../core/env';
import { ISession } from './i-session';
@injectable()