ログをデータベースに保存して管理画面で見れるように
This commit is contained in:
parent
f3ceb32a7c
commit
977af0a24d
29 changed files with 326 additions and 99 deletions
|
@ -21,6 +21,7 @@
|
||||||
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li>
|
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li>
|
||||||
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
|
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
|
||||||
<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
|
<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li>
|
||||||
|
<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li>
|
||||||
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
|
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
|
||||||
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
|
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
|
||||||
<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
|
<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li>
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
<div v-if="page == 'dashboard'"><x-dashboard/></div>
|
<div v-if="page == 'dashboard'"><x-dashboard/></div>
|
||||||
<div v-if="page == 'instance'"><x-instance/></div>
|
<div v-if="page == 'instance'"><x-instance/></div>
|
||||||
<div v-if="page == 'queue'"><x-queue/></div>
|
<div v-if="page == 'queue'"><x-queue/></div>
|
||||||
|
<div v-if="page == 'logs'"><x-logs/></div>
|
||||||
<div v-if="page == 'moderators'"><x-moderators/></div>
|
<div v-if="page == 'moderators'"><x-moderators/></div>
|
||||||
<div v-if="page == 'users'"><x-users/></div>
|
<div v-if="page == 'users'"><x-users/></div>
|
||||||
<div v-if="page == 'emoji'"><x-emoji/></div>
|
<div v-if="page == 'emoji'"><x-emoji/></div>
|
||||||
|
@ -62,6 +64,7 @@ import { version } from '../../config';
|
||||||
import XDashboard from "./dashboard.vue";
|
import XDashboard from "./dashboard.vue";
|
||||||
import XInstance from "./instance.vue";
|
import XInstance from "./instance.vue";
|
||||||
import XQueue from "./queue.vue";
|
import XQueue from "./queue.vue";
|
||||||
|
import XLogs from "./logs.vue";
|
||||||
import XModerators from "./moderators.vue";
|
import XModerators from "./moderators.vue";
|
||||||
import XEmoji from "./emoji.vue";
|
import XEmoji from "./emoji.vue";
|
||||||
import XAnnouncements from "./announcements.vue";
|
import XAnnouncements from "./announcements.vue";
|
||||||
|
@ -71,7 +74,7 @@ import XDrive from "./drive.vue";
|
||||||
import XAbuse from "./abuse.vue";
|
import XAbuse from "./abuse.vue";
|
||||||
import XFederation from "./federation.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';
|
import { faGrin } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
// Detect the user agent
|
// Detect the user agent
|
||||||
|
@ -84,6 +87,7 @@ export default Vue.extend({
|
||||||
XDashboard,
|
XDashboard,
|
||||||
XInstance,
|
XInstance,
|
||||||
XQueue,
|
XQueue,
|
||||||
|
XLogs,
|
||||||
XModerators,
|
XModerators,
|
||||||
XEmoji,
|
XEmoji,
|
||||||
XAnnouncements,
|
XAnnouncements,
|
||||||
|
@ -107,7 +111,8 @@ export default Vue.extend({
|
||||||
faHeadset,
|
faHeadset,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
faTasks
|
faTasks,
|
||||||
|
faStream
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
101
src/client/app/admin/views/logs.vue
Normal file
101
src/client/app/admin/views/logs.vue
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ui-card>
|
||||||
|
<template #title><fa :icon="faStream"/> {{ $t('logs') }}</template>
|
||||||
|
<section class="fit-top">
|
||||||
|
<ui-horizon-group inputs>
|
||||||
|
<ui-input v-model="domain">
|
||||||
|
<span>{{ $t('domain') }}</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-select v-model="level">
|
||||||
|
<template #label>{{ $t('level') }}</template>
|
||||||
|
<option value="all">{{ $t('levels.all') }}</option>
|
||||||
|
<option value="info">{{ $t('levels.info') }}</option>
|
||||||
|
<option value="success">{{ $t('levels.success') }}</option>
|
||||||
|
<option value="warning">{{ $t('levels.warning') }}</option>
|
||||||
|
<option value="error">{{ $t('levels.error') }}</option>
|
||||||
|
<option value="debug">{{ $t('levels.debug') }}</option>
|
||||||
|
</ui-select>
|
||||||
|
</ui-horizon-group>
|
||||||
|
|
||||||
|
<div class="nqjzuvev">
|
||||||
|
<code v-for="log in logs" :key="log._id" :class="log.level">
|
||||||
|
<mk-time :time="log.createdAt"/> [{{ log.domain.join(' ') }}] {{ log.message }}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../i18n';
|
||||||
|
import { faStream } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n('admin/views/logs.vue'),
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logs: [],
|
||||||
|
level: 'all',
|
||||||
|
domain: '',
|
||||||
|
faStream
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
level() {
|
||||||
|
this.logs = [];
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
domain() {
|
||||||
|
this.logs = [];
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.$root.api('admin/logs', {
|
||||||
|
level: this.level === 'all' ? null : this.level,
|
||||||
|
domain: this.domain === '' ? null : this.domain,
|
||||||
|
limit: 50
|
||||||
|
}).then(logs => {
|
||||||
|
this.logs = logs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.nqjzuvev
|
||||||
|
white-space nowrap
|
||||||
|
overflow auto
|
||||||
|
padding 8px
|
||||||
|
background #000
|
||||||
|
color #fff
|
||||||
|
|
||||||
|
> code
|
||||||
|
display block
|
||||||
|
|
||||||
|
&.error
|
||||||
|
color #f00
|
||||||
|
|
||||||
|
&.warning
|
||||||
|
color #ff0
|
||||||
|
|
||||||
|
&.success
|
||||||
|
color #0f0
|
||||||
|
|
||||||
|
&.debug
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
import * as elasticsearch from 'elasticsearch';
|
import * as elasticsearch from 'elasticsearch';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import Logger from '../misc/logger';
|
import Logger from '../services/logger';
|
||||||
|
|
||||||
const esLogger = new Logger('es');
|
const esLogger = new Logger('es');
|
||||||
|
|
||||||
|
|
3
src/db/logger.ts
Normal file
3
src/db/logger.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import Logger from '../services/logger';
|
||||||
|
|
||||||
|
export const dbLogger = new Logger('db');
|
|
@ -18,7 +18,6 @@ export default db;
|
||||||
* MongoDB native module (officialy)
|
* MongoDB native module (officialy)
|
||||||
*/
|
*/
|
||||||
import * as mongodb from 'mongodb';
|
import * as mongodb from 'mongodb';
|
||||||
import Logger from '../misc/logger';
|
|
||||||
|
|
||||||
let mdb: mongodb.Db;
|
let mdb: mongodb.Db;
|
||||||
|
|
||||||
|
@ -38,5 +37,3 @@ const nativeDbConn = async (): Promise<mongodb.Db> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export { nativeDbConn };
|
export { nativeDbConn };
|
||||||
|
|
||||||
export const dbLogger = new Logger('db');
|
|
||||||
|
|
28
src/index.ts
28
src/index.ts
|
@ -13,7 +13,7 @@ import * as portscanner from 'portscanner';
|
||||||
import * as isRoot from 'is-root';
|
import * as isRoot from 'is-root';
|
||||||
import Xev from 'xev';
|
import Xev from 'xev';
|
||||||
|
|
||||||
import Logger from './misc/logger';
|
import Logger from './services/logger';
|
||||||
import serverStats from './daemons/server-stats';
|
import serverStats from './daemons/server-stats';
|
||||||
import notesStats from './daemons/notes-stats';
|
import notesStats from './daemons/notes-stats';
|
||||||
import loadConfig from './config/load';
|
import loadConfig from './config/load';
|
||||||
|
@ -25,7 +25,7 @@ import { checkMongoDB } from './misc/check-mongodb';
|
||||||
import { showMachineInfo } from './misc/show-machine-info';
|
import { showMachineInfo } from './misc/show-machine-info';
|
||||||
|
|
||||||
const logger = new Logger('core', 'cyan');
|
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 clusterLogger = logger.createSubLogger('cluster', 'orange');
|
||||||
const ev = new Xev();
|
const ev = new Xev();
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ function greet() {
|
||||||
console.log(chalk`${os.hostname()} {gray (PID: ${process.pid.toString()})}`);
|
console.log(chalk`${os.hostname()} {gray (PID: ${process.pid.toString()})}`);
|
||||||
|
|
||||||
bootLogger.info('Welcome to Misskey!');
|
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.');
|
bootLogger.info('Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,21 +90,21 @@ async function masterMain() {
|
||||||
config = await init();
|
config = await init();
|
||||||
|
|
||||||
if (config.port == null) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await isPortAvailable(config.port)) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bootLogger.error('Fatal error occurred during initialization', true);
|
bootLogger.error('Fatal error occurred during initialization', null, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ async function masterMain() {
|
||||||
// start queue
|
// start queue
|
||||||
require('./queue').default();
|
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
|
// initialize app
|
||||||
await init();
|
await init();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bootLogger.error('Fatal error occurred during initialization', true);
|
bootLogger.error('Fatal error occurred during initialization', null, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ async function queueMain() {
|
||||||
const queue = require('./queue').default();
|
const queue = require('./queue').default();
|
||||||
|
|
||||||
if (queue) {
|
if (queue) {
|
||||||
bootLogger.succ('Queue started', true);
|
bootLogger.succ('Queue started', null, true);
|
||||||
} else {
|
} else {
|
||||||
bootLogger.error('Queue not available');
|
bootLogger.error('Queue not available');
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ function showEnvironment(): void {
|
||||||
|
|
||||||
if (env !== 'production') {
|
if (env !== 'production') {
|
||||||
logger.warn('The environment is not in production mode.');
|
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`);
|
logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
|
||||||
|
@ -192,7 +192,7 @@ async function init(): Promise<Config> {
|
||||||
nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`);
|
nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`);
|
||||||
|
|
||||||
if (!satisfyNodejsVersion) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ async function init(): Promise<Config> {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (exception.code === 'ENOENT') {
|
if (exception.code === 'ENOENT') {
|
||||||
configLogger.error('Configuration file not found', true);
|
configLogger.error('Configuration file not found', null, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
throw exception;
|
throw exception;
|
||||||
|
@ -221,7 +221,7 @@ async function init(): Promise<Config> {
|
||||||
try {
|
try {
|
||||||
await checkMongoDB(config, bootLogger);
|
await checkMongoDB(config, bootLogger);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bootLogger.error('Cannot connect to database', true);
|
bootLogger.error('Cannot connect to database', null, true);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { nativeDbConn } from '../db/mongodb';
|
import { nativeDbConn } from '../db/mongodb';
|
||||||
import { Config } from '../config/types';
|
import { Config } from '../config/types';
|
||||||
import Logger from './logger';
|
import Logger from '../services/logger';
|
||||||
import { lessThan } from '../prelude/array';
|
import { lessThan } from '../prelude/array';
|
||||||
|
|
||||||
const requiredMongoDBVersion = [3, 6];
|
const requiredMongoDBVersion = [3, 6];
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as sysUtils from 'systeminformation';
|
import * as sysUtils from 'systeminformation';
|
||||||
import Logger from './logger';
|
import Logger from '../services/logger';
|
||||||
|
|
||||||
export async function showMachineInfo(parentLogger: Logger) {
|
export async function showMachineInfo(parentLogger: Logger) {
|
||||||
const logger = parentLogger.createSubLogger('machine');
|
const logger = parentLogger.createSubLogger('machine');
|
||||||
|
|
|
@ -2,9 +2,10 @@ import * as mongo from 'mongodb';
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import { pack as packFolder } from './drive-folder';
|
import { pack as packFolder } from './drive-folder';
|
||||||
import { pack as packUser } from './user';
|
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 isObjectId from '../misc/is-objectid';
|
||||||
import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url';
|
import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url';
|
||||||
|
import { dbLogger } from '../db/logger';
|
||||||
|
|
||||||
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
||||||
DriveFile.createIndex('md5');
|
DriveFile.createIndex('md5');
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import db, { dbLogger } from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import isObjectId from '../misc/is-objectid';
|
import isObjectId from '../misc/is-objectid';
|
||||||
import { pack as packNote } from './note';
|
import { pack as packNote } from './note';
|
||||||
|
import { dbLogger } from '../db/logger';
|
||||||
|
|
||||||
const Favorite = db.get<IFavorite>('favorites');
|
const Favorite = db.get<IFavorite>('favorites');
|
||||||
Favorite.createIndex('userId');
|
Favorite.createIndex('userId');
|
||||||
|
|
17
src/models/log.ts
Normal file
17
src/models/log.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
|
import db from '../db/mongodb';
|
||||||
|
|
||||||
|
const Log = db.get<ILog>('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;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import db, { dbLogger } from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import isObjectId from '../misc/is-objectid';
|
import isObjectId from '../misc/is-objectid';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import { IUser, pack as packUser } from './user';
|
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 { packMany as packFileMany, IDriveFile } from './drive-file';
|
||||||
import Following from './following';
|
import Following from './following';
|
||||||
import Emoji from './emoji';
|
import Emoji from './emoji';
|
||||||
|
import { dbLogger } from '../db/logger';
|
||||||
|
|
||||||
const Note = db.get<INote>('notes');
|
const Note = db.get<INote>('notes');
|
||||||
Note.createIndex('uri', { sparse: true, unique: true });
|
Note.createIndex('uri', { sparse: true, unique: true });
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import db, { dbLogger } from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import isObjectId from '../misc/is-objectid';
|
import isObjectId from '../misc/is-objectid';
|
||||||
import { IUser, pack as packUser } from './user';
|
import { IUser, pack as packUser } from './user';
|
||||||
import { pack as packNote } from './note';
|
import { pack as packNote } from './note';
|
||||||
|
import { dbLogger } from '../db/logger';
|
||||||
|
|
||||||
const Notification = db.get<INotification>('notifications');
|
const Notification = db.get<INotification>('notifications');
|
||||||
Notification.createIndex('notifieeId');
|
Notification.createIndex('notifieeId');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import db, { dbLogger } from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import isObjectId from '../misc/is-objectid';
|
import isObjectId from '../misc/is-objectid';
|
||||||
import { packMany as packNoteMany } from './note';
|
import { packMany as packNoteMany } from './note';
|
||||||
import Following from './following';
|
import Following from './following';
|
||||||
|
@ -12,6 +12,7 @@ import config from '../config';
|
||||||
import FollowRequest from './follow-request';
|
import FollowRequest from './follow-request';
|
||||||
import fetchMeta from '../misc/fetch-meta';
|
import fetchMeta from '../misc/fetch-meta';
|
||||||
import Emoji from './emoji';
|
import Emoji from './emoji';
|
||||||
|
import { dbLogger } from '../db/logger';
|
||||||
|
|
||||||
const User = db.get<IUser>('users');
|
const User = db.get<IUser>('users');
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import Logger from '../misc/logger';
|
import Logger from '../services/logger';
|
||||||
|
|
||||||
export const queueLogger = new Logger('queue', 'orange');
|
export const queueLogger = new Logger('queue', 'orange');
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { publishApLogStream } from '../../../services/stream';
|
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 { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
|
||||||
import Instance from '../../../models/instance';
|
import Instance from '../../../models/instance';
|
||||||
import instanceChart from '../../../services/chart/instance';
|
import instanceChart from '../../../services/chart/instance';
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import Logger from "../misc/logger";
|
import Logger from "../services/logger";
|
||||||
|
|
||||||
export const remoteLogger = new Logger('remote', 'cyan');
|
export const remoteLogger = new Logger('remote', 'cyan');
|
||||||
|
|
51
src/server/api/endpoints/admin/logs.ts
Normal file
51
src/server/api/endpoints/admin/logs.ts
Normal file
|
@ -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;
|
||||||
|
});
|
|
@ -3,7 +3,7 @@ import limiterDB from '../../db/redis';
|
||||||
import { IEndpoint } from './endpoints';
|
import { IEndpoint } from './endpoints';
|
||||||
import getAcct from '../../misc/acct/render';
|
import getAcct from '../../misc/acct/render';
|
||||||
import { IUser } from '../../models/user';
|
import { IUser } from '../../models/user';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../../services/logger';
|
||||||
|
|
||||||
const logger = new Logger('limiter');
|
const logger = new Logger('limiter');
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import Logger from "../../misc/logger";
|
import Logger from "../../services/logger";
|
||||||
|
|
||||||
export const apiLogger = new Logger('api');
|
export const apiLogger = new Logger('api');
|
||||||
|
|
|
@ -23,10 +23,10 @@ import networkChart from '../services/chart/network';
|
||||||
import apiServer from './api';
|
import apiServer from './api';
|
||||||
import { sum } from '../prelude/array';
|
import { sum } from '../prelude/array';
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
import Logger from '../misc/logger';
|
import Logger from '../services/logger';
|
||||||
import { program } from '../argv';
|
import { program } from '../argv';
|
||||||
|
|
||||||
export const serverLogger = new Logger('server', 'gray');
|
export const serverLogger = new Logger('server', 'gray', false);
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
const app = new Koa();
|
const app = new Koa();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as Koa from 'koa';
|
||||||
import * as request from 'request-promise-native';
|
import * as request from 'request-promise-native';
|
||||||
import summaly from 'summaly';
|
import summaly from 'summaly';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../../services/logger';
|
||||||
|
|
||||||
const logger = new Logger('url-preview');
|
const logger = new Logger('url-preview');
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { renderActivity } from '../../remote/activitypub/renderer';
|
||||||
import renderBlock from '../../remote/activitypub/renderer/block';
|
import renderBlock from '../../remote/activitypub/renderer/block';
|
||||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../logger';
|
||||||
|
|
||||||
const logger = new Logger('blocking/delete');
|
const logger = new Logger('blocking/delete');
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import autobind from 'autobind-decorator';
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import db from '../../db/mongodb';
|
import db from '../../db/mongodb';
|
||||||
import { ICollection } from 'monk';
|
import { ICollection } from 'monk';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../logger';
|
||||||
import { Schema } from '../../misc/schema';
|
import { Schema } from '../../misc/schema';
|
||||||
|
|
||||||
const logger = new Logger('chart');
|
const logger = new Logger('chart');
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import Logger from "../../misc/logger";
|
import Logger from "../logger";
|
||||||
|
|
||||||
export const driveLogger = new Logger('drive', 'blue');
|
export const driveLogger = new Logger('drive', 'blue');
|
||||||
|
|
|
@ -13,7 +13,7 @@ import perUserFollowingChart from '../../services/chart/per-user-following';
|
||||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
import Instance from '../../models/instance';
|
import Instance from '../../models/instance';
|
||||||
import instanceChart from '../../services/chart/instance';
|
import instanceChart from '../../services/chart/instance';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../logger';
|
||||||
import FollowRequest from '../../models/follow-request';
|
import FollowRequest from '../../models/follow-request';
|
||||||
import { IdentifiableError } from '../../misc/identifiable-error';
|
import { IdentifiableError } from '../../misc/identifiable-error';
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import renderFollow from '../../remote/activitypub/renderer/follow';
|
||||||
import renderUndo from '../../remote/activitypub/renderer/undo';
|
import renderUndo from '../../remote/activitypub/renderer/undo';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
import perUserFollowingChart from '../../services/chart/per-user-following';
|
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 { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
||||||
import Instance from '../../models/instance';
|
import Instance from '../../models/instance';
|
||||||
import instanceChart from '../../services/chart/instance';
|
import instanceChart from '../../services/chart/instance';
|
||||||
|
|
107
src/services/logger.ts
Normal file
107
src/services/logger.ts
Normal file
|
@ -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<string, any>, 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<string, any>, 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<string, any>, important = false): void { // 実行を継続できるが改善すべき状況で使う
|
||||||
|
this.log('warning', message, data, important);
|
||||||
|
}
|
||||||
|
|
||||||
|
public succ(message: string, data?: Record<string, any>, important = false): void { // 何かに成功した状況で使う
|
||||||
|
this.log('success', message, data, important);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(message: string, data?: Record<string, any>, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報)
|
||||||
|
if (process.env.NODE_ENV != 'production' || program.verbose) {
|
||||||
|
this.log('debug', message, data, important);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(message: string, data?: Record<string, any>, important = false): void { // それ以外
|
||||||
|
this.log('info', message, data, important);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue