From 977af0a24d3f0b3cb9799d133b02c6316f286660 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 2 Mar 2019 18:51:59 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=92=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=83=99=E3=83=BC=E3=82=B9=E3=81=AB=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=81=97=E3=81=A6=E7=AE=A1=E7=90=86=E7=94=BB=E9=9D=A2=E3=81=A7?= =?UTF-8?q?=E8=A6=8B=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/app/admin/views/index.vue | 9 +- src/client/app/admin/views/logs.vue | 101 +++++++++++++++++++ src/db/elasticsearch.ts | 2 +- src/db/logger.ts | 3 + src/db/mongodb.ts | 3 - src/index.ts | 28 +++--- src/misc/check-mongodb.ts | 2 +- src/misc/logger.ts | 59 ------------ src/misc/show-machine-info.ts | 2 +- src/models/drive-file.ts | 3 +- src/models/favorite.ts | 3 +- src/models/log.ts | 17 ++++ src/models/note.ts | 3 +- src/models/notification.ts | 3 +- src/models/user.ts | 3 +- src/queue/logger.ts | 2 +- src/queue/processors/http/process-inbox.ts | 2 +- src/remote/logger.ts | 2 +- src/server/api/endpoints/admin/logs.ts | 51 ++++++++++ src/server/api/limiter.ts | 2 +- src/server/api/logger.ts | 2 +- src/server/index.ts | 4 +- src/server/web/url-preview.ts | 2 +- src/services/blocking/delete.ts | 2 +- src/services/chart/index.ts | 2 +- src/services/drive/logger.ts | 2 +- src/services/following/create.ts | 2 +- src/services/following/delete.ts | 2 +- src/services/logger.ts | 107 +++++++++++++++++++++ 29 files changed, 326 insertions(+), 99 deletions(-) create mode 100644 src/client/app/admin/views/logs.vue create mode 100644 src/db/logger.ts delete mode 100644 src/misc/logger.ts create mode 100644 src/models/log.ts create mode 100644 src/server/api/endpoints/admin/logs.ts create mode 100644 src/services/logger.ts diff --git a/src/client/app/admin/views/index.vue b/src/client/app/admin/views/index.vue index b37bb8f7f..4bce197ed 100644 --- a/src/client/app/admin/views/index.vue +++ b/src/client/app/admin/views/index.vue @@ -21,6 +21,7 @@
  • {{ $t('dashboard') }}
  • {{ $t('instance') }}
  • {{ $t('queue') }}
  • +
  • {{ $t('logs') }}
  • {{ $t('moderators') }}
  • {{ $t('users') }}
  • {{ $t('@.drive') }}
  • @@ -42,6 +43,7 @@
    +
    @@ -62,6 +64,7 @@ import { version } from '../../config'; import XDashboard from "./dashboard.vue"; import XInstance from "./instance.vue"; import XQueue from "./queue.vue"; +import XLogs from "./logs.vue"; import XModerators from "./moderators.vue"; import XEmoji from "./emoji.vue"; import XAnnouncements from "./announcements.vue"; @@ -71,7 +74,7 @@ import XDrive from "./drive.vue"; import XAbuse from "./abuse.vue"; import XFederation from "./federation.vue"; -import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks } from '@fortawesome/free-solid-svg-icons'; +import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream } from '@fortawesome/free-solid-svg-icons'; import { faGrin } from '@fortawesome/free-regular-svg-icons'; // Detect the user agent @@ -84,6 +87,7 @@ export default Vue.extend({ XDashboard, XInstance, XQueue, + XLogs, XModerators, XEmoji, XAnnouncements, @@ -107,7 +111,8 @@ export default Vue.extend({ faHeadset, faGlobe, faExclamationCircle, - faTasks + faTasks, + faStream }; }, methods: { diff --git a/src/client/app/admin/views/logs.vue b/src/client/app/admin/views/logs.vue new file mode 100644 index 000000000..1e76d141b --- /dev/null +++ b/src/client/app/admin/views/logs.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/db/elasticsearch.ts b/src/db/elasticsearch.ts index cbe6afbbb..d54b01763 100644 --- a/src/db/elasticsearch.ts +++ b/src/db/elasticsearch.ts @@ -1,6 +1,6 @@ import * as elasticsearch from 'elasticsearch'; import config from '../config'; -import Logger from '../misc/logger'; +import Logger from '../services/logger'; const esLogger = new Logger('es'); diff --git a/src/db/logger.ts b/src/db/logger.ts new file mode 100644 index 000000000..1f702c18e --- /dev/null +++ b/src/db/logger.ts @@ -0,0 +1,3 @@ +import Logger from '../services/logger'; + +export const dbLogger = new Logger('db'); diff --git a/src/db/mongodb.ts b/src/db/mongodb.ts index dedb289ce..f82ced176 100644 --- a/src/db/mongodb.ts +++ b/src/db/mongodb.ts @@ -18,7 +18,6 @@ export default db; * MongoDB native module (officialy) */ import * as mongodb from 'mongodb'; -import Logger from '../misc/logger'; let mdb: mongodb.Db; @@ -38,5 +37,3 @@ const nativeDbConn = async (): Promise => { }; export { nativeDbConn }; - -export const dbLogger = new Logger('db'); diff --git a/src/index.ts b/src/index.ts index 6983ec722..3206ee3d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import * as portscanner from 'portscanner'; import * as isRoot from 'is-root'; import Xev from 'xev'; -import Logger from './misc/logger'; +import Logger from './services/logger'; import serverStats from './daemons/server-stats'; import notesStats from './daemons/notes-stats'; import loadConfig from './config/load'; @@ -25,7 +25,7 @@ import { checkMongoDB } from './misc/check-mongodb'; import { showMachineInfo } from './misc/show-machine-info'; const logger = new Logger('core', 'cyan'); -const bootLogger = logger.createSubLogger('boot', 'magenta'); +const bootLogger = logger.createSubLogger('boot', 'magenta', false); const clusterLogger = logger.createSubLogger('cluster', 'orange'); const ev = new Xev(); @@ -73,7 +73,7 @@ function greet() { console.log(chalk`${os.hostname()} {gray (PID: ${process.pid.toString()})}`); bootLogger.info('Welcome to Misskey!'); - bootLogger.info(`Misskey v${pkg.version}`, true); + bootLogger.info(`Misskey v${pkg.version}`, null, true); bootLogger.info('Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.'); } @@ -90,21 +90,21 @@ async function masterMain() { config = await init(); if (config.port == null) { - bootLogger.error('The port is not configured. Please configure port.', true); + bootLogger.error('The port is not configured. Please configure port.', null, true); process.exit(1); } if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) { - bootLogger.error('You need root privileges to listen on well-known port on Linux', true); + bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true); process.exit(1); } if (!await isPortAvailable(config.port)) { - bootLogger.error(`Port ${config.port} is already in use`, true); + bootLogger.error(`Port ${config.port} is already in use`, null, true); process.exit(1); } } catch (e) { - bootLogger.error('Fatal error occurred during initialization', true); + bootLogger.error('Fatal error occurred during initialization', null, true); process.exit(1); } @@ -117,7 +117,7 @@ async function masterMain() { // start queue require('./queue').default(); - bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, true); + bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); } /** @@ -140,7 +140,7 @@ async function queueMain() { // initialize app await init(); } catch (e) { - bootLogger.error('Fatal error occurred during initialization', true); + bootLogger.error('Fatal error occurred during initialization', null, true); process.exit(1); } @@ -150,7 +150,7 @@ async function queueMain() { const queue = require('./queue').default(); if (queue) { - bootLogger.succ('Queue started', true); + bootLogger.succ('Queue started', null, true); } else { bootLogger.error('Queue not available'); } @@ -175,7 +175,7 @@ function showEnvironment(): void { if (env !== 'production') { logger.warn('The environment is not in production mode.'); - logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', true); + logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true); } logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`); @@ -192,7 +192,7 @@ async function init(): Promise { nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`); if (!satisfyNodejsVersion) { - nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, true); + nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true); process.exit(1); } @@ -209,7 +209,7 @@ async function init(): Promise { process.exit(1); } if (exception.code === 'ENOENT') { - configLogger.error('Configuration file not found', true); + configLogger.error('Configuration file not found', null, true); process.exit(1); } throw exception; @@ -221,7 +221,7 @@ async function init(): Promise { try { await checkMongoDB(config, bootLogger); } catch (e) { - bootLogger.error('Cannot connect to database', true); + bootLogger.error('Cannot connect to database', null, true); process.exit(1); } diff --git a/src/misc/check-mongodb.ts b/src/misc/check-mongodb.ts index f3839da31..8e03db5d4 100644 --- a/src/misc/check-mongodb.ts +++ b/src/misc/check-mongodb.ts @@ -1,6 +1,6 @@ import { nativeDbConn } from '../db/mongodb'; import { Config } from '../config/types'; -import Logger from './logger'; +import Logger from '../services/logger'; import { lessThan } from '../prelude/array'; const requiredMongoDBVersion = [3, 6]; diff --git a/src/misc/logger.ts b/src/misc/logger.ts deleted file mode 100644 index 1d159fb6a..000000000 --- a/src/misc/logger.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as cluster from 'cluster'; -import chalk from 'chalk'; -import * as dateformat from 'dateformat'; -import { program } from '../argv'; - -export default class Logger { - private domain: string; - private color?: string; - private parentLogger: Logger; - - constructor(domain: string, color?: string) { - this.domain = domain; - this.color = color; - } - - public createSubLogger(domain: string, color?: string): Logger { - const logger = new Logger(domain, color); - logger.parentLogger = this; - return logger; - } - - private log(level: string, message: string, important = false, subDomains: string[] = []): void { - if (program.quiet) return; - if (process.env.NODE_ENV === 'test') return; - const domain = this.color ? chalk.keyword(this.color)(this.domain) : chalk.white(this.domain); - const domains = [domain].concat(subDomains); - if (this.parentLogger) { - this.parentLogger.log(level, message, important, domains); - } else { - const time = dateformat(new Date(), 'HH:MM:ss'); - const process = cluster.isMaster ? '*' : cluster.worker.id; - let log = `${level} ${process}\t[${domains.join(' ')}]\t${message}`; - if (program.withLogTime) log = chalk.gray(time) + ' ' + log; - console.log(important ? chalk.bold(log) : log); - } - } - - public error(message: string | Error, important = false): void { // 実行を継続できない状況で使う - this.log(important ? chalk.bgRed.white('ERR ') : chalk.red('ERR '), chalk.red(message.toString()), important); - } - - public warn(message: string, important = false): void { // 実行を継続できるが改善すべき状況で使う - this.log(chalk.yellow('WARN'), chalk.yellow(message), important); - } - - public succ(message: string, important = false): void { // 何かに成功した状況で使う - this.log(important ? chalk.bgGreen.white('DONE') : chalk.green('DONE'), chalk.green(message), important); - } - - public debug(message: string, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) - if (process.env.NODE_ENV != 'production' || program.verbose) { - this.log(chalk.gray('VERB'), chalk.gray(message), important); - } - } - - public info(message: string, important = false): void { // それ以外 - this.log(chalk.blue('INFO'), message, important); - } -} diff --git a/src/misc/show-machine-info.ts b/src/misc/show-machine-info.ts index a4d85edfe..2aae019be 100644 --- a/src/misc/show-machine-info.ts +++ b/src/misc/show-machine-info.ts @@ -1,6 +1,6 @@ import * as os from 'os'; import * as sysUtils from 'systeminformation'; -import Logger from './logger'; +import Logger from '../services/logger'; export async function showMachineInfo(parentLogger: Logger) { const logger = parentLogger.createSubLogger('machine'); diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index f3e21f209..c31e9a709 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -2,9 +2,10 @@ import * as mongo from 'mongodb'; import * as deepcopy from 'deepcopy'; import { pack as packFolder } from './drive-folder'; import { pack as packUser } from './user'; -import monkDb, { nativeDbConn, dbLogger } from '../db/mongodb'; +import monkDb, { nativeDbConn } from '../db/mongodb'; import isObjectId from '../misc/is-objectid'; import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url'; +import { dbLogger } from '../db/logger'; const DriveFile = monkDb.get('driveFiles.files'); DriveFile.createIndex('md5'); diff --git a/src/models/favorite.ts b/src/models/favorite.ts index e3aa92c88..2008edbfa 100644 --- a/src/models/favorite.ts +++ b/src/models/favorite.ts @@ -1,8 +1,9 @@ import * as mongo from 'mongodb'; import * as deepcopy from 'deepcopy'; -import db, { dbLogger } from '../db/mongodb'; +import db from '../db/mongodb'; import isObjectId from '../misc/is-objectid'; import { pack as packNote } from './note'; +import { dbLogger } from '../db/logger'; const Favorite = db.get('favorites'); Favorite.createIndex('userId'); diff --git a/src/models/log.ts b/src/models/log.ts new file mode 100644 index 000000000..f74332e94 --- /dev/null +++ b/src/models/log.ts @@ -0,0 +1,17 @@ +import * as mongo from 'mongodb'; +import db from '../db/mongodb'; + +const Log = db.get('logs'); +Log.createIndex('createdAt', { expireAfterSeconds: 3600 * 24 * 3 }); +export default Log; + +export interface ILog { + _id: mongo.ObjectID; + createdAt: Date; + machine: string; + worker: string; + domain: string[]; + level: string; + message: string; + data: any; +} diff --git a/src/models/note.ts b/src/models/note.ts index 9bdf22e97..c3413164b 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -1,7 +1,7 @@ import * as mongo from 'mongodb'; import * as deepcopy from 'deepcopy'; import rap from '@prezzemolo/rap'; -import db, { dbLogger } from '../db/mongodb'; +import db from '../db/mongodb'; import isObjectId from '../misc/is-objectid'; import { length } from 'stringz'; import { IUser, pack as packUser } from './user'; @@ -11,6 +11,7 @@ import Reaction from './note-reaction'; import { packMany as packFileMany, IDriveFile } from './drive-file'; import Following from './following'; import Emoji from './emoji'; +import { dbLogger } from '../db/logger'; const Note = db.get('notes'); Note.createIndex('uri', { sparse: true, unique: true }); diff --git a/src/models/notification.ts b/src/models/notification.ts index aedeafb52..75456af57 100644 --- a/src/models/notification.ts +++ b/src/models/notification.ts @@ -1,9 +1,10 @@ import * as mongo from 'mongodb'; import * as deepcopy from 'deepcopy'; -import db, { dbLogger } from '../db/mongodb'; +import db from '../db/mongodb'; import isObjectId from '../misc/is-objectid'; import { IUser, pack as packUser } from './user'; import { pack as packNote } from './note'; +import { dbLogger } from '../db/logger'; const Notification = db.get('notifications'); Notification.createIndex('notifieeId'); diff --git a/src/models/user.ts b/src/models/user.ts index 97c703793..56e052ed4 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,7 +1,7 @@ import * as mongo from 'mongodb'; import * as deepcopy from 'deepcopy'; import rap from '@prezzemolo/rap'; -import db, { dbLogger } from '../db/mongodb'; +import db from '../db/mongodb'; import isObjectId from '../misc/is-objectid'; import { packMany as packNoteMany } from './note'; import Following from './following'; @@ -12,6 +12,7 @@ import config from '../config'; import FollowRequest from './follow-request'; import fetchMeta from '../misc/fetch-meta'; import Emoji from './emoji'; +import { dbLogger } from '../db/logger'; const User = db.get('users'); diff --git a/src/queue/logger.ts b/src/queue/logger.ts index 99d88bd63..d6d077468 100644 --- a/src/queue/logger.ts +++ b/src/queue/logger.ts @@ -1,3 +1,3 @@ -import Logger from '../misc/logger'; +import Logger from '../services/logger'; export const queueLogger = new Logger('queue', 'orange'); diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts index 43170848f..ea737593d 100644 --- a/src/queue/processors/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -7,7 +7,7 @@ import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/ import { toUnicode } from 'punycode'; import { URL } from 'url'; import { publishApLogStream } from '../../../services/stream'; -import Logger from '../../../misc/logger'; +import Logger from '../../../services/logger'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; import Instance from '../../../models/instance'; import instanceChart from '../../../services/chart/instance'; diff --git a/src/remote/logger.ts b/src/remote/logger.ts index cf2fe1e90..43b4f105a 100644 --- a/src/remote/logger.ts +++ b/src/remote/logger.ts @@ -1,3 +1,3 @@ -import Logger from "../misc/logger"; +import Logger from "../services/logger"; export const remoteLogger = new Logger('remote', 'cyan'); diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts new file mode 100644 index 000000000..b5dc7d728 --- /dev/null +++ b/src/server/api/endpoints/admin/logs.ts @@ -0,0 +1,51 @@ +import $ from 'cafy'; +import define from '../../define'; +import Log from '../../../../models/log'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 30 + }, + + level: { + validator: $.optional.nullable.str, + default: null as any + }, + + domain: { + validator: $.optional.nullable.str, + default: null as any + } + } +}; + +export default define(meta, async (ps) => { + const sort = { + _id: -1 + }; + const query = {} as any; + + if (ps.level) query.level = ps.level; + if (ps.domain) { + let i = 0; + for (const d of ps.domain.split(' ')) { + query[`domain.${i}`] = d; + i++; + } + } + + const logs = await Log + .find(query, { + limit: ps.limit, + sort: sort + }); + + return logs; +}); diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index f5c326f7d..3d66172fd 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -3,7 +3,7 @@ import limiterDB from '../../db/redis'; import { IEndpoint } from './endpoints'; import getAcct from '../../misc/acct/render'; import { IUser } from '../../models/user'; -import Logger from '../../misc/logger'; +import Logger from '../../services/logger'; const logger = new Logger('limiter'); diff --git a/src/server/api/logger.ts b/src/server/api/logger.ts index 334a69633..0ea67a90e 100644 --- a/src/server/api/logger.ts +++ b/src/server/api/logger.ts @@ -1,3 +1,3 @@ -import Logger from "../../misc/logger"; +import Logger from "../../services/logger"; export const apiLogger = new Logger('api'); diff --git a/src/server/index.ts b/src/server/index.ts index 470562d80..7c51923f9 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,10 +23,10 @@ import networkChart from '../services/chart/network'; import apiServer from './api'; import { sum } from '../prelude/array'; import User from '../models/user'; -import Logger from '../misc/logger'; +import Logger from '../services/logger'; import { program } from '../argv'; -export const serverLogger = new Logger('server', 'gray'); +export const serverLogger = new Logger('server', 'gray', false); // Init app const app = new Koa(); diff --git a/src/server/web/url-preview.ts b/src/server/web/url-preview.ts index 9fda5f16f..90c1a4930 100644 --- a/src/server/web/url-preview.ts +++ b/src/server/web/url-preview.ts @@ -2,7 +2,7 @@ import * as Koa from 'koa'; import * as request from 'request-promise-native'; import summaly from 'summaly'; import fetchMeta from '../../misc/fetch-meta'; -import Logger from '../../misc/logger'; +import Logger from '../../services/logger'; const logger = new Logger('url-preview'); diff --git a/src/services/blocking/delete.ts b/src/services/blocking/delete.ts index 425648f4c..099fa14b3 100644 --- a/src/services/blocking/delete.ts +++ b/src/services/blocking/delete.ts @@ -4,7 +4,7 @@ import { renderActivity } from '../../remote/activitypub/renderer'; import renderBlock from '../../remote/activitypub/renderer/block'; import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; -import Logger from '../../misc/logger'; +import Logger from '../logger'; const logger = new Logger('blocking/delete'); diff --git a/src/services/chart/index.ts b/src/services/chart/index.ts index 2fabb5572..7a6470f4d 100644 --- a/src/services/chart/index.ts +++ b/src/services/chart/index.ts @@ -8,7 +8,7 @@ import autobind from 'autobind-decorator'; import * as mongo from 'mongodb'; import db from '../../db/mongodb'; import { ICollection } from 'monk'; -import Logger from '../../misc/logger'; +import Logger from '../logger'; import { Schema } from '../../misc/schema'; const logger = new Logger('chart'); diff --git a/src/services/drive/logger.ts b/src/services/drive/logger.ts index 979d282cc..b66db9ed8 100644 --- a/src/services/drive/logger.ts +++ b/src/services/drive/logger.ts @@ -1,3 +1,3 @@ -import Logger from "../../misc/logger"; +import Logger from "../logger"; export const driveLogger = new Logger('drive', 'blue'); diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 38f86fbf7..1eaad750f 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -13,7 +13,7 @@ import perUserFollowingChart from '../../services/chart/per-user-following'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Instance from '../../models/instance'; import instanceChart from '../../services/chart/instance'; -import Logger from '../../misc/logger'; +import Logger from '../logger'; import FollowRequest from '../../models/follow-request'; import { IdentifiableError } from '../../misc/identifiable-error'; diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index 93f72b51d..28268f1fd 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -6,7 +6,7 @@ import renderFollow from '../../remote/activitypub/renderer/follow'; import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; import perUserFollowingChart from '../../services/chart/per-user-following'; -import Logger from '../../misc/logger'; +import Logger from '../logger'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Instance from '../../models/instance'; import instanceChart from '../../services/chart/instance'; diff --git a/src/services/logger.ts b/src/services/logger.ts new file mode 100644 index 000000000..aa93954bc --- /dev/null +++ b/src/services/logger.ts @@ -0,0 +1,107 @@ +import * as cluster from 'cluster'; +import * as os from 'os'; +import chalk from 'chalk'; +import * as dateformat from 'dateformat'; +import { program } from '../argv'; +import Log from '../models/log'; + +type Domain = { + name: string; + color: string; +}; + +type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; + +export default class Logger { + private domain: Domain; + private parentLogger: Logger; + private store: boolean; + + constructor(domain: string, color?: string, store = true) { + this.domain = { + name: domain, + color: color, + }; + this.store = store; + } + + public createSubLogger(domain: string, color?: string, store = true): Logger { + const logger = new Logger(domain, color, store); + logger.parentLogger = this; + return logger; + } + + private log(level: Level, message: string, data: Record, important = false, subDomains: Domain[] = [], store = true): void { + if (program.quiet) return; + if (process.env.NODE_ENV === 'test') return; + if (!this.store) store = false; + + if (this.parentLogger) { + this.parentLogger.log(level, message, data, important, [this.domain].concat(subDomains), store); + return; + } + + const time = dateformat(new Date(), 'HH:MM:ss'); + const worker = cluster.isMaster ? '*' : cluster.worker.id; + const l = + level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') : + level === 'warning' ? chalk.yellow('WARN') : + level === 'success' ? important ? chalk.bgGreen.white('DONE') : chalk.green('DONE') : + level === 'debug' ? chalk.gray('VERB') : + level === 'info' ? chalk.blue('INFO') : + null; + const domains = [this.domain].concat(subDomains).map(d => d.color ? chalk.keyword(d.color)(d.name) : chalk.white(d.name)); + const m = + level === 'error' ? chalk.red(message) : + level === 'warning' ? chalk.yellow(message) : + level === 'success' ? chalk.green(message) : + level === 'debug' ? chalk.gray(message) : + level === 'info' ? message : + null; + + let log = `${l} ${worker}\t[${domains.join(' ')}]\t${m}`; + if (program.withLogTime) log = chalk.gray(time) + ' ' + log; + + console.log(important ? chalk.bold(log) : log); + + if (store) { + Log.insert({ + createdAt: new Date(), + machine: os.hostname(), + worker: worker, + domain: [this.domain].concat(subDomains).map(d => d.name), + level: level, + message: message, + data: data, + }); + } + } + + public error(x: string | Error, data?: Record, important = false): void { // 実行を継続できない状況で使う + if (x instanceof Error) { + data = data || {}; + data.e = x; + this.log('error', x.toString(), data, important); + } else { + this.log('error', x, data, important); + } + } + + public warn(message: string, data?: Record, important = false): void { // 実行を継続できるが改善すべき状況で使う + this.log('warning', message, data, important); + } + + public succ(message: string, data?: Record, important = false): void { // 何かに成功した状況で使う + this.log('success', message, data, important); + } + + public debug(message: string, data?: Record, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) + if (process.env.NODE_ENV != 'production' || program.verbose) { + this.log('debug', message, data, important); + } + } + + public info(message: string, data?: Record, important = false): void { // それ以外 + this.log('info', message, data, important); + } +}