diff --git a/build/publish.ts b/build/publish.ts index 317fc0ce7..29f68ec2f 100644 --- a/build/publish.ts +++ b/build/publish.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable consistent-return */ /* eslint-disable no-await-in-loop */ diff --git a/build/start.ts b/build/start.ts index 901bcddf5..3733ff5e7 100644 --- a/build/start.ts +++ b/build/start.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /* eslint-disable import/no-extraneous-dependencies */ import os from 'os'; import path from 'path'; diff --git a/build/utils.ts b/build/utils.ts index a05a4ae8a..c13dfff47 100644 --- a/build/utils.ts +++ b/build/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /* eslint-disable import/no-extraneous-dependencies */ import path from 'path'; import globby from 'globby'; diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index e260acf27..3550a18af 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.13.5", + "version": "2.13.6", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", @@ -31,6 +31,7 @@ "serialize-javascript": "^4.0.0", "sockjs": "^0.3.20", "superagent": "^6.1.0", + "supports-color": "^7.2.0", "systeminformation": "^4.27.3", "yargs": "^15.4.1" }, @@ -49,6 +50,7 @@ "@types/serialize-javascript": "^4.0.0", "@types/sockjs": "^0.3.32", "@types/superagent": "^4.1.8", + "@types/supports-color": "^5.3.0", "@types/yargs": "^15.0.5" }, "scripts": { diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index ec5b4fd6e..cc4370173 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -3,7 +3,9 @@ import fs from 'fs'; import path from 'path'; import yaml from 'js-yaml'; -import { argv } from 'yargs'; +import { Logger } from '../logger'; + +const logger = new Logger('common', true); export const builtinLib = [ 'jwt', 'download', 'i18n', 'mail', 'useragent', @@ -46,14 +48,12 @@ export async function handler(pending: string[], fail: string[]) { const p = path.resolve(i, 'handler.js'); if (fs.existsSync(p) && !fail.includes(i)) { try { - console.log(`Handler init: ${i}`); - console.time(`Handler init: ${i}`); + logger.info('Handler init: %s', i); eval('require')(p); - console.timeEnd(`Handler init: ${i}`); } catch (e) { fail.push(i); - console.error(`Handler Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Handler Load Fail: %s', i); + logger.error('%o', e); } } } @@ -72,11 +72,11 @@ export async function locale(pending: string[], fail: string[]) { locales[file.split('.')[0]] = yaml.safeLoad(content); } global.Hydro.lib.i18n(locales); - console.log(`Locale init: ${i}`); + logger.info('Locale init: %s', i); } catch (e) { fail.push(i); - console.error(`Locale Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Locale Load Fail: %s', i); + logger.error('%o', e); } } } @@ -105,8 +105,8 @@ export async function setting(pending: string[], fail: string[], modelSetting: t ); } } catch (e) { - console.error(`Config Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Config Load Fail: %s', i); + logger.error(e); } } } @@ -124,11 +124,11 @@ export async function template(pending: string[], fail: string[]) { path.resolve(p, file), ).toString(); } - console.log(`Template init: ${i}`); + logger.info('Template init: %s', i); } catch (e) { fail.push(i); - console.error(`Template Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Template Load Fail: %s', i); + logger.error(e); } } } @@ -152,14 +152,12 @@ export async function model(pending: string[], fail: string[]) { const p = path.resolve(i, 'model.js'); if (fs.existsSync(p) && !fail.includes(i)) { try { - console.log(`Model init: ${i}`); - console.time(`Model init: ${i}`); + logger.info('Model init: %s', i); eval('require')(p); - console.timeEnd(`Model init: ${i}`); } catch (e) { fail.push(i); - console.error(`Model Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Model Load Fail: %s', i); + logger.error(e); } } } @@ -170,14 +168,12 @@ export async function lib(pending: string[], fail: string[]) { const p = path.resolve(i, 'lib.js'); if (fs.existsSync(p) && !fail.includes(i)) { try { - console.log(`Lib init: ${i}`); - console.time(`Lib init: ${i}`); + logger.info('Lib init: %s', i); eval('require')(p); - console.timeEnd(`Lib init: ${i}`); } catch (e) { fail.push(i); - console.error(`Lib Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Lib Load Fail: %s', i); + logger.error(e); } } } @@ -188,14 +184,12 @@ export async function service(pending: string[], fail: string[]) { const p = path.resolve(i, 'service.js'); if (fs.existsSync(p) && !fail.includes(i)) { try { - console.log(`Service init: ${i}`); - console.time(`Service init: ${i}`); + logger.info('Service init: %s', i); eval('require')(p); - console.timeEnd(`Service init: ${i}`); } catch (e) { fail.push(i); - console.error(`Service Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Service Load Fail: %s', i); + logger.error(e); } } } @@ -206,13 +200,12 @@ export async function script(pending: string[], fail: string[], active: string[] const p = path.resolve(i, 'script.js'); if (fs.existsSync(p) && !fail.includes(i)) { try { - console.time(`Script init: ${i}`); + logger.info('Script init: %s', i); eval('require')(p); - console.timeEnd(`Script init: ${i}`); } catch (e) { fail.push(i); - console.error(`Script Load Fail: ${i}`); - if (argv.debug) console.error(e); + logger.error('Script Load Fail: %s', i); + logger.error(e); } } active.push(i); diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 25060e6a6..ae5c61cae 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -47,11 +47,7 @@ export async function load(call: Entry) { fs.removeSync(lockfile); }); await new Promise((resolve) => { - const h = () => { - console.log('Database connected'); - resolve(); - }; - bus.once('database/connect', h); + bus.once('database/connect', resolve); require('../service/db'); }); require('../service/monitor'); diff --git a/packages/hydrooj/src/entry/setup.ts b/packages/hydrooj/src/entry/setup.ts index 088c769e7..89b313072 100644 --- a/packages/hydrooj/src/entry/setup.ts +++ b/packages/hydrooj/src/entry/setup.ts @@ -7,6 +7,9 @@ import Body from 'koa-body'; import cache from 'koa-static-cache'; import mongodb from 'mongodb'; import { argv } from 'yargs'; +import { Logger } from '../logger'; + +const logger = new Logger('setup'); const listenPort = argv.port || 8888; @@ -141,5 +144,5 @@ export function load() { return get(ctx); }); server.listen(listenPort); - console.log('Server listening at:', listenPort); + logger.success('Server listening at: %d', listenPort); } diff --git a/packages/hydrooj/src/entry/worker.ts b/packages/hydrooj/src/entry/worker.ts index c1d757409..5e8c3ca04 100644 --- a/packages/hydrooj/src/entry/worker.ts +++ b/packages/hydrooj/src/entry/worker.ts @@ -20,11 +20,7 @@ export async function load() { uistatic(pending, fail), ]); await new Promise((resolve) => { - const h = () => { - console.log('Database connected'); - resolve(); - }; - bus.once('database/connect', h); + bus.once('database/connect', resolve); require('../service/db'); }); for (const i of builtinLib) require(`../lib/${i}`); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 393ea97c5..8942513d1 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -510,6 +510,7 @@ declare global { stat: any, ui: UI, error: typeof import('./error'), + Logger: typeof import('./logger'), locales: Dict>, }, addons: string[], diff --git a/packages/hydrooj/src/loader.ts b/packages/hydrooj/src/loader.ts index f959bd597..f1fa1a35c 100644 --- a/packages/hydrooj/src/loader.ts +++ b/packages/hydrooj/src/loader.ts @@ -40,14 +40,14 @@ if (!global.Hydro) { global.addons = []; } -if (argv.debug) { - console.log(process.argv); - console.log(argv); -} - +// eslint-disable-next-line import/first +import { Logger } from './logger'; // eslint-disable-next-line import/first import * as bus from './service/bus'; +const logger = new Logger('loader'); +logger.debug('%o', argv); + async function terminate() { try { await bus.parallel('app/exit'); @@ -113,9 +113,9 @@ async function executeCommand(input: string) { } async function reload(count = 1) { - console.log('Reloading'); + logger.info('Reloading'); await stopWorker(); - console.log('Worker stopped'); + logger.info('Worker stopped'); await startWorker(count); } @@ -176,13 +176,13 @@ export function addon(addonPath: string) { if (fs.existsSync(publicPath)) fs.copySync(publicPath, publicTemp); global.addons.push(targetPath); } catch (e) { - console.error('Addon load fail: ', e); + logger.error('Addon load fail: ', e); throw e; } } else throw new Error(`Addon not found: ${addonPath}`); } -process.on('unhandledRejection', (e) => console.error(e)); +process.on('unhandledRejection', logger.error); process.on('SIGINT', terminate); export async function load() { @@ -191,7 +191,7 @@ export async function load() { process.on('message', messageHandler); cluster.on('message', messageHandler); if (cluster.isMaster || argv.startAsMaster) { - console.log(`Master ${process.pid} Starting`); + logger.info(`Master ${process.pid} Starting`); process.stdin.setEncoding('utf8'); process.stdin.on('data', (buf) => { const input = buf.toString(); @@ -205,33 +205,33 @@ export async function load() { } }); const cnt = await entry({ entry: 'master' }); - console.log('Master started'); + logger.info('Master started'); cluster.on('exit', (worker, code, signal) => { - console.log(`Worker ${worker.process.pid} ${worker.id} exit: ${code} ${signal}`); + logger.info(`Worker ${worker.process.pid} ${worker.id} exit: ${code} ${signal}`); }); cluster.on('disconnect', (worker) => { - console.log(`Worker ${worker.process.pid} ${worker.id} disconnected`); + logger.info(`Worker ${worker.process.pid} ${worker.id} disconnected`); }); cluster.on('listening', (worker, address) => { - console.log(`Worker ${worker.process.pid} ${worker.id} listening at `, address); + logger.info(`Worker ${worker.process.pid} ${worker.id} listening at `, address); }); cluster.on('online', (worker) => { - console.log(`Worker ${worker.process.pid} ${worker.id} is online`); + logger.success(`Worker ${worker.process.pid} ${worker.id} is online`); }); await startWorker(cnt); } else { global.addons = JSON.parse(Buffer.from(argv.addons as string, 'base64').toString()); - console.log(global.addons); + logger.info('%o', global.addons); if (argv.entry) { - console.log(`Worker ${process.pid} Starting as ${argv.entry}`); + logger.info(`Worker ${process.pid} Starting as ${argv.entry}`); await entry({ entry: argv.entry as string }); - console.log(`Worker ${process.pid} Started as ${argv.entry}`); + logger.success(`Worker ${process.pid} Started as ${argv.entry}`); } else { if (argv.firstWorker) cluster.isFirstWorker = true; else cluster.isFirstWorker = false; - console.log(`Worker ${process.pid} Starting`); + logger.info(`Worker ${process.pid} Starting`); await entry({ entry: 'worker' }); - console.log(`Worker ${process.pid} Started`); + logger.success(`Worker ${process.pid} Started`); } } if (global.gc) global.gc(); @@ -245,7 +245,7 @@ export async function loadCli() { if (argv.pandora || !module.parent) { const func = argv._[0] === 'cli' ? load : loadCli; func().catch((e) => { - console.error(e); + logger.error(e); process.exit(1); }); } diff --git a/packages/hydrooj/src/logger.ts b/packages/hydrooj/src/logger.ts new file mode 100644 index 000000000..063f8118f --- /dev/null +++ b/packages/hydrooj/src/logger.ts @@ -0,0 +1,142 @@ +import { inspect, InspectOptions, format } from 'util'; +import { argv } from 'yargs'; +import { stderr } from 'supports-color'; + +namespace Time { + export const second = 1000; + export const minute = second * 60; + export const hour = minute * 60; + export const day = hour * 24; + export const week = day * 7; + export function formatTimeShort(ms: number) { + const abs = Math.abs(ms); + if (abs >= day - hour / 2) { + return `${Math.round(ms / day)}d`; + } if (abs >= hour - minute / 2) { + return `${Math.round(ms / hour)}h`; + } if (abs >= minute - second / 2) { + return `${Math.round(ms / minute)}m`; + } if (abs >= second) { + return `${Math.round(ms / second)}s`; + } + return `${ms}ms`; + } +} + +const colors = stderr.level < 2 ? [6, 2, 3, 4, 5, 1] : [ + 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, + 63, 68, 69, 74, 75, 76, 77, 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, + 129, 134, 135, 148, 149, 160, 161, 162, 163, 164, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221, +]; + +const instances: Record = {}; + +type LogFunction = (format: any, ...param: any[]) => void + +type LogType = 'success' | 'error' | 'info' | 'warn' | 'debug' + +export interface Logger extends Record { } + +export class Logger { + static readonly SUCCESS = 1 + + static readonly ERROR = 1 + + static readonly INFO = 2 + + static readonly WARN = 2 + + static readonly DEBUG = 3 + + static baseLevel = argv.debug ? 3 : 2 + + static showDiff = false + + static levels: Record = {} + + static lastTime = 0 + + static options: InspectOptions = { + colors: stderr.hasBasic, + } + + static formatters: Record string> = { + c: Logger.prototype.color, + C: (value) => Logger.color(15, value, ';1'), + o: (value) => inspect(value, Logger.options).replace(/\s*\n\s*/g, ' '), + } + + static color(code: number, value: any, decoration = '') { + if (!Logger.options.colors) return `${value}`; + return `\u001B[3${code < 8 ? code : `8;5;${code}`}${decoration}m${value}\u001B[0m`; + } + + private code: number + + private displayName: string + + public stream: NodeJS.WritableStream = process.stderr + + constructor(public name: string, private showDiff = false) { + if (name in instances) return instances[name]; + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = ((hash << 3) - hash) + name.charCodeAt(i); + hash |= 0; + } + instances[name] = this; + this.code = colors[Math.abs(hash) % colors.length]; + this.displayName = name ? this.color(`${name} `, ';1') : ''; + this.createMethod('success', '[S] ', Logger.SUCCESS); + this.createMethod('error', '[E] ', Logger.ERROR); + this.createMethod('info', '[I] ', Logger.INFO); + this.createMethod('warn', '[W] ', Logger.WARN); + this.createMethod('debug', '[D] ', Logger.DEBUG); + } + + private color(value: any, decoration = '') { + return Logger.color(this.code, value, decoration); + } + + private createMethod(name: LogType, prefix: string, minLevel: number) { + this[name] = (...args: [any, ...any[]]) => { + if (this.level < minLevel) return; + this.stream.write(`${prefix + this.displayName + this.format(...args)}\n`); + }; + } + + get level() { + return Logger.levels[this.name] ?? Logger.baseLevel; + } + + extend = (namespace: string, showDiff = this.showDiff) => new Logger(`${this.name}:${namespace}`, showDiff) + + format: (format: any, ...param: any[]) => string = (...args) => { + if (args[0] instanceof Error) args[0] = args[0].stack || args[0].message; + else if (typeof args[0] !== 'string') args.unshift('%O'); + let index = 0; + args[0] = (args[0] as string).replace(/%([a-zA-Z%])/g, (match, fmt) => { + if (match === '%%') return '%'; + index += 1; + const formatter = Logger.formatters[fmt]; + if (typeof formatter === 'function') { + match = formatter.call(this, args[index]); + args.splice(index, 1); + index -= 1; + } + return match; + }).split('\n').join('\n '); + if (Logger.showDiff || this.showDiff) { + const now = Date.now(); + if (Logger.lastTime) { + args.push(this.color(`+${Time.formatTimeShort(now - Logger.lastTime)}`)); + } + Logger.lastTime = now; + } + return format(...args); + } +} + +global.Hydro.Logger = { Logger }; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 01b04d144..2ae1e4c18 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -528,8 +528,8 @@ export function count(domainId: string, query: any, type: Type = document.TYPE_C return document.count(domainId, type, query); } -export function getMulti( - domainId: string, query: FilterQuery = {}, type?: K, +export function getMulti( + domainId: string, query: FilterQuery = {}, type?: K, ) { return document.getMulti(domainId, type || document.TYPE_CONTEST, query); } diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index bcc113e9c..97a8f5d70 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -22,7 +22,7 @@ export const TYPE_TRAINING = 40; export const TYPE_FILE = 50; export const TYPE_HOMEWORK = 60; -export interface DocumentType { +export interface DocType { [TYPE_PROBLEM]: Pdoc, [TYPE_PROBLEM_SOLUTION]: any, [TYPE_PROBLEM_LIST]: any, @@ -35,13 +35,13 @@ export interface DocumentType { [TYPE_HOMEWORK]: Tdoc, } -export async function add( +export async function add( domainId: string, content: string, owner: number, - docType: K, docId: DocumentType[K]['docId'], + docType: K, docId: DocType[K]['docId'], parentType?: number | null, parentId?: DocID, args?: any, ): Promise -export async function add( +export async function add( domainId: string, content: string, owner: number, docType: K, docId: null, parentType?: number, parentId?: DocID, @@ -73,13 +73,13 @@ export async function add( return docId || res.insertedId; } -export function get(domainId: string, docType: K, docId: DocumentType[K]['docId']) { +export function get(domainId: string, docType: K, docId: DocType[K]['docId']) { return coll.findOne({ domainId, docType, docId }); } -export async function set( - domainId: string, docType: K, docId: DocumentType[K]['docId'], $set: Partial, -): Promise { +export async function set( + domainId: string, docType: K, docId: DocType[K]['docId'], $set: Partial, +): Promise { await bus.parallel('document/set', domainId, docType, docId, $set); const res = await coll.findOneAndUpdate( { domainId, docType, docId }, @@ -89,8 +89,8 @@ export async function set( return res.value; } -export function deleteOne( - domainId: string, docType: K, docId: DocumentType[K]['docId'], +export function deleteOne( + domainId: string, docType: K, docId: DocType[K]['docId'], ) { return Promise.all([ collStatus.deleteMany({ domainId, docType, docId }), @@ -98,27 +98,27 @@ export function deleteOne( ]); } -export function deleteMulti( - domainId: string, docType: K, query?: FilterQuery, +export function deleteMulti( + domainId: string, docType: K, query?: FilterQuery, ) { return coll.deleteMany({ ...query, domainId, docType }); } -export function deleteMultiStatus( - domainId: string, docType: K, query?: FilterQuery, +export function deleteMultiStatus( + domainId: string, docType: K, query?: FilterQuery, ) { return collStatus.deleteMany({ ...query, domainId, docType }); } -export function getMulti( - domainId: string, docType: K, query?: FilterQuery, -): Cursor { +export function getMulti( + domainId: string, docType: K, query?: FilterQuery, +): Cursor { return coll.find({ ...query, docType, domainId }); } -export async function inc( - domainId: string, docType: K, docId: DocumentType[K]['docId'], - key: NumberKeys, value: number, +export async function inc( + domainId: string, docType: K, docId: DocType[K]['docId'], + key: NumberKeys, value: number, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, @@ -128,11 +128,11 @@ export async function inc( return res.value; } -type Q = NumberKeys +type Q = NumberKeys -export async function incAndSet( - domainId: string, docType: K, docId: DocumentType[K]['docId'], - key: NumberKeys, value: number, args: Partial, +export async function incAndSet( + domainId: string, docType: K, docId: DocType[K]['docId'], + key: NumberKeys, value: number, args: Partial, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, @@ -142,15 +142,15 @@ export async function incAndSet( return res.value; } -export function count( - domainId: string, docType: K, query?: FilterQuery, +export function count( + domainId: string, docType: K, query?: FilterQuery, ) { return coll.find({ ...query, docType, domainId }).count(); } -export async function push( - domainId: string, docType: K, docId: DocumentType[K]['docId'], - key: keyof DocumentType[K], content: string, owner: number, args: DocumentType[K][T][0] = {}, +export async function push( + domainId: string, docType: K, docId: DocType[K]['docId'], + key: keyof DocType[K], content: string, owner: number, args: DocType[K][T][0] = {}, ) { const _id = new ObjectID(); const doc = await coll.findOneAndUpdate( @@ -167,10 +167,10 @@ export async function push( - domainId: string, docType: K, docId: DocumentType[K]['docId'], +export async function pull( + domainId: string, docType: K, docId: DocType[K]['docId'], setKey: string, contents: string[], -): Promise { +): Promise { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $pull: { [setKey]: { $in: contents } } }, diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index 241e9e2d0..315308381 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -1,10 +1,14 @@ /* eslint-disable no-await-in-loop */ import cluster from 'cluster'; -import { Db, ObjectID } from 'mongodb'; +import { Db, UpdateQuery } from 'mongodb'; +import { Logger } from '../logger'; import { Mdoc, Rdoc, User } from '../interface'; +// only typings. +// eslint-disable-next-line import/no-cycle +import { DocType } from '../model/document'; const _hooks: Record any>> = {}; -const _disposables = []; +const logger = new Logger('bus', true); function isBailed(value: any) { return value !== null && value !== false && value !== undefined; @@ -12,7 +16,7 @@ function isBailed(value: any) { export type Disposable = () => void -interface EventMap { +export interface EventMap { 'app/started': () => void 'app/exit': () => Promise | void 'dispose': () => void @@ -22,15 +26,16 @@ interface EventMap { 'user/message': (uid: number, mdoc: Mdoc, udoc: User) => void 'document/add': (doc: any) => Promise | void - 'document/set': (domainId: string, docType: number, docId: ObjectID | string | number, $set: any) => Promise | void + 'document/set': + (domainId: string, docType: T, docId: DocType[T], $set: UpdateQuery['$set']) => Promise | void - 'record/change': (rdoc: Rdoc, $set?: any, $push?: any) => void + 'record/change': (rdoc: Rdoc, $set?: UpdateQuery['$set'], $push?: UpdateQuery['$push']) => void } function getHooks(name: K) { const hooks = _hooks[name] || (_hooks[name] = []); if (hooks.length >= 128) { - console.warn( + logger.warn( 'max listener count (128) for event "%s" exceeded, which may be caused by a memory leak', name, ); @@ -49,23 +54,17 @@ export function removeListener(name: K, listener: Even export function addListener(name: K, listener: EventMap[K]) { getHooks(name).push(listener); - const dispose = () => removeListener(name, listener); - _disposables.push(name === 'dispose' ? listener as Disposable : dispose); - return dispose; + return () => removeListener(name, listener); } export function prependListener(name: K, listener: EventMap[K]) { getHooks(name).unshift(listener); - const dispose = () => removeListener(name, listener); - _disposables.push(name === 'dispose' ? listener as Disposable : dispose); - return dispose; + return () => removeListener(name, listener); } export function once(name: K, listener: EventMap[K]) { - // @ts-ignore - const dispose = addListener(name, (...args: Parameters) => { + const dispose = addListener(name, function _listener(...args: any[]) { dispose(); - // @ts-ignore return listener.apply(this, args); }); return dispose; @@ -91,12 +90,12 @@ export function emit(name: K, ...args: Parameters(name: K, ...args: Parameters): Promise> { - for (const callback of _hooks[name] || []) { - const result = await callback.apply(this, args); - if (isBailed(result)) return result; +export async function serial(name: K, ...args: Parameters): Promise { + logger.debug('serial: %s', name, args); + const hooks = Array.from(_hooks[name]); + for (const callback of hooks) { + await callback.apply(this, args); } - return null; } export function bail(name: K, ...args: Parameters): ReturnType { diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 699034f8f..8b04bc581 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -18,6 +18,7 @@ import serialize, { SerializeJSOptions } from 'serialize-javascript'; import { argv } from 'yargs'; import { lrucache } from '../utils'; import { User, DomainDoc } from '../interface'; +import { Logger } from '../logger'; import { UserNotFoundError, BlacklistedError, PermissionError, UserFacingError, ValidationError, PrivilegeError, @@ -33,6 +34,7 @@ import * as blacklist from '../model/blacklist'; import * as token from '../model/token'; import * as opcount from '../model/opcount'; +const logger = new Logger('server'); export const app = new Koa(); export const server = http.createServer(app.callback()); export const router = new Router(); @@ -762,7 +764,7 @@ export function Connection( RouteConnHandler: any, ...permPrivChecker: Array ) { - const log = argv.debug ? console.log : () => { }; + const log = (v: string, fmt: string, ...args: any[]) => logger.debug(fmt, ...args); const sock = sockjs.createServer({ prefix, log }); const checker = Checker(permPrivChecker); sock.on('connection', async (conn) => { @@ -792,7 +794,7 @@ export function Connection( if (h.finish) await h.finish(args); }); } catch (e) { - console.log(e); + logger.warn(e); await h.onerror(e); } }); @@ -820,7 +822,7 @@ export async function start() { }); app.use(router.routes()).use(router.allowedMethods()); server.listen(argv.port || port); - console.log('Server listening at: %s', argv.port || port); + logger.success('Server listening at: %d', argv.port || port); } global.Hydro.service.server = {