Use AsyncLocalStorage to provide requestId context

This commit is contained in:
Alice Gaudon 2021-01-22 15:54:26 +01:00
parent 6f9ecaa9c4
commit 8fab93e709
23 changed files with 108 additions and 99 deletions

View File

@ -35,6 +35,7 @@
"@types/jest": "^26.0.4", "@types/jest": "^26.0.4",
"@types/mjml": "^4.0.4", "@types/mjml": "^4.0.4",
"@types/mysql": "^2.15.10", "@types/mysql": "^2.15.10",
"@types/nanoid": "^2.1.0",
"@types/node-fetch": "^2.5.7", "@types/node-fetch": "^2.5.7",
"@types/nodemailer": "^6.4.0", "@types/nodemailer": "^6.4.0",
"@types/nunjucks": "^3.1.3", "@types/nunjucks": "^3.1.3",
@ -69,6 +70,7 @@
"geoip-lite": "^1.4.2", "geoip-lite": "^1.4.2",
"mjml": "^4.6.2", "mjml": "^4.6.2",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"nanoid": "^3.1.20",
"nodemailer": "^6.4.6", "nodemailer": "^6.4.6",
"nunjucks": "^3.2.1", "nunjucks": "^3.2.1",
"on-finished": "^2.3.0", "on-finished": "^2.3.0",

View File

@ -16,7 +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 {logger, loggingContextMiddleware} 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);
log.info(`Added websocket listener on ${path}`); logger.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> {
log.info(`${config.get('app.name')} v${this.version} - hi`); logger.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);
}); });
@ -82,6 +82,11 @@ export default abstract class Application implements Extendable<ApplicationCompo
// Init express // Init express
const app = express(); const app = express();
// Logging context
app.use(loggingContextMiddleware);
// Routers
const initRouter = express.Router(); const initRouter = express.Router();
const handleRouter = express.Router(); const handleRouter = express.Router();
app.use(initRouter); app.use(initRouter);
@ -191,7 +196,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':
log.setSettings({minLevel: "trace"}); logger.setSettings({minLevel: "trace"});
break; break;
case '--full-http-requests': case '--full-http-requests':
LogRequestsComponent.logFullHttpRequests(); LogRequestsComponent.logFullHttpRequests();
@ -200,7 +205,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:
log.warn('Unrecognized argument', args[i]); logger.warn('Unrecognized argument', args[i]);
return true; return true;
} }
} }
@ -228,13 +233,13 @@ export default abstract class Application implements Extendable<ApplicationCompo
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
log.info('Stopping application...'); logger.info('Stopping application...');
for (const component of this.components) { for (const component of this.components) {
await component.stop?.(); await component.stop?.();
} }
log.info(`${this.constructor.name} v${this.version} - bye`); logger.info(`${this.constructor.name} v${this.version} - bye`);
} }
private routes(initRouter: Router, handleRouter: Router) { private routes(initRouter: Router, handleRouter: Router) {
@ -242,7 +247,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
if (controller.hasGlobalMiddlewares()) { if (controller.hasGlobalMiddlewares()) {
controller.setupGlobalHandlers(handleRouter); controller.setupGlobalHandlers(handleRouter);
log.info(`Registered global middlewares for controller ${controller.constructor.name}`); logger.info(`Registered global middlewares for controller ${controller.constructor.name}`);
} }
} }
@ -251,7 +256,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);
log.info(`> Registered routes for controller ${controller.constructor.name} at ${controller.getRoutesPrefix()}`); logger.info(`> Registered routes for controller ${controller.constructor.name} at ${controller.getRoutesPrefix()}`);
} }
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 {log} from "./Logger"; import {logger} 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;
log.error(err, `${name} failed to prepare; retrying in 5s...`); logger.error(err, `${name} failed to prepare; retrying in 5s...`);
await sleep(5000); await sleep(5000);
} }
} while (err); } while (err);
log.info(`${name} ready!`); logger.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();
})); }));
log.info(`${thingName} closed.`); logger.info(`${thingName} closed.`);
} catch (e) { } catch (e) {
log.error(e, `An error occurred while closing the ${thingName}.`); logger.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 {log} from "./Logger"; import {logger} from "./Logger";
import FileUploadMiddleware from "./FileUploadMiddleware"; import FileUploadMiddleware from "./FileUploadMiddleware";
import * as querystring from "querystring"; import * as querystring from "querystring";
import {ParsedUrlQueryInput} from "querystring"; import {ParsedUrlQueryInput} from "querystring";
@ -75,7 +75,7 @@ export default abstract class Controller {
protected use(handler: RequestHandler): void { protected use(handler: RequestHandler): void {
this.router.use(this.wrap(handler)); this.router.use(this.wrap(handler));
log.info('Installed anonymous middleware on ' + this.getRoutesPrefix()); logger.info('Installed anonymous middleware on ' + this.getRoutesPrefix());
} }
protected useMiddleware(...middlewares: MiddlewareType<Middleware>[]): void { protected useMiddleware(...middlewares: MiddlewareType<Middleware>[]): void {
@ -87,7 +87,7 @@ export default abstract class Controller {
this.router.use(this.wrap(instance.getRequestHandler())); this.router.use(this.wrap(instance.getRequestHandler()));
} }
log.info('Installed ' + middleware.name + ' on ' + this.getRoutesPrefix()); logger.info('Installed ' + middleware.name + ' on ' + this.getRoutesPrefix());
} }
} }
@ -176,10 +176,10 @@ export default abstract class Controller {
if (!Controller.routes[routeName]) { if (!Controller.routes[routeName]) {
if (typeof routePath === 'string') { if (typeof routePath === 'string') {
log.info(`Route ${routeName} has path ${routePath}`); logger.info(`Route ${routeName} has path ${routePath}`);
Controller.routes[routeName] = routePath; Controller.routes[routeName] = routePath;
} else { } else {
log.warn(`Cannot assign path to route ${routeName}.`); logger.warn(`Cannot assign path to route ${routeName}.`);
} }
} }
} }

View File

@ -1,11 +1,18 @@
import {v4 as uuid} from "uuid";
import {Logger as TsLogger} from "tslog"; import {Logger as TsLogger} from "tslog";
import {AsyncLocalStorage} from "async_hooks";
import {RequestHandler} from "express";
import {nanoid} from "nanoid";
export const log = new TsLogger(); const requestIdStorage: AsyncLocalStorage<string> = new AsyncLocalStorage();
export function makeUniqueLogger(): TsLogger { export const logger = new TsLogger({
const id = uuid(); requestId: (): string => {
return log.getChildLogger({ return requestIdStorage.getStore() as string;
requestId: id, },
});
export const loggingContextMiddleware: RequestHandler = (req, res, next) => {
requestIdStorage.run(nanoid(8), () => {
next();
}); });
} };

View File

@ -27,10 +27,10 @@ import CsrfProtectionComponent from "./components/CsrfProtectionComponent";
import MailController from "./mail/MailController"; import MailController from "./mail/MailController";
import WebSocketServerComponent from "./components/WebSocketServerComponent"; import WebSocketServerComponent from "./components/WebSocketServerComponent";
import Controller from "./Controller"; import Controller from "./Controller";
import packageJson = require('../package.json');
import AccountController from "./auth/AccountController"; import AccountController from "./auth/AccountController";
import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/MakeMagicLinksSessionNotUniqueMigration"; import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/MakeMagicLinksSessionNotUniqueMigration";
import AddUsedToMagicLinksMigration from "./auth/magic_link/AddUsedToMagicLinksMigration"; import AddUsedToMagicLinksMigration from "./auth/magic_link/AddUsedToMagicLinksMigration";
import packageJson = require('../package.json');
export const MIGRATIONS = [ export const MIGRATIONS = [
CreateMigrationsTable, CreateMigrationsTable,

View File

@ -1,5 +1,5 @@
import {TooManyRequestsHttpError} from "./HttpError"; import {TooManyRequestsHttpError} from "./HttpError";
import {log} from "./Logger"; import {logger} from "./Logger";
export default class Throttler { export default class Throttler {
private static readonly throttles: Record<string, Throttle | undefined> = {}; private static readonly throttles: Record<string, Throttle | undefined> = {};
@ -81,7 +81,7 @@ class Throttle {
trigger.jailed = currentDate; trigger.jailed = currentDate;
const unjailedIn = trigger.jailed + this.jailPeriod - currentDate; const unjailedIn = trigger.jailed + this.jailPeriod - currentDate;
log.info(`Jail ${this.jailName} triggered by ${id} and will be unjailed in ${unjailedIn}ms.`); logger.info(`Jail ${this.jailName} triggered by ${id} and will be unjailed in ${unjailedIn}ms.`);
return this.throw(unjailedIn); return this.throw(unjailedIn);
} }

View File

@ -1,5 +1,3 @@
import * as crypto from "crypto";
export async function sleep(ms: number): Promise<void> { export async function sleep(ms: number): Promise<void> {
return await new Promise(resolve => { return await new Promise(resolve => {
setTimeout(() => resolve(), ms); setTimeout(() => resolve(), ms);
@ -23,17 +21,6 @@ export abstract class WrappingError extends Error {
} }
} }
export function cryptoRandomDictionary(size: number, dictionary: string): string {
const randomBytes = crypto.randomBytes(size);
const output = new Array(size);
for (let i = 0; i < size; i++) {
output[i] = dictionary[Math.floor(randomBytes[i] / 255 * dictionary.length)];
}
return output.join('');
}
export type Type<T> = { new(...args: never[]): T }; export type Type<T> = { new(...args: never[]): T };
export function bufferToUuid(buffer: Buffer): string { export function bufferToUuid(buffer: Buffer): string {

View File

@ -11,7 +11,7 @@ import UserEmail from "./models/UserEmail";
import MagicLinkController from "./magic_link/MagicLinkController"; import MagicLinkController from "./magic_link/MagicLinkController";
import {MailTemplate} from "../mail/Mail"; import {MailTemplate} from "../mail/Mail";
import {ADD_EMAIL_MAIL_TEMPLATE} from "../Mails"; import {ADD_EMAIL_MAIL_TEMPLATE} from "../Mails";
import {log} from "../Logger"; import {logger} from "../Logger";
import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType"; import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType";
export default class AccountController extends Controller { export default class AccountController extends Controller {
@ -65,7 +65,7 @@ export default class AccountController extends Controller {
await passwordComponent.setPassword(req.body.new_password, 'new_password'); await passwordComponent.setPassword(req.body.new_password, 'new_password');
await user.save(); await user.save();
log.debug('user ' + user.id + ' changed their password and was saved.'); logger.debug('user ' + user.id + ' changed their password and was saved.');
req.flash('success', 'Password changed successfully.'); req.flash('success', 'Password changed successfully.');
res.redirectBack(Controller.route('account')); res.redirectBack(Controller.route('account'));

View File

@ -17,7 +17,7 @@ import AuthMagicLinkActionType from "./AuthMagicLinkActionType";
import {QueryVariable} from "../../db/MysqlConnectionManager"; import {QueryVariable} from "../../db/MysqlConnectionManager";
import UserNameComponent from "../models/UserNameComponent"; import UserNameComponent from "../models/UserNameComponent";
import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent"; import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent";
import {log} from "../../Logger"; import {logger} from "../../Logger";
export default class MagicLinkController<A extends Application> extends Controller { export default class MagicLinkController<A extends Application> extends Controller {
public static async sendMagicLink( public static async sendMagicLink(
@ -233,7 +233,7 @@ export default class MagicLinkController<A extends Application> extends Controll
break; break;
} }
default: default:
log.warn('Unknown magic link action type ' + magicLink.action_type); logger.warn('Unknown magic link action type ' + magicLink.action_type);
break; break;
} }
} }

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 {log} from "../Logger"; import {logger} 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> {
log.info(`> ${command}`); logger.info(`> ${command}`);
log.info(child_process.execSync(command).toString()); logger.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(req.log.error); this.update(req.body).catch(logger.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 }) {
log.info('Update params:', params); logger.info('Update params:', params);
try { try {
log.info('Starting auto update...'); logger.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();
log.info('Success!'); logger.info('Success!');
} catch (e) { } catch (e) {
log.error(e, 'An error occurred while running the auto update.'); logger.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 {log, makeUniqueLogger} from "../Logger"; import {logger} 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, () => {
log.info(`Web server running on ${this.addr}:${this.port}.`); logger.info(`Web server running on ${this.addr}:${this.port}.`);
}); });
// Proxy // Proxy
@ -41,7 +41,6 @@ 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 {log} from "../Logger"; import {logger} 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;
log.info('Http requests will be logged with more details.'); logger.info('Http requests will be logged with more details.');
} }
public static logRequest( public static logRequest(
@ -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 req.log.error(err, requestObj, err).requestId; return logger.error(err, requestObj, err).requestId;
} else { } else {
return req.log.error(new Error(String(err)), requestObj).requestId; return logger.error(new Error(String(err)), requestObj).requestId;
} }
} else { } else {
req.log.info(requestObj); logger.info(requestObj);
} }
} else { } else {
let codeDescription = ''; let codeDescription = '';
@ -59,15 +59,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 req.log.info(err.name, logStr).requestId; return logger.info(err.name, logStr).requestId;
} else { } else {
return req.log.error(err, logStr, additionalStr, err).requestId; return logger.error(err, logStr, additionalStr, err).requestId;
} }
} else { } else {
return req.log.error(new Error(String(err)), logStr).requestId; return logger.error(new Error(String(err)), logStr).requestId;
} }
} else { } else {
req.log.info(logStr); logger.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 {log} from "../Logger"; import {logger} 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) {
log.warn('Couldn\'t determine coreVersion.', e); logger.warn('Couldn\'t determine coreVersion.', e);
} }
const opts = { const opts = {

View File

@ -2,6 +2,7 @@ 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 {
@ -28,10 +29,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;
req.log.debug('Prev url set to', session.previousUrl); logger.debug('Prev url set to', session.previousUrl);
session.save((err) => { session.save((err) => {
if (err) { if (err) {
req.log.error(err, 'Error while saving session'); logger.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 {log} from "../Logger"; import {logger} 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) => {
log.error(err, 'An error occurred with redis.'); logger.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 {log} from "../Logger"; import {logger} 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(),
}, () => { }, () => {
log.info(`Websocket server started over webserver.`); logger.info(`Websocket server started over webserver.`);
}).on('error', (err) => { }).on('error', (err) => {
log.error(err, 'An error occurred in the websocket server.'); logger.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 => {
log.error(err, 'Error in websocket listener.'); logger.error(err, 'Error in websocket listener.');
}); });
return; return;
} }
log.debug(`Websocket on ${request.url}`); logger.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) {
log.error(err, 'Error while initializing session in websocket.'); logger.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, (<Request>request).session).catch(err => { listener.handle(socket, request, (<Request>request).session).catch(err => {
log.error(err, 'Error in websocket listener.'); logger.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 {log} from "../Logger"; import {logger} 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');
log.info(`Creating database ${dbName}...`); logger.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();
log.info(`Database ${dbName} created!`); logger.info(`Database ${dbName} created!`);
} }
this.databaseReady = true; this.databaseReady = true;
@ -97,7 +97,7 @@ export default class MysqlConnectionManager {
} }
this.currentPool.end(() => { this.currentPool.end(() => {
log.info('Mysql connection pool ended.'); logger.info('Mysql connection pool ended.');
resolve(); resolve();
}); });
this.currentPool = undefined; this.currentPool = undefined;
@ -110,7 +110,7 @@ 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) => {
log.debug('SQL:', log.settings.minLevel === 'trace' || log.settings.minLevel === 'silly' ? logger.debug('SQL:', logger.settings.minLevel === 'trace' || logger.settings.minLevel === 'silly' ?
mysql.format(queryString, values) : mysql.format(queryString, values) :
queryString); queryString);
@ -195,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)) {
log.info('Running migration ', migration.version, migration.constructor.name); logger.info('Running migration ', migration.version, migration.constructor.name);
await MysqlConnectionManager.wrapTransaction<void>(async c => { await MysqlConnectionManager.wrapTransaction<void>(async c => {
migration.setCurrentConnection(c); migration.setCurrentConnection(c);
await migration.install(); await migration.install();
@ -219,7 +219,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];
log.info('Rolling back migration ', migration.version, migration.constructor.name); logger.info('Rolling back migration ', migration.version, migration.constructor.name);
await MysqlConnectionManager.wrapTransaction<void>(async c => { await MysqlConnectionManager.wrapTransaction<void>(async c => {
migration.setCurrentConnection(c); migration.setCurrentConnection(c);
await migration.rollback(); await migration.rollback();
@ -230,7 +230,7 @@ export default class MysqlConnectionManager {
public static async migrationCommand(args: string[]): Promise<void> { public static async migrationCommand(args: string[]): Promise<void> {
try { try {
log.info('Current migration:', await this.getCurrentMigrationVersion()); logger.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

@ -5,7 +5,7 @@ import {Environment} 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 {log} from "../Logger"; import {logger} 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);
} }
log.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`); logger.info(`Mail ready to be distributed via ${config.get('mail.host')}:${config.get('mail.port')}`);
} }
public static end(): void { public static end(): void {
@ -110,7 +110,7 @@ export default class Mail {
this.data.app = config.get('app'); this.data.app = config.get('app');
// Log // Log
log.debug('Send mail', this.options); logger.debug('Send mail', this.options);
// Render email // Render email
this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk', this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk',

View File

@ -6,15 +6,15 @@ process.env['NODE_CONFIG_DIR'] =
+ delimiter + delimiter
+ (process.env['NODE_CONFIG_DIR'] || __dirname + '/../../config/'); + (process.env['NODE_CONFIG_DIR'] || __dirname + '/../../config/');
import {log} from "./Logger"; import {logger} from "./Logger";
import TestApp from "./TestApp"; import TestApp from "./TestApp";
import config from "config"; import config from "config";
(async () => { (async () => {
log.debug('Config path:', process.env['NODE_CONFIG_DIR']); logger.debug('Config path:', process.env['NODE_CONFIG_DIR']);
const app = new TestApp(config.get<string>('listen_addr'), config.get<number>('port')); const app = new TestApp(config.get<string>('listen_addr'), config.get<number>('port'));
await app.start(); await app.start();
})().catch(err => { })().catch(err => {
log.error(err); logger.error(err);
}); });

View File

@ -2,16 +2,12 @@ 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";
import {Session, SessionData} from "express-session"; import {Session, SessionData} from "express-session";
import {PasswordAuthProofSessionData} from "../auth/password/PasswordAuthProof"; import {PasswordAuthProofSessionData} from "../auth/password/PasswordAuthProof";
declare global { declare global {
namespace Express { namespace Express {
export interface Request { export interface Request {
log: Logger;
getSession(): Session & Partial<SessionData>; getSession(): Session & Partial<SessionData>;
getSessionOptional(): Session & Partial<SessionData> | undefined; getSessionOptional(): Session & Partial<SessionData> | undefined;

View File

@ -2,7 +2,7 @@ import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
import Model from "../src/db/Model"; import Model from "../src/db/Model";
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 {log} from "../src/Logger"; import {logger} from "../src/Logger";
import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation"; import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation";
import {MIGRATIONS} from "../src/TestApp"; import {MIGRATIONS} from "../src/TestApp";
import config from "config"; import config from "config";
@ -104,7 +104,7 @@ beforeAll(async () => {
await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database')); await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database'));
await MysqlConnectionManager.endPool(); await MysqlConnectionManager.endPool();
log.setSettings({minLevel: "trace"}); logger.setSettings({minLevel: "trace"});
MysqlConnectionManager.registerMigrations(MIGRATIONS); MysqlConnectionManager.registerMigrations(MIGRATIONS);
ModelFactory.register(FakeDummyModel); ModelFactory.register(FakeDummyModel);
ModelFactory.register(Post); ModelFactory.register(Post);

View File

@ -742,6 +742,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/nanoid@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/nanoid/-/nanoid-2.1.0.tgz#41edfda78986e9127d0dc14de982de766f994020"
integrity sha512-xdkn/oRTA0GSNPLIKZgHWqDTWZsVrieKomxJBOQUK9YDD+zfSgmwD5t4WJYra5S7XyhTw7tfvwznW+pFexaepQ==
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.5.7": "@types/node-fetch@^2.5.7":
version "2.5.8" version "2.5.8"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
@ -4815,6 +4822,11 @@ mysql@^2.18.1:
safe-buffer "5.1.2" safe-buffer "5.1.2"
sqlstring "2.3.1" sqlstring "2.3.1"
nanoid@^3.1.20:
version "3.1.20"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"