优化logger,修复serial与once同时使用导致的调用问题

This commit is contained in:
undefined committed 2020-09-08 13:24:46 +08:00
1 parent 6ba93841ea
commit 3e721fdd55
15 files changed
+260 -123

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import path from 'path'; import path from 'path';
import globby from 'globby'; import globby from 'globby';
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.13.5", "version": "2.13.6",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"main": "dist/loader.js", "main": "dist/loader.js",
"typings": "dist/loader.d.ts", "typings": "dist/loader.d.ts",
@@ -31,6 +31,7 @@
"serialize-javascript": "^4.0.0", "serialize-javascript": "^4.0.0",
"sockjs": "^0.3.20", "sockjs": "^0.3.20",
"superagent": "^6.1.0", "superagent": "^6.1.0",
"supports-color": "^7.2.0",
"systeminformation": "^4.27.3", "systeminformation": "^4.27.3",
"yargs": "^15.4.1" "yargs": "^15.4.1"
}, },
@@ -49,6 +50,7 @@
"@types/serialize-javascript": "^4.0.0", "@types/serialize-javascript": "^4.0.0",
"@types/sockjs": "^0.3.32", "@types/sockjs": "^0.3.32",
"@types/superagent": "^4.1.8", "@types/superagent": "^4.1.8",
"@types/supports-color": "^5.3.0",
"@types/yargs": "^15.0.5" "@types/yargs": "^15.0.5"
}, },
"scripts": { "scripts": {
+26 -33
View File
@@ -3,7 +3,9 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { argv } from 'yargs'; import { Logger } from '../logger';
const logger = new Logger('common', true);
export const builtinLib = [ export const builtinLib = [
'jwt', 'download', 'i18n', 'mail', 'useragent', 'jwt', 'download', 'i18n', 'mail', 'useragent',
@@ -46,14 +48,12 @@ export async function handler(pending: string[], fail: string[]) {
const p = path.resolve(i, 'handler.js'); const p = path.resolve(i, 'handler.js');
if (fs.existsSync(p) && !fail.includes(i)) { if (fs.existsSync(p) && !fail.includes(i)) {
try { try {
console.log(`Handler init: ${i}`); logger.info('Handler init: %s', i);
console.time(`Handler init: ${i}`);
eval('require')(p); eval('require')(p);
console.timeEnd(`Handler init: ${i}`);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Handler Load Fail: ${i}`); logger.error('Handler Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error('%o', e);
} }
} }
} }
@@ -72,11 +72,11 @@ export async function locale(pending: string[], fail: string[]) {
locales[file.split('.')[0]] = yaml.safeLoad(content); locales[file.split('.')[0]] = yaml.safeLoad(content);
} }
global.Hydro.lib.i18n(locales); global.Hydro.lib.i18n(locales);
console.log(`Locale init: ${i}`); logger.info('Locale init: %s', i);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Locale Load Fail: ${i}`); logger.error('Locale Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error('%o', e);
} }
} }
} }
@@ -105,8 +105,8 @@ export async function setting(pending: string[], fail: string[], modelSetting: t
); );
} }
} catch (e) { } catch (e) {
console.error(`Config Load Fail: ${i}`); logger.error('Config Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
} }
@@ -124,11 +124,11 @@ export async function template(pending: string[], fail: string[]) {
path.resolve(p, file), path.resolve(p, file),
).toString(); ).toString();
} }
console.log(`Template init: ${i}`); logger.info('Template init: %s', i);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Template Load Fail: ${i}`); logger.error('Template Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
} }
@@ -152,14 +152,12 @@ export async function model(pending: string[], fail: string[]) {
const p = path.resolve(i, 'model.js'); const p = path.resolve(i, 'model.js');
if (fs.existsSync(p) && !fail.includes(i)) { if (fs.existsSync(p) && !fail.includes(i)) {
try { try {
console.log(`Model init: ${i}`); logger.info('Model init: %s', i);
console.time(`Model init: ${i}`);
eval('require')(p); eval('require')(p);
console.timeEnd(`Model init: ${i}`);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Model Load Fail: ${i}`); logger.error('Model Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
} }
@@ -170,14 +168,12 @@ export async function lib(pending: string[], fail: string[]) {
const p = path.resolve(i, 'lib.js'); const p = path.resolve(i, 'lib.js');
if (fs.existsSync(p) && !fail.includes(i)) { if (fs.existsSync(p) && !fail.includes(i)) {
try { try {
console.log(`Lib init: ${i}`); logger.info('Lib init: %s', i);
console.time(`Lib init: ${i}`);
eval('require')(p); eval('require')(p);
console.timeEnd(`Lib init: ${i}`);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Lib Load Fail: ${i}`); logger.error('Lib Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
} }
@@ -188,14 +184,12 @@ export async function service(pending: string[], fail: string[]) {
const p = path.resolve(i, 'service.js'); const p = path.resolve(i, 'service.js');
if (fs.existsSync(p) && !fail.includes(i)) { if (fs.existsSync(p) && !fail.includes(i)) {
try { try {
console.log(`Service init: ${i}`); logger.info('Service init: %s', i);
console.time(`Service init: ${i}`);
eval('require')(p); eval('require')(p);
console.timeEnd(`Service init: ${i}`);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Service Load Fail: ${i}`); logger.error('Service Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
} }
@@ -206,13 +200,12 @@ export async function script(pending: string[], fail: string[], active: string[]
const p = path.resolve(i, 'script.js'); const p = path.resolve(i, 'script.js');
if (fs.existsSync(p) && !fail.includes(i)) { if (fs.existsSync(p) && !fail.includes(i)) {
try { try {
console.time(`Script init: ${i}`); logger.info('Script init: %s', i);
eval('require')(p); eval('require')(p);
console.timeEnd(`Script init: ${i}`);
} catch (e) { } catch (e) {
fail.push(i); fail.push(i);
console.error(`Script Load Fail: ${i}`); logger.error('Script Load Fail: %s', i);
if (argv.debug) console.error(e); logger.error(e);
} }
} }
active.push(i); active.push(i);
+1 -5
View File
@@ -47,11 +47,7 @@ export async function load(call: Entry) {
fs.removeSync(lockfile); fs.removeSync(lockfile);
}); });
await new Promise((resolve) => { await new Promise((resolve) => {
const h = () => { bus.once('database/connect', resolve);
console.log('Database connected');
resolve();
};
bus.once('database/connect', h);
require('../service/db'); require('../service/db');
}); });
require('../service/monitor'); require('../service/monitor');
+4 -1
View File
@@ -7,6 +7,9 @@ import Body from 'koa-body';
import cache from 'koa-static-cache'; import cache from 'koa-static-cache';
import mongodb from 'mongodb'; import mongodb from 'mongodb';
import { argv } from 'yargs'; import { argv } from 'yargs';
import { Logger } from '../logger';
const logger = new Logger('setup');
const listenPort = argv.port || 8888; const listenPort = argv.port || 8888;
@@ -141,5 +144,5 @@ export function load() {
return get(ctx); return get(ctx);
}); });
server.listen(listenPort); server.listen(listenPort);
console.log('Server listening at:', listenPort); logger.success('Server listening at: %d', listenPort);
} }
+1 -5
View File
@@ -20,11 +20,7 @@ export async function load() {
uistatic(pending, fail), uistatic(pending, fail),
]); ]);
await new Promise((resolve) => { await new Promise((resolve) => {
const h = () => { bus.once('database/connect', resolve);
console.log('Database connected');
resolve();
};
bus.once('database/connect', h);
require('../service/db'); require('../service/db');
}); });
for (const i of builtinLib) require(`../lib/${i}`); for (const i of builtinLib) require(`../lib/${i}`);
+1
View File
@@ -510,6 +510,7 @@ declare global {
stat: any, stat: any,
ui: UI, ui: UI,
error: typeof import('./error'), error: typeof import('./error'),
Logger: typeof import('./logger'),
locales: Dict<Dict<string>>, locales: Dict<Dict<string>>,
}, },
addons: string[], addons: string[],
+21 -21
View File
@@ -40,14 +40,14 @@ if (!global.Hydro) {
global.addons = []; global.addons = [];
} }
if (argv.debug) { // eslint-disable-next-line import/first
console.log(process.argv); import { Logger } from './logger';
console.log(argv);
}
// eslint-disable-next-line import/first // eslint-disable-next-line import/first
import * as bus from './service/bus'; import * as bus from './service/bus';
const logger = new Logger('loader');
logger.debug('%o', argv);
async function terminate() { async function terminate() {
try { try {
await bus.parallel('app/exit'); await bus.parallel('app/exit');
@@ -113,9 +113,9 @@ async function executeCommand(input: string) {
} }
async function reload(count = 1) { async function reload(count = 1) {
console.log('Reloading'); logger.info('Reloading');
await stopWorker(); await stopWorker();
console.log('Worker stopped'); logger.info('Worker stopped');
await startWorker(count); await startWorker(count);
} }
@@ -176,13 +176,13 @@ export function addon(addonPath: string) {
if (fs.existsSync(publicPath)) fs.copySync(publicPath, publicTemp); if (fs.existsSync(publicPath)) fs.copySync(publicPath, publicTemp);
global.addons.push(targetPath); global.addons.push(targetPath);
} catch (e) { } catch (e) {
console.error('Addon load fail: ', e); logger.error('Addon load fail: ', e);
throw e; throw e;
} }
} else throw new Error(`Addon not found: ${addonPath}`); } else throw new Error(`Addon not found: ${addonPath}`);
} }
process.on('unhandledRejection', (e) => console.error(e)); process.on('unhandledRejection', logger.error);
process.on('SIGINT', terminate); process.on('SIGINT', terminate);
export async function load() { export async function load() {
@@ -191,7 +191,7 @@ export async function load() {
process.on('message', messageHandler); process.on('message', messageHandler);
cluster.on('message', messageHandler); cluster.on('message', messageHandler);
if (cluster.isMaster || argv.startAsMaster) { if (cluster.isMaster || argv.startAsMaster) {
console.log(`Master ${process.pid} Starting`); logger.info(`Master ${process.pid} Starting`);
process.stdin.setEncoding('utf8'); process.stdin.setEncoding('utf8');
process.stdin.on('data', (buf) => { process.stdin.on('data', (buf) => {
const input = buf.toString(); const input = buf.toString();
@@ -205,33 +205,33 @@ export async function load() {
} }
}); });
const cnt = await entry({ entry: 'master' }); const cnt = await entry({ entry: 'master' });
console.log('Master started'); logger.info('Master started');
cluster.on('exit', (worker, code, signal) => { 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) => { 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) => { 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) => { 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); await startWorker(cnt);
} else { } else {
global.addons = JSON.parse(Buffer.from(argv.addons as string, 'base64').toString()); global.addons = JSON.parse(Buffer.from(argv.addons as string, 'base64').toString());
console.log(global.addons); logger.info('%o', global.addons);
if (argv.entry) { 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 }); 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 { } else {
if (argv.firstWorker) cluster.isFirstWorker = true; if (argv.firstWorker) cluster.isFirstWorker = true;
else cluster.isFirstWorker = false; else cluster.isFirstWorker = false;
console.log(`Worker ${process.pid} Starting`); logger.info(`Worker ${process.pid} Starting`);
await entry({ entry: 'worker' }); await entry({ entry: 'worker' });
console.log(`Worker ${process.pid} Started`); logger.success(`Worker ${process.pid} Started`);
} }
} }
if (global.gc) global.gc(); if (global.gc) global.gc();
@@ -245,7 +245,7 @@ export async function loadCli() {
if (argv.pandora || !module.parent) { if (argv.pandora || !module.parent) {
const func = argv._[0] === 'cli' ? load : loadCli; const func = argv._[0] === 'cli' ? load : loadCli;
func().catch((e) => { func().catch((e) => {
console.error(e); logger.error(e);
process.exit(1); process.exit(1);
}); });
} }
+142
View File
@@ -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<string, Logger> = {};
type LogFunction = (format: any, ...param: any[]) => void
type LogType = 'success' | 'error' | 'info' | 'warn' | 'debug'
export interface Logger extends Record<LogType, LogFunction> { }
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<string, number> = {}
static lastTime = 0
static options: InspectOptions = {
colors: stderr.hasBasic,
}
static formatters: Record<string, (this: Logger, value: any) => 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 };
+2 -2
View File
@@ -528,8 +528,8 @@ export function count(domainId: string, query: any, type: Type = document.TYPE_C
return document.count(domainId, type, query); return document.count(domainId, type, query);
} }
export function getMulti<K extends Type & keyof document.DocumentType>( export function getMulti<K extends Type & keyof document.DocType>(
domainId: string, query: FilterQuery<document.DocumentType[K]> = {}, type?: K, domainId: string, query: FilterQuery<document.DocType[K]> = {}, type?: K,
) { ) {
return document.getMulti(domainId, type || document.TYPE_CONTEST, query); return document.getMulti(domainId, type || document.TYPE_CONTEST, query);
} }
+32 -32
View File
@@ -22,7 +22,7 @@ export const TYPE_TRAINING = 40;
export const TYPE_FILE = 50; export const TYPE_FILE = 50;
export const TYPE_HOMEWORK = 60; export const TYPE_HOMEWORK = 60;
export interface DocumentType { export interface DocType {
[TYPE_PROBLEM]: Pdoc, [TYPE_PROBLEM]: Pdoc,
[TYPE_PROBLEM_SOLUTION]: any, [TYPE_PROBLEM_SOLUTION]: any,
[TYPE_PROBLEM_LIST]: any, [TYPE_PROBLEM_LIST]: any,
@@ -35,13 +35,13 @@ export interface DocumentType {
[TYPE_HOMEWORK]: Tdoc, [TYPE_HOMEWORK]: Tdoc,
} }
export async function add<T extends DocID, K extends keyof DocumentType>( export async function add<T extends DocID, K extends keyof DocType>(
domainId: string, content: string, owner: number, domainId: string, content: string, owner: number,
docType: K, docId: DocumentType[K]['docId'], docType: K, docId: DocType[K]['docId'],
parentType?: number | null, parentId?: DocID, parentType?: number | null, parentId?: DocID,
args?: any, args?: any,
): Promise<T> ): Promise<T>
export async function add<K extends keyof DocumentType>( export async function add<K extends keyof DocType>(
domainId: string, content: string, owner: number, domainId: string, content: string, owner: number,
docType: K, docId: null, docType: K, docId: null,
parentType?: number, parentId?: DocID, parentType?: number, parentId?: DocID,
@@ -73,13 +73,13 @@ export async function add(
return docId || res.insertedId; return docId || res.insertedId;
} }
export function get<K extends keyof DocumentType>(domainId: string, docType: K, docId: DocumentType[K]['docId']) { export function get<K extends keyof DocType>(domainId: string, docType: K, docId: DocType[K]['docId']) {
return coll.findOne({ domainId, docType, docId }); return coll.findOne({ domainId, docType, docId });
} }
export async function set<K extends keyof DocumentType>( export async function set<K extends keyof DocType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], $set: Partial<DocumentType[K]>, domainId: string, docType: K, docId: DocType[K]['docId'], $set: Partial<DocType[K]>,
): Promise<DocumentType[K]> { ): Promise<DocType[K]> {
await bus.parallel('document/set', domainId, docType, docId, $set); await bus.parallel('document/set', domainId, docType, docId, $set);
const res = await coll.findOneAndUpdate( const res = await coll.findOneAndUpdate(
{ domainId, docType, docId }, { domainId, docType, docId },
@@ -89,8 +89,8 @@ export async function set<K extends keyof DocumentType>(
return res.value; return res.value;
} }
export function deleteOne<K extends keyof DocumentType>( export function deleteOne<K extends keyof DocType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], domainId: string, docType: K, docId: DocType[K]['docId'],
) { ) {
return Promise.all([ return Promise.all([
collStatus.deleteMany({ domainId, docType, docId }), collStatus.deleteMany({ domainId, docType, docId }),
@@ -98,27 +98,27 @@ export function deleteOne<K extends keyof DocumentType>(
]); ]);
} }
export function deleteMulti<K extends keyof DocumentType>( export function deleteMulti<K extends keyof DocType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>, domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
) { ) {
return coll.deleteMany({ ...query, domainId, docType }); return coll.deleteMany({ ...query, domainId, docType });
} }
export function deleteMultiStatus<K extends keyof DocumentType>( export function deleteMultiStatus<K extends keyof DocType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>, domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
) { ) {
return collStatus.deleteMany({ ...query, domainId, docType }); return collStatus.deleteMany({ ...query, domainId, docType });
} }
export function getMulti<K extends keyof DocumentType>( export function getMulti<K extends keyof DocType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>, domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
): Cursor<DocumentType[K]> { ): Cursor<DocType[K]> {
return coll.find({ ...query, docType, domainId }); return coll.find({ ...query, docType, domainId });
} }
export async function inc<K extends keyof DocumentType>( export async function inc<K extends keyof DocType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], domainId: string, docType: K, docId: DocType[K]['docId'],
key: NumberKeys<DocumentType[K]>, value: number, key: NumberKeys<DocType[K]>, value: number,
) { ) {
const res = await coll.findOneAndUpdate( const res = await coll.findOneAndUpdate(
{ domainId, docType, docId }, { domainId, docType, docId },
@@ -128,11 +128,11 @@ export async function inc<K extends keyof DocumentType>(
return res.value; return res.value;
} }
type Q = NumberKeys<DocumentType[10]> type Q = NumberKeys<DocType[10]>
export async function incAndSet<K extends keyof DocumentType>( export async function incAndSet<K extends keyof DocType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], domainId: string, docType: K, docId: DocType[K]['docId'],
key: NumberKeys<DocumentType[K]>, value: number, args: Partial<DocumentType[K]>, key: NumberKeys<DocType[K]>, value: number, args: Partial<DocType[K]>,
) { ) {
const res = await coll.findOneAndUpdate( const res = await coll.findOneAndUpdate(
{ domainId, docType, docId }, { domainId, docType, docId },
@@ -142,15 +142,15 @@ export async function incAndSet<K extends keyof DocumentType>(
return res.value; return res.value;
} }
export function count<K extends keyof DocumentType>( export function count<K extends keyof DocType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>, domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
) { ) {
return coll.find({ ...query, docType, domainId }).count(); return coll.find({ ...query, docType, domainId }).count();
} }
export async function push<K extends keyof DocumentType, T extends keyof DocumentType[K]>( export async function push<K extends keyof DocType, T extends keyof DocType[K]>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], domainId: string, docType: K, docId: DocType[K]['docId'],
key: keyof DocumentType[K], content: string, owner: number, args: DocumentType[K][T][0] = {}, key: keyof DocType[K], content: string, owner: number, args: DocType[K][T][0] = {},
) { ) {
const _id = new ObjectID(); const _id = new ObjectID();
const doc = await coll.findOneAndUpdate( const doc = await coll.findOneAndUpdate(
@@ -167,10 +167,10 @@ export async function push<K extends keyof DocumentType, T extends keyof Documen
return [doc.value, _id]; return [doc.value, _id];
} }
export async function pull<K extends keyof DocumentType>( export async function pull<K extends keyof DocType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], domainId: string, docType: K, docId: DocType[K]['docId'],
setKey: string, contents: string[], setKey: string, contents: string[],
): Promise<DocumentType[K]> { ): Promise<DocType[K]> {
const res = await coll.findOneAndUpdate( const res = await coll.findOneAndUpdate(
{ domainId, docType, docId }, { domainId, docType, docId },
{ $pull: { [setKey]: { $in: contents } } }, { $pull: { [setKey]: { $in: contents } } },
+19 -20
View File
@@ -1,10 +1,14 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import cluster from 'cluster'; import cluster from 'cluster';
import { Db, ObjectID } from 'mongodb'; import { Db, UpdateQuery } from 'mongodb';
import { Logger } from '../logger';
import { Mdoc, Rdoc, User } from '../interface'; import { Mdoc, Rdoc, User } from '../interface';
// only typings.
// eslint-disable-next-line import/no-cycle
import { DocType } from '../model/document';
const _hooks: Record<keyof any, Array<(...args: any[]) => any>> = {}; const _hooks: Record<keyof any, Array<(...args: any[]) => any>> = {};
const _disposables = []; const logger = new Logger('bus', true);
function isBailed(value: any) { function isBailed(value: any) {
return value !== null && value !== false && value !== undefined; return value !== null && value !== false && value !== undefined;
@@ -12,7 +16,7 @@ function isBailed(value: any) {
export type Disposable = () => void export type Disposable = () => void
interface EventMap { export interface EventMap {
'app/started': () => void 'app/started': () => void
'app/exit': () => Promise<void> | void 'app/exit': () => Promise<void> | void
'dispose': () => void 'dispose': () => void
@@ -22,15 +26,16 @@ interface EventMap {
'user/message': (uid: number, mdoc: Mdoc, udoc: User) => void 'user/message': (uid: number, mdoc: Mdoc, udoc: User) => void
'document/add': (doc: any) => Promise<void> | void 'document/add': (doc: any) => Promise<void> | void
'document/set': (domainId: string, docType: number, docId: ObjectID | string | number, $set: any) => Promise<void> | void 'document/set': <T extends keyof DocType>
(domainId: string, docType: T, docId: DocType[T], $set: UpdateQuery<DocType[T]>['$set']) => Promise<void> | void
'record/change': (rdoc: Rdoc, $set?: any, $push?: any) => void 'record/change': (rdoc: Rdoc, $set?: UpdateQuery<Rdoc>['$set'], $push?: UpdateQuery<Rdoc>['$push']) => void
} }
function getHooks<K extends keyof EventMap>(name: K) { function getHooks<K extends keyof EventMap>(name: K) {
const hooks = _hooks[name] || (_hooks[name] = []); const hooks = _hooks[name] || (_hooks[name] = []);
if (hooks.length >= 128) { if (hooks.length >= 128) {
console.warn( logger.warn(
'max listener count (128) for event "%s" exceeded, which may be caused by a memory leak', 'max listener count (128) for event "%s" exceeded, which may be caused by a memory leak',
name, name,
); );
@@ -49,23 +54,17 @@ export function removeListener<K extends keyof EventMap>(name: K, listener: Even
export function addListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) { export function addListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
getHooks(name).push(listener); getHooks(name).push(listener);
const dispose = () => removeListener(name, listener); return () => removeListener(name, listener);
_disposables.push(name === 'dispose' ? listener as Disposable : dispose);
return dispose;
} }
export function prependListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) { export function prependListener<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
getHooks(name).unshift(listener); getHooks(name).unshift(listener);
const dispose = () => removeListener(name, listener); return () => removeListener(name, listener);
_disposables.push(name === 'dispose' ? listener as Disposable : dispose);
return dispose;
} }
export function once<K extends keyof EventMap>(name: K, listener: EventMap[K]) { export function once<K extends keyof EventMap>(name: K, listener: EventMap[K]) {
// @ts-ignore const dispose = addListener(name, function _listener(...args: any[]) {
const dispose = addListener(name, (...args: Parameters<EventMap[K]>) => {
dispose(); dispose();
// @ts-ignore
return listener.apply(this, args); return listener.apply(this, args);
}); });
return dispose; return dispose;
@@ -91,12 +90,12 @@ export function emit<K extends keyof EventMap>(name: K, ...args: Parameters<Even
return parallel(name, ...args); return parallel(name, ...args);
} }
export async function serial<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<ReturnType<EventMap[K]>> { export async function serial<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): Promise<void> {
for (const callback of _hooks[name] || []) { logger.debug('serial: %s', name, args);
const result = await callback.apply(this, args); const hooks = Array.from(_hooks[name]);
if (isBailed(result)) return result; for (const callback of hooks) {
await callback.apply(this, args);
} }
return null;
} }
export function bail<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): ReturnType<EventMap[K]> { export function bail<K extends keyof EventMap>(name: K, ...args: Parameters<EventMap[K]>): ReturnType<EventMap[K]> {
+5 -3
View File
@@ -18,6 +18,7 @@ import serialize, { SerializeJSOptions } from 'serialize-javascript';
import { argv } from 'yargs'; import { argv } from 'yargs';
import { lrucache } from '../utils'; import { lrucache } from '../utils';
import { User, DomainDoc } from '../interface'; import { User, DomainDoc } from '../interface';
import { Logger } from '../logger';
import { import {
UserNotFoundError, BlacklistedError, PermissionError, UserNotFoundError, BlacklistedError, PermissionError,
UserFacingError, ValidationError, PrivilegeError, UserFacingError, ValidationError, PrivilegeError,
@@ -33,6 +34,7 @@ import * as blacklist from '../model/blacklist';
import * as token from '../model/token'; import * as token from '../model/token';
import * as opcount from '../model/opcount'; import * as opcount from '../model/opcount';
const logger = new Logger('server');
export const app = new Koa(); export const app = new Koa();
export const server = http.createServer(app.callback()); export const server = http.createServer(app.callback());
export const router = new Router(); export const router = new Router();
@@ -762,7 +764,7 @@ export function Connection(
RouteConnHandler: any, RouteConnHandler: any,
...permPrivChecker: Array<number | bigint | Function> ...permPrivChecker: Array<number | bigint | Function>
) { ) {
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 sock = sockjs.createServer({ prefix, log });
const checker = Checker(permPrivChecker); const checker = Checker(permPrivChecker);
sock.on('connection', async (conn) => { sock.on('connection', async (conn) => {
@@ -792,7 +794,7 @@ export function Connection(
if (h.finish) await h.finish(args); if (h.finish) await h.finish(args);
}); });
} catch (e) { } catch (e) {
console.log(e); logger.warn(e);
await h.onerror(e); await h.onerror(e);
} }
}); });
@@ -820,7 +822,7 @@ export async function start() {
}); });
app.use(router.routes()).use(router.allowedMethods()); app.use(router.routes()).use(router.allowedMethods());
server.listen(argv.port || port); 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 = { global.Hydro.service.server = {