Replace custom logging system with tslog

This commit is contained in:
Alice Gaudon 2020-11-02 17:48:52 +01:00
parent 93bff1fdca
commit 88e5e19730
22 changed files with 110 additions and 297 deletions

View File

@ -69,6 +69,7 @@
"on-finished": "^2.3.0", "on-finished": "^2.3.0",
"redis": "^3.0.2", "redis": "^3.0.2",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"tslog": "^2.10.0",
"uuid": "^8.0.0", "uuid": "^8.0.0",
"ws": "^7.2.3" "ws": "^7.2.3"
} }

View File

@ -1,7 +1,6 @@
import express, {NextFunction, Request, Response, Router} from 'express'; import express, {NextFunction, Request, Response, Router} from 'express';
import {BadRequestError, HttpError, NotFoundHttpError, ServerError, ServiceUnavailableHttpError} from "./HttpError"; import {BadRequestError, HttpError, NotFoundHttpError, ServerError, ServiceUnavailableHttpError} from "./HttpError";
import {lib} from "nunjucks"; import {lib} from "nunjucks";
import Logger from "./Logger";
import WebSocketListener from "./WebSocketListener"; import WebSocketListener from "./WebSocketListener";
import ApplicationComponent from "./ApplicationComponent"; import ApplicationComponent from "./ApplicationComponent";
import Controller from "./Controller"; import Controller from "./Controller";
@ -17,6 +16,7 @@ import * as path from "path";
import CacheProvider from "./CacheProvider"; import CacheProvider from "./CacheProvider";
import RedisComponent from "./components/RedisComponent"; import RedisComponent from "./components/RedisComponent";
import Extendable from "./Extendable"; import Extendable from "./Extendable";
import {log} from "./Logger";
import TemplateError = lib.TemplateError; import TemplateError = lib.TemplateError;
export default abstract class Application implements Extendable<ApplicationComponent | WebSocketListener<Application>> { export default abstract class Application implements Extendable<ApplicationComponent | WebSocketListener<Application>> {
@ -46,7 +46,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
const path = thing.path(); const path = thing.path();
this.webSocketListeners[path] = thing; this.webSocketListeners[path] = thing;
thing.init(this); thing.init(this);
Logger.info(`Added websocket listener on ${path}`); log.info(`Added websocket listener on ${path}`);
} else { } else {
thing.setApp(this); thing.setApp(this);
this.components.push(thing); this.components.push(thing);
@ -58,7 +58,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
} }
public async start(): Promise<void> { public async start(): Promise<void> {
Logger.info(`${config.get('app.name')} v${this.version} - hi`); log.info(`${config.get('app.name')} v${this.version} - hi`);
process.once('SIGINT', () => { process.once('SIGINT', () => {
this.stop().catch(console.error); this.stop().catch(console.error);
}); });
@ -114,7 +114,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
return; return;
} }
const errorId: string = LogRequestsComponent.logRequest(req, res, err, '500 Internal Error', const errorId = LogRequestsComponent.logRequest(req, res, err, '500 Internal Error',
err instanceof BadRequestError || err instanceof ServiceUnavailableHttpError); err instanceof BadRequestError || err instanceof ServiceUnavailableHttpError);
let httpError: HttpError; let httpError: HttpError;
@ -183,7 +183,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
for (let i = 2; i < args.length; i++) { for (let i = 2; i < args.length; i++) {
switch (args[i]) { switch (args[i]) {
case '--verbose': case '--verbose':
Logger.verbose(); log.setSettings({minLevel: "trace"});
break; break;
case '--full-http-requests': case '--full-http-requests':
LogRequestsComponent.logFullHttpRequests(); LogRequestsComponent.logFullHttpRequests();
@ -192,7 +192,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
await MysqlConnectionManager.migrationCommand(args.slice(i + 1)); await MysqlConnectionManager.migrationCommand(args.slice(i + 1));
return true; return true;
default: default:
Logger.warn('Unrecognized argument', args[i]); log.warn('Unrecognized argument', args[i]);
return true; return true;
} }
} }
@ -220,13 +220,13 @@ export default abstract class Application implements Extendable<ApplicationCompo
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
Logger.info('Stopping application...'); log.info('Stopping application...');
for (const component of this.components) { for (const component of this.components) {
await component.stop?.(); await component.stop?.();
} }
Logger.info(`${this.constructor.name} v${this.version} - bye`); log.info(`${this.constructor.name} v${this.version} - bye`);
} }
private routes(initRouter: Router, handleRouter: Router) { private routes(initRouter: Router, handleRouter: Router) {
@ -234,7 +234,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
if (controller.hasGlobalMiddlewares()) { if (controller.hasGlobalMiddlewares()) {
controller.setupGlobalHandlers(handleRouter); controller.setupGlobalHandlers(handleRouter);
Logger.info(`Registered global middlewares for controller ${controller.constructor.name}`); log.info(`Registered global middlewares for controller ${controller.constructor.name}`);
} }
} }
@ -243,7 +243,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
initRouter.use(controller.getRoutesPrefix(), fileUploadFormRouter); initRouter.use(controller.getRoutesPrefix(), fileUploadFormRouter);
handleRouter.use(controller.getRoutesPrefix(), mainRouter); handleRouter.use(controller.getRoutesPrefix(), mainRouter);
Logger.info(`> Registered routes for controller ${controller.constructor.name}`); log.info(`> Registered routes for controller ${controller.constructor.name}`);
} }
handleRouter.use((req: Request) => { handleRouter.use((req: Request) => {

View File

@ -1,5 +1,5 @@
import {Express, Router} from "express"; import {Express, Router} from "express";
import Logger from "./Logger"; import {log} from "./Logger";
import {sleep} from "./Utils"; import {sleep} from "./Utils";
import Application from "./Application"; import Application from "./Application";
import config from "config"; import config from "config";
@ -28,11 +28,11 @@ export default abstract class ApplicationComponent {
err = null; err = null;
} catch (e) { } catch (e) {
err = e; err = e;
Logger.error(err, `${name} failed to prepare; retrying in 5s...`); log.error(err, `${name} failed to prepare; retrying in 5s...`);
await sleep(5000); await sleep(5000);
} }
} while (err); } while (err);
Logger.info(`${name} ready!`); log.info(`${name} ready!`);
} }
protected async close(thingName: string, fn: (callback: (err?: Error | null) => void) => void): Promise<void> { protected async close(thingName: string, fn: (callback: (err?: Error | null) => void) => void): Promise<void> {
@ -42,9 +42,9 @@ export default abstract class ApplicationComponent {
else resolve(); else resolve();
})); }));
Logger.info(`${thingName} closed.`); log.info(`${thingName} closed.`);
} catch (e) { } catch (e) {
Logger.error(e, `An error occurred while closing the ${thingName}.`); log.error(e, `An error occurred while closing the ${thingName}.`);
} }
} }

View File

@ -1,7 +1,7 @@
import express, {IRouter, RequestHandler, Router} from "express"; import express, {IRouter, RequestHandler, Router} from "express";
import {PathParams} from "express-serve-static-core"; import {PathParams} from "express-serve-static-core";
import config from "config"; import config from "config";
import Logger from "./Logger"; import {log} from "./Logger";
import Validator, {ValidationBag} from "./db/Validator"; import Validator, {ValidationBag} from "./db/Validator";
import FileUploadMiddleware from "./FileUploadMiddleware"; import FileUploadMiddleware from "./FileUploadMiddleware";
import * as querystring from "querystring"; import * as querystring from "querystring";
@ -163,10 +163,10 @@ export default abstract class Controller {
if (!Controller.routes[routeName]) { if (!Controller.routes[routeName]) {
if (typeof routePath === 'string') { if (typeof routePath === 'string') {
Logger.info(`Route ${routeName} has path ${routePath}`); log.info(`Route ${routeName} has path ${routePath}`);
Controller.routes[routeName] = routePath; Controller.routes[routeName] = routePath;
} else { } else {
Logger.warn(`Cannot assign path to route ${routeName}.`); log.warn(`Cannot assign path to route ${routeName}.`);
} }
} }
} }

View File

@ -1,140 +1,11 @@
import config from "config";
import {v4 as uuid} from "uuid"; import {v4 as uuid} from "uuid";
import Log from "./models/Log"; import {Logger as TsLogger} from "tslog";
import {bufferToUuid} from "./Utils";
import ModelFactory from "./db/ModelFactory";
export enum LogLevel { export const log = new TsLogger();
ERROR,
WARN,
INFO,
DEBUG,
DEV,
}
export type LogLevelKeys = keyof typeof LogLevel; export function makeUniqueLogger(): TsLogger {
const id = uuid();
/** return log.getChildLogger({
* TODO: make logger not static requestId: id,
*/
export default class Logger {
private static logLevel: LogLevel = LogLevel[<LogLevelKeys>config.get<string>('log.level')];
private static dbLogLevel: LogLevel = LogLevel[<LogLevelKeys>config.get<string>('log.db_level')];
private static verboseMode: boolean = config.get<boolean>('log.verbose');
public static verbose(): void {
this.verboseMode = true;
if (LogLevel[this.logLevel + 1]) this.logLevel++;
if (LogLevel[this.dbLogLevel + 1]) this.dbLogLevel++;
Logger.info('Verbose mode');
}
public static isVerboseMode(): boolean {
return this.verboseMode;
}
public static silentError(error: Error, ...message: unknown[]): string {
return this.log(LogLevel.ERROR, message, error, true) || '';
}
public static error(error: Error, ...message: unknown[]): string {
return this.log(LogLevel.ERROR, message, error) || '';
}
public static warn(...message: unknown[]): void {
this.log(LogLevel.WARN, message);
}
public static info(...message: unknown[]): void {
this.log(LogLevel.INFO, message);
}
public static debug(...message: unknown[]): void {
this.log(LogLevel.DEBUG, message);
}
public static dev(...message: unknown[]): void {
this.log(LogLevel.DEV, message);
}
private static log(level: LogLevel, message: unknown[], error?: Error, silent: boolean = false): string | null {
if (level <= this.logLevel) {
if (error) {
if (level > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`);
} else {
if (level <= LogLevel.ERROR) this.warn(`No error attached with log level ${level}.`);
}
const computedMsg = message.map(v => {
if (typeof v === 'string') {
return v;
} else {
return JSON.stringify(v, (key: string, value: string | unknown[] | Record<string, unknown>) => {
if (!Array.isArray(value) && value instanceof Object) {
if (value.type === 'Buffer' && typeof value.data === 'string') {
return `Buffer<${Buffer.from(value.data).toString('hex')}>`;
} else if (value !== v) {
return `[object Object]`;
}
}
if (typeof value === 'string' && value.length > 96) {
return value.substr(0, 96) + '...';
}
return value;
}, 4);
}
}).join(' ');
const shouldSaveToDB = level <= this.dbLogLevel;
let output = `[${LogLevel[level]}] `;
const pad = output.length;
const logId = Buffer.alloc(16);
uuid({}, logId);
const strLogId = bufferToUuid(logId);
if (shouldSaveToDB) output += `${strLogId} - `;
output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad));
switch (level) {
case LogLevel.ERROR:
if (silent || !error) {
console.error(output);
} else {
console.error(output, error);
}
break;
case LogLevel.WARN:
console.warn(output);
break;
case LogLevel.INFO:
console.info(output);
break;
case LogLevel.DEBUG:
case LogLevel.DEV:
console.debug(output);
break;
}
if (shouldSaveToDB && ModelFactory.has(Log)) {
const log = Log.create({});
log.setLevel(level);
log.message = computedMsg;
log.setError(error);
log.setLogId(logId);
log.save().catch(err => {
if (!silent && err.message.indexOf('ECONNREFUSED') < 0) {
console.error({save_err: err, error});
}
}); });
}
return strLogId;
}
return null;
}
private constructor() {
// disable constructor
}
} }

View File

@ -5,7 +5,7 @@ import nunjucks from 'nunjucks';
import * as util from "util"; import * as util from "util";
import {WrappingError} from "./Utils"; import {WrappingError} from "./Utils";
import mjml2html from "mjml"; import mjml2html from "mjml";
import Logger from "./Logger"; import {log} from "./Logger";
import Controller from "./Controller"; import Controller from "./Controller";
import {ParsedUrlQueryInput} from "querystring"; import {ParsedUrlQueryInput} from "querystring";
@ -38,7 +38,7 @@ export default class Mail {
throw new MailError('Connection to mail service unsuccessful.', e); throw new MailError('Connection to mail service unsuccessful.', e);
} }
Logger.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`); log.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`);
} }
public static end(): void { public static end(): void {
@ -103,7 +103,7 @@ export default class Mail {
this.data.app = config.get('app'); this.data.app = config.get('app');
// Log // Log
Logger.dev('Send mail', this.options); log.debug('Send mail', this.options);
// Render email // Render email
this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false); this.options.html = Mail.parse('mails/' + this.template.template + '.mjml.njk', this.data, false);

View File

@ -3,12 +3,12 @@ import config from "config";
import * as child_process from "child_process"; import * as child_process from "child_process";
import ApplicationComponent from "../ApplicationComponent"; import ApplicationComponent from "../ApplicationComponent";
import {ForbiddenHttpError} from "../HttpError"; import {ForbiddenHttpError} from "../HttpError";
import Logger from "../Logger"; import {log} from "../Logger";
export default class AutoUpdateComponent extends ApplicationComponent { export default class AutoUpdateComponent extends ApplicationComponent {
private static async runCommand(command: string): Promise<void> { private static async runCommand(command: string): Promise<void> {
Logger.info(`> ${command}`); log.info(`> ${command}`);
Logger.info(child_process.execSync(command).toString()); log.info(child_process.execSync(command).toString());
} }
public async checkSecuritySettings(): Promise<void> { public async checkSecuritySettings(): Promise<void> {
@ -21,7 +21,7 @@ export default class AutoUpdateComponent extends ApplicationComponent {
if (!token || token !== config.get<string>('gitlab_webhook_token')) if (!token || token !== config.get<string>('gitlab_webhook_token'))
throw new ForbiddenHttpError('Invalid token', req.url); throw new ForbiddenHttpError('Invalid token', req.url);
this.update(req.body).catch(Logger.error); this.update(req.body).catch(req.log.error);
res.json({ res.json({
'status': 'ok', 'status': 'ok',
@ -30,10 +30,10 @@ export default class AutoUpdateComponent extends ApplicationComponent {
} }
private async update(params: { [p: string]: unknown }) { private async update(params: { [p: string]: unknown }) {
Logger.info('Update params:', params); log.info('Update params:', params);
try { try {
Logger.info('Starting auto update...'); log.info('Starting auto update...');
// Fetch // Fetch
await AutoUpdateComponent.runCommand(`git pull`); await AutoUpdateComponent.runCommand(`git pull`);
@ -47,9 +47,9 @@ export default class AutoUpdateComponent extends ApplicationComponent {
// Stop app // Stop app
await this.getApp().stop(); await this.getApp().stop();
Logger.info('Success!'); log.info('Success!');
} catch (e) { } catch (e) {
Logger.error(e, 'An error occurred while running the auto update.'); log.error(e, 'An error occurred while running the auto update.');
} }
} }
} }

View File

@ -1,6 +1,6 @@
import ApplicationComponent from "../ApplicationComponent"; import ApplicationComponent from "../ApplicationComponent";
import express, {Express, Router} from "express"; import express, {Express, Router} from "express";
import Logger from "../Logger"; import {log, makeUniqueLogger} from "../Logger";
import {Server} from "http"; import {Server} from "http";
import compression from "compression"; import compression from "compression";
import Middleware from "../Middleware"; import Middleware from "../Middleware";
@ -20,7 +20,7 @@ export default class ExpressAppComponent extends ApplicationComponent {
public async start(app: Express): Promise<void> { public async start(app: Express): Promise<void> {
this.server = app.listen(this.port, this.addr, () => { this.server = app.listen(this.port, this.addr, () => {
Logger.info(`Web server running on ${this.addr}:${this.port}.`); log.info(`Web server running on ${this.addr}:${this.port}.`);
}); });
// Proxy // Proxy
@ -41,6 +41,7 @@ export default class ExpressAppComponent extends ApplicationComponent {
router.use(compression()); router.use(compression());
router.use((req, res, next) => { router.use((req, res, next) => {
req.log = makeUniqueLogger();
req.middlewares = []; req.middlewares = [];
req.as = <M extends Middleware>(type: Type<M>): M => { req.as = <M extends Middleware>(type: Type<M>): M => {
const middleware = req.middlewares.find(m => m.constructor === type); const middleware = req.middlewares.find(m => m.constructor === type);

View File

@ -1,6 +1,6 @@
import ApplicationComponent from "../ApplicationComponent"; import ApplicationComponent from "../ApplicationComponent";
import onFinished from "on-finished"; import onFinished from "on-finished";
import Logger from "../Logger"; import {log} from "../Logger";
import {Request, Response, Router} from "express"; import {Request, Response, Router} from "express";
import {HttpError} from "../HttpError"; import {HttpError} from "../HttpError";
@ -9,7 +9,7 @@ export default class LogRequestsComponent extends ApplicationComponent {
public static logFullHttpRequests(): void { public static logFullHttpRequests(): void {
this.fullRequests = true; this.fullRequests = true;
Logger.info('Http requests will be logged with more details.'); log.info('Http requests will be logged with more details.');
} }
public static logRequest( public static logRequest(
@ -18,7 +18,7 @@ export default class LogRequestsComponent extends ApplicationComponent {
err?: unknown, err?: unknown,
additionalStr: string = '', additionalStr: string = '',
silent: boolean = false, silent: boolean = false,
): string { ): string | undefined {
if (LogRequestsComponent.fullRequests) { if (LogRequestsComponent.fullRequests) {
const requestObj = JSON.stringify({ const requestObj = JSON.stringify({
ip: req.ip, ip: req.ip,
@ -38,12 +38,12 @@ export default class LogRequestsComponent extends ApplicationComponent {
}, null, 4); }, null, 4);
if (err) { if (err) {
if (err instanceof Error) { if (err instanceof Error) {
return Logger.error(err, requestObj, err); return req.log.error(err, requestObj, err).requestId;
} else { } else {
return Logger.error(new Error(String(err)), requestObj); return req.log.error(new Error(String(err)), requestObj).requestId;
} }
} else { } else {
Logger.info(requestObj); req.log.info(requestObj);
} }
} else { } else {
let logStr = `${req.method} ${req.originalUrl} - ${res.statusCode}`; let logStr = `${req.method} ${req.originalUrl} - ${res.statusCode}`;
@ -52,15 +52,15 @@ export default class LogRequestsComponent extends ApplicationComponent {
if (silent) { if (silent) {
if (err instanceof HttpError) logStr += ` ${err.errorCode}`; if (err instanceof HttpError) logStr += ` ${err.errorCode}`;
logStr += ` ${err.name}`; logStr += ` ${err.name}`;
return Logger.silentError(err, logStr); return req.log.error(err, logStr, additionalStr).requestId;
} else { } else {
return Logger.error(err, logStr, additionalStr, err); return req.log.error(err, logStr, additionalStr, err).requestId;
} }
} else { } else {
return Logger.error(new Error(String(err)), logStr); return req.log.error(new Error(String(err)), logStr).requestId;
} }
} else { } else {
Logger.info(logStr); req.log.info(logStr);
} }
} }

View File

@ -8,7 +8,7 @@ import {ParsedUrlQueryInput} from "querystring";
import * as util from "util"; import * as util from "util";
import * as path from "path"; import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import Logger from "../Logger"; import {log} from "../Logger";
import Middleware from "../Middleware"; import Middleware from "../Middleware";
export default class NunjucksComponent extends ApplicationComponent { export default class NunjucksComponent extends ApplicationComponent {
@ -29,7 +29,7 @@ export default class NunjucksComponent extends ApplicationComponent {
try { try {
coreVersion = JSON.parse(fs.readFileSync(file).toString()).version; coreVersion = JSON.parse(fs.readFileSync(file).toString()).version;
} catch (e) { } catch (e) {
Logger.warn('Couldn\'t determine coreVersion.', e); log.warn('Couldn\'t determine coreVersion.', e);
} }
this.env = new nunjucks.Environment([ this.env = new nunjucks.Environment([

View File

@ -2,7 +2,6 @@ import ApplicationComponent from "../ApplicationComponent";
import {Request, Router} from "express"; import {Request, Router} from "express";
import {ServerError} from "../HttpError"; import {ServerError} from "../HttpError";
import onFinished from "on-finished"; import onFinished from "on-finished";
import Logger from "../Logger";
export default class RedirectBackComponent extends ApplicationComponent { export default class RedirectBackComponent extends ApplicationComponent {
public static getPreviousURL(req: Request, defaultUrl?: string): string | undefined { public static getPreviousURL(req: Request, defaultUrl?: string): string | undefined {
@ -29,10 +28,10 @@ export default class RedirectBackComponent extends ApplicationComponent {
contentType && typeof contentType !== 'number' && contentType.indexOf('text/html') >= 0 contentType && typeof contentType !== 'number' && contentType.indexOf('text/html') >= 0
)) { )) {
session.previousUrl = req.originalUrl; session.previousUrl = req.originalUrl;
Logger.debug('Prev url set to', session.previousUrl); req.log.debug('Prev url set to', session.previousUrl);
session.save((err) => { session.save((err) => {
if (err) { if (err) {
Logger.error(err, 'Error while saving session'); req.log.error(err, 'Error while saving session');
} }
}); });
} }

View File

@ -2,7 +2,7 @@ import ApplicationComponent from "../ApplicationComponent";
import {Express} from "express"; import {Express} from "express";
import redis, {RedisClient} from "redis"; import redis, {RedisClient} from "redis";
import config from "config"; import config from "config";
import Logger from "../Logger"; import {log} from "../Logger";
import session, {Store} from "express-session"; import session, {Store} from "express-session";
import connect_redis from "connect-redis"; import connect_redis from "connect-redis";
import CacheProvider from "../CacheProvider"; import CacheProvider from "../CacheProvider";
@ -18,7 +18,7 @@ export default class RedisComponent extends ApplicationComponent implements Cach
password: config.has('redis.password') ? config.get<string>('redis.password') : undefined, password: config.has('redis.password') ? config.get<string>('redis.password') : undefined,
}); });
this.redisClient.on('error', (err: Error) => { this.redisClient.on('error', (err: Error) => {
Logger.error(err, 'An error occurred with redis.'); log.error(err, 'An error occurred with redis.');
}); });
this.store = new RedisStore({ this.store = new RedisStore({
client: this.redisClient, client: this.redisClient,

View File

@ -1,7 +1,7 @@
import ApplicationComponent from "../ApplicationComponent"; import ApplicationComponent from "../ApplicationComponent";
import {Express, Request} from "express"; import {Express, Request} from "express";
import WebSocket, {Server as WebSocketServer} from "ws"; import WebSocket, {Server as WebSocketServer} from "ws";
import Logger from "../Logger"; import {log} from "../Logger";
import cookie from "cookie"; import cookie from "cookie";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import config from "config"; import config from "config";
@ -28,9 +28,9 @@ export default class WebSocketServerComponent extends ApplicationComponent {
this.wss = new WebSocketServer({ this.wss = new WebSocketServer({
server: this.expressAppComponent.getServer(), server: this.expressAppComponent.getServer(),
}, () => { }, () => {
Logger.info(`Websocket server started over webserver.`); log.info(`Websocket server started over webserver.`);
}).on('error', (err) => { }).on('error', (err) => {
Logger.error(err, 'An error occurred in the websocket server.'); log.error(err, 'An error occurred in the websocket server.');
}).on('connection', (socket, request) => { }).on('connection', (socket, request) => {
const listener = request.url ? listeners[request.url] : null; const listener = request.url ? listeners[request.url] : null;
@ -39,12 +39,12 @@ export default class WebSocketServerComponent extends ApplicationComponent {
return; return;
} else if (!request.headers.cookie) { } else if (!request.headers.cookie) {
listener.handle(socket, request, null).catch(err => { listener.handle(socket, request, null).catch(err => {
Logger.error(err, 'Error in websocket listener.'); log.error(err, 'Error in websocket listener.');
}); });
return; return;
} }
Logger.debug(`Websocket on ${request.url}`); log.debug(`Websocket on ${request.url}`);
const cookies = cookie.parse(request.headers.cookie); const cookies = cookie.parse(request.headers.cookie);
const sid = cookieParser.signedCookie(cookies['connect.sid'], config.get('session.secret')); const sid = cookieParser.signedCookie(cookies['connect.sid'], config.get('session.secret'));
@ -57,7 +57,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
const store = this.storeComponent.getStore(); const store = this.storeComponent.getStore();
store.get(sid, (err, session) => { store.get(sid, (err, session) => {
if (err || !session) { if (err || !session) {
Logger.error(err, 'Error while initializing session in websocket.'); log.error(err, 'Error while initializing session in websocket.');
socket.close(1011); socket.close(1011);
return; return;
} }
@ -66,7 +66,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
store.createSession(<Request>request, session); store.createSession(<Request>request, session);
listener.handle(socket, request, <Express.Session>session).catch(err => { listener.handle(socket, request, <Express.Session>session).catch(err => {
Logger.error(err, 'Error in websocket listener.'); log.error(err, 'Error in websocket listener.');
}); });
}); });
}); });

View File

@ -1,7 +1,7 @@
import mysql, {Connection, FieldInfo, MysqlError, Pool, PoolConnection} from 'mysql'; import mysql, {Connection, FieldInfo, MysqlError, Pool, PoolConnection} from 'mysql';
import config from 'config'; import config from 'config';
import Migration, {MigrationType} from "./Migration"; import Migration, {MigrationType} from "./Migration";
import Logger from "../Logger"; import {log} from "../Logger";
import {Type} from "../Utils"; import {Type} from "../Utils";
export interface QueryResult { export interface QueryResult {
@ -50,7 +50,7 @@ export default class MysqlConnectionManager {
public static async prepare(runMigrations: boolean = true): Promise<void> { public static async prepare(runMigrations: boolean = true): Promise<void> {
if (config.get('mysql.create_database_automatically') === true) { if (config.get('mysql.create_database_automatically') === true) {
const dbName = config.get('mysql.database'); const dbName = config.get('mysql.database');
Logger.info(`Creating database ${dbName}...`); log.info(`Creating database ${dbName}...`);
const connection = mysql.createConnection({ const connection = mysql.createConnection({
host: config.get('mysql.host'), host: config.get('mysql.host'),
user: config.get('mysql.user'), user: config.get('mysql.user'),
@ -65,7 +65,7 @@ export default class MysqlConnectionManager {
}); });
}); });
connection.end(); connection.end();
Logger.info(`Database ${dbName} created!`); log.info(`Database ${dbName} created!`);
} }
this.databaseReady = true; this.databaseReady = true;
@ -97,7 +97,7 @@ export default class MysqlConnectionManager {
} }
this.currentPool.end(() => { this.currentPool.end(() => {
Logger.info('Mysql connection pool ended.'); log.info('Mysql connection pool ended.');
resolve(); resolve();
}); });
this.currentPool = undefined; this.currentPool = undefined;
@ -110,7 +110,9 @@ export default class MysqlConnectionManager {
connection?: Connection, connection?: Connection,
): Promise<QueryResult> { ): Promise<QueryResult> {
return await new Promise<QueryResult>((resolve, reject) => { return await new Promise<QueryResult>((resolve, reject) => {
Logger.dev('SQL:', Logger.isVerboseMode() ? mysql.format(queryString, values) : queryString); log.debug('SQL:', log.settings.minLevel === 'trace' || log.settings.minLevel === 'silly' ?
mysql.format(queryString, values) :
queryString);
(connection ? connection : this.pool).query(queryString, values, (error, results, fields) => { (connection ? connection : this.pool).query(queryString, values, (error, results, fields) => {
if (error !== null) { if (error !== null) {
@ -193,7 +195,7 @@ export default class MysqlConnectionManager {
const currentVersion = await this.getCurrentMigrationVersion(); const currentVersion = await this.getCurrentMigrationVersion();
for (const migration of this.migrations) { for (const migration of this.migrations) {
if (await migration.shouldRun(currentVersion)) { if (await migration.shouldRun(currentVersion)) {
Logger.info('Running migration ', migration.version, migration.constructor.name); log.info('Running migration ', migration.version, migration.constructor.name);
await MysqlConnectionManager.wrapTransaction<void>(async c => { await MysqlConnectionManager.wrapTransaction<void>(async c => {
await migration.install(c); await migration.install(c);
await query('INSERT INTO migrations VALUES(?, ?, NOW())', [ await query('INSERT INTO migrations VALUES(?, ?, NOW())', [
@ -215,7 +217,7 @@ export default class MysqlConnectionManager {
public static async rollbackMigration(migrationId: number = 0): Promise<void> { public static async rollbackMigration(migrationId: number = 0): Promise<void> {
migrationId--; migrationId--;
const migration = this.migrations[migrationId]; const migration = this.migrations[migrationId];
Logger.info('Rolling back migration ', migration.version, migration.constructor.name); log.info('Rolling back migration ', migration.version, migration.constructor.name);
await MysqlConnectionManager.wrapTransaction<void>(async c => { await MysqlConnectionManager.wrapTransaction<void>(async c => {
await migration.rollback(c); await migration.rollback(c);
await query('DELETE FROM migrations WHERE id=?', [migration.version]); await query('DELETE FROM migrations WHERE id=?', [migration.version]);
@ -224,7 +226,7 @@ export default class MysqlConnectionManager {
public static async migrationCommand(args: string[]): Promise<void> { public static async migrationCommand(args: string[]): Promise<void> {
try { try {
Logger.info('Current migration:', await this.getCurrentMigrationVersion()); log.info('Current migration:', await this.getCurrentMigrationVersion());
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
if (args[i] === 'rollback') { if (args[i] === 'rollback') {

View File

@ -1,32 +0,0 @@
import Migration from "../db/Migration";
import {Connection} from "mysql";
import ModelFactory from "../db/ModelFactory";
import Log from "../models/Log";
/**
* Must be the first migration
*/
export default class CreateLogsTable extends Migration {
public async install(connection: Connection): Promise<void> {
await this.query(`CREATE TABLE logs
(
id INT NOT NULL AUTO_INCREMENT,
level TINYINT UNSIGNED NOT NULL,
message TEXT NOT NULL,
log_id BINARY(16),
error_name VARCHAR(128),
error_message VARCHAR(512),
error_stack TEXT,
created_at DATETIME NOT NULL DEFAULT NOW(),
PRIMARY KEY (id)
)`, connection);
}
public async rollback(connection: Connection): Promise<void> {
await this.query('DROP TABLE logs', connection);
}
public registerModels(): void {
ModelFactory.register(Log);
}
}

View File

@ -0,0 +1,12 @@
import Migration from "../db/Migration";
import {Connection} from "mysql";
export default class DropLegacyLogsTable extends Migration {
public async install(connection: Connection): Promise<void> {
await this.query('DROP TABLE IF EXIST logs', connection);
}
public async rollback(): Promise<void> {
// Do nothing
}
}

View File

@ -0,0 +1,11 @@
import Migration from "../db/Migration";
export default class DummyMigration extends Migration {
public async install(): Promise<void> {
// Do nothing
}
public async rollback(): Promise<void> {
// Do nothing
}
}

View File

@ -1,60 +0,0 @@
import Model from "../db/Model";
import {LogLevel, LogLevelKeys} from "../Logger";
import {bufferToUuid} from "../Utils";
export default class Log extends Model {
public readonly id?: number = undefined;
private level?: number = undefined;
public message?: string = undefined;
private log_id?: Buffer = undefined;
private error_name?: string = undefined;
private error_message?: string = undefined;
private error_stack?: string = undefined;
private created_at?: Date = undefined;
protected init(): void {
this.setValidation('level').defined();
this.setValidation('message').defined().between(0, 65535);
this.setValidation('log_id').acceptUndefined().length(16);
this.setValidation('error_name').acceptUndefined().between(0, 128);
this.setValidation('error_message').acceptUndefined().between(0, 512);
this.setValidation('error_stack').acceptUndefined().between(0, 65535);
}
public getLevel(): LogLevelKeys {
if (typeof this.level !== 'number') return 'ERROR';
return <LogLevelKeys>LogLevel[this.level];
}
public setLevel(level: LogLevel): void {
this.level = level;
}
public getLogId(): string | null {
return this.log_id ? bufferToUuid(this.log_id) : null;
}
public setLogId(buffer: Buffer): void {
this.log_id = buffer;
}
public getErrorName(): string {
return this.error_name || '';
}
public getErrorMessage(): string {
return this.error_message || '';
}
public getErrorStack(): string {
return this.error_stack || '';
}
public setError(error?: Error): void {
if (!error) return;
this.error_name = error.name;
this.error_message = error.message;
this.error_stack = error.stack;
}
}

View File

@ -2,10 +2,13 @@ import {Files} from "formidable";
import {Type} from "../Utils"; import {Type} from "../Utils";
import Middleware from "../Middleware"; import Middleware from "../Middleware";
import {FlashMessages} from "../components/SessionComponent"; import {FlashMessages} from "../components/SessionComponent";
import {Logger} from "tslog";
declare global { declare global {
namespace Express { namespace Express {
export interface Request { export interface Request {
log: Logger;
getSession(): Session; getSession(): Session;

View File

@ -3,7 +3,7 @@ import Model from "../src/db/Model";
import {MIGRATIONS} from "./_migrations"; import {MIGRATIONS} from "./_migrations";
import ModelFactory from "../src/db/ModelFactory"; import ModelFactory from "../src/db/ModelFactory";
import {ValidationBag} from "../src/db/Validator"; import {ValidationBag} from "../src/db/Validator";
import Logger from "../src/Logger"; import {log} from "../src/Logger";
import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation"; import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation";
class FakeDummyModel extends Model { class FakeDummyModel extends Model {
@ -99,7 +99,7 @@ let roleFactory: ModelFactory<Role>;
let permissionFactory: ModelFactory<Permission>; let permissionFactory: ModelFactory<Permission>;
beforeAll(async () => { beforeAll(async () => {
Logger.verbose(); log.setSettings({minLevel: "trace"});
MysqlConnectionManager.registerMigrations(MIGRATIONS); MysqlConnectionManager.registerMigrations(MIGRATIONS);
ModelFactory.register(FakeDummyModel); ModelFactory.register(FakeDummyModel);
ModelFactory.register(Post); ModelFactory.register(Post);

View File

@ -1,5 +1,4 @@
import CreateMigrationsTable from "../src/migrations/CreateMigrationsTable"; import CreateMigrationsTable from "../src/migrations/CreateMigrationsTable";
import CreateLogsTable from "../src/migrations/CreateLogsTable";
import CreateUsersAndUserEmailsTable from "../src/auth/migrations/CreateUsersAndUserEmailsTable"; import CreateUsersAndUserEmailsTable from "../src/auth/migrations/CreateUsersAndUserEmailsTable";
import FixUserMainEmailRelation from "../src/auth/migrations/FixUserMainEmailRelation"; import FixUserMainEmailRelation from "../src/auth/migrations/FixUserMainEmailRelation";
import DropNameFromUsers from "../src/auth/migrations/DropNameFromUsers"; import DropNameFromUsers from "../src/auth/migrations/DropNameFromUsers";
@ -7,7 +6,6 @@ import CreateMagicLinksTable from "../src/auth/migrations/CreateMagicLinksTable"
export const MIGRATIONS = [ export const MIGRATIONS = [
CreateMigrationsTable, CreateMigrationsTable,
CreateLogsTable,
CreateUsersAndUserEmailsTable, CreateUsersAndUserEmailsTable,
FixUserMainEmailRelation, FixUserMainEmailRelation,
DropNameFromUsers, DropNameFromUsers,

View File

@ -5621,7 +5621,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0" source-map-url "^0.4.0"
urix "^0.1.0" urix "^0.1.0"
source-map-support@^0.5.17, source-map-support@^0.5.6: source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6:
version "0.5.19" version "0.5.19"
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@ -6058,6 +6058,13 @@ tslib@^1.8.1:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
tslog@^2.10.0:
version "2.10.0"
resolved "https://registry.npmjs.org/tslog/-/tslog-2.10.0.tgz#725fe08e097b31dfabbb1b67929a81b7351dda8f"
integrity sha512-quVCes9HYxR3gHkT1JgDPj56OzbFeMxdKKbJwCMEW2lRSM80Awcr/WbBMTqzu9PI++mD9IDwIT4MJOa5CKh99Q==
dependencies:
source-map-support "^0.5.19"
tsutils@^3.17.1: tsutils@^3.17.1:
version "3.17.1" version "3.17.1"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"