データベースにログを保存するのを廃止

Close #7878
This commit is contained in:
syuilo 2021-10-22 20:41:15 +09:00
parent 81a0ee4b2d
commit 632af91878
9 changed files with 17 additions and 295 deletions

View File

@ -27,6 +27,10 @@
- クライアント: リモートノートで意図せずローカルカスタム絵文字が使われてしまうことがあるのを修正 - クライアント: リモートノートで意図せずローカルカスタム絵文字が使われてしまうことがあるのを修正
- ActivityPub: not reacted な Undo.Like がinboxに滞留するのを修正 - ActivityPub: not reacted な Undo.Like がinboxに滞留するのを修正
### Changes
- データベースにログを保存しないようになりました
- ログを永続化したい場合はsyslogを利用してください
## 12.92.0 (2021/10/16) ## 12.92.0 (2021/10/16)
### Improvements ### Improvements

View File

@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class deleteLog1634902659689 implements MigrationInterface {
name = 'deleteLog1634902659689'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "log"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View File

@ -201,11 +201,6 @@ export default defineComponent({
text: i18n.locale.database, text: i18n.locale.database,
to: '/admin/database', to: '/admin/database',
active: page.value === 'database', active: page.value === 'database',
}, {
icon: 'fas fa-stream',
text: i18n.locale.logs,
to: '/admin/logs',
active: page.value === 'logs',
}], }],
}]); }]);
const component = computed(() => { const component = computed(() => {
@ -220,7 +215,6 @@ export default defineComponent({
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
case 'ads': return defineAsyncComponent(() => import('./ads.vue')); case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue'));
case 'logs': return defineAsyncComponent(() => import('./logs.vue'));
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
case 'settings': return defineAsyncComponent(() => import('./settings.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));

View File

@ -1,97 +0,0 @@
<template>
<div class="_section">
<div class="_inputs">
<MkInput v-model="logDomain" :debounce="true">
<template #label>{{ $ts.domain }}</template>
</MkInput>
<MkSelect v-model="logLevel">
<template #label>Level</template>
<option value="all">All</option>
<option value="info">Info</option>
<option value="success">Success</option>
<option value="warning">Warning</option>
<option value="error">Error</option>
<option value="debug">Debug</option>
</MkSelect>
</div>
<div class="logs">
<code v-for="log in logs" :key="log.id" :class="log.level">
<details>
<summary><MkTime :time="log.createdAt"/> [{{ log.domain.join('.') }}] {{ log.message }}</summary>
<!--<vue-json-pretty v-if="log.data" :data="log.data"></vue-json-pretty>-->
</details>
</code>
</div>
<MkButton @click="deleteAllLogs()" primary><i class="fas fa-trash-alt"></i> {{ $ts.deleteAll }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/form/input.vue';
import MkSelect from '@client/components/form/select.vue';
import MkTextarea from '@client/components/form/textarea.vue';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
export default defineComponent({
components: {
MkButton,
MkInput,
MkSelect,
MkTextarea,
},
emits: ['info'],
data() {
return {
[symbols.PAGE_INFO]: {
title: this.$ts.serverLogs,
icon: 'fas fa-stream'
},
logs: [],
logLevel: 'all',
logDomain: '',
}
},
watch: {
logLevel() {
this.logs = [];
this.fetchLogs();
},
logDomain() {
this.logs = [];
this.fetchLogs();
}
},
created() {
this.fetchLogs();
},
mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
},
methods: {
fetchLogs() {
os.api('admin/logs', {
level: this.logLevel === 'all' ? null : this.logLevel,
domain: this.logDomain === '' ? null : this.logDomain,
limit: 30
}).then(logs => {
this.logs = logs.reverse();
});
},
deleteAllLogs() {
os.apiWithDialog('admin/delete-logs');
},
}
});
</script>

View File

@ -8,7 +8,6 @@ import { entities as charts } from '@/services/chart/entities';
import { dbLogger } from './logger'; import { dbLogger } from './logger';
import * as highlight from 'cli-highlight'; import * as highlight from 'cli-highlight';
import { Log } from '@/models/entities/log';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { DriveFile } from '@/models/entities/drive-file'; import { DriveFile } from '@/models/entities/drive-file';
import { DriveFolder } from '@/models/entities/drive-folder'; import { DriveFolder } from '@/models/entities/drive-folder';
@ -144,7 +143,6 @@ export const entities = [
PageLike, PageLike,
GalleryPost, GalleryPost,
GalleryLike, GalleryLike,
Log,
DriveFile, DriveFile,
DriveFolder, DriveFolder,
Poll, Poll,

View File

@ -1,46 +0,0 @@
import { Entity, PrimaryColumn, Index, Column } from 'typeorm';
import { id } from '../id';
@Entity()
export class Log {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the Log.'
})
public createdAt: Date;
@Index()
@Column('varchar', {
length: 64, array: true, default: '{}'
})
public domain: string[];
@Index()
@Column('enum', {
enum: ['error', 'warning', 'info', 'success', 'debug']
})
public level: string;
@Column('varchar', {
length: 8
})
public worker: string;
@Column('varchar', {
length: 128
})
public machine: string;
@Column('varchar', {
length: 2048
})
public message: string;
@Column('jsonb', {
default: {}
})
public data: Record<string, any>;
}

View File

@ -13,7 +13,6 @@ import { UserRepository } from './repositories/user';
import { NoteRepository } from './repositories/note'; import { NoteRepository } from './repositories/note';
import { DriveFileRepository } from './repositories/drive-file'; import { DriveFileRepository } from './repositories/drive-file';
import { DriveFolderRepository } from './repositories/drive-folder'; import { DriveFolderRepository } from './repositories/drive-folder';
import { Log } from './entities/log';
import { AccessToken } from './entities/access-token'; import { AccessToken } from './entities/access-token';
import { UserNotePining } from './entities/user-note-pining'; import { UserNotePining } from './entities/user-note-pining';
import { SigninRepository } from './repositories/signin'; import { SigninRepository } from './repositories/signin';
@ -108,7 +107,6 @@ export const Signins = getCustomRepository(SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository); export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
export const ReversiGames = getCustomRepository(ReversiGameRepository); export const ReversiGames = getCustomRepository(ReversiGameRepository);
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
export const Logs = getRepository(Log);
export const Pages = getCustomRepository(PageRepository); export const Pages = getCustomRepository(PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository); export const PageLikes = getCustomRepository(PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository); export const GalleryPosts = getCustomRepository(GalleryPostRepository);

View File

@ -1,126 +0,0 @@
import $ from 'cafy';
import define from '../../define';
import { Logs } from '@/models/index';
import { Brackets } from 'typeorm';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireModerator: true,
params: {
limit: {
validator: $.optional.num.range(1, 100),
default: 30
},
level: {
validator: $.optional.nullable.str,
default: null
},
domain: {
validator: $.optional.nullable.str,
default: null
}
},
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
domain: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: true as const, nullable: false as const
}
},
level: {
type: 'string' as const,
optional: false as const, nullable: false as const
},
worker: {
type: 'string' as const,
optional: false as const, nullable: false as const
},
machine: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
message: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
data: {
type: 'object' as const,
optional: false as const, nullable: false as const
}
}
}
}
};
export default define(meta, async (ps) => {
const query = Logs.createQueryBuilder('log');
if (ps.level) query.andWhere('log.level = :level', { level: ps.level });
if (ps.domain) {
const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-'));
const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')).map(x => x.substr(1));
if (whiteDomains.length > 0) {
query.andWhere(new Brackets(qb => {
for (const whiteDomain of whiteDomains) {
let i = 0;
for (const subDomain of whiteDomain.split('.')) {
const p = `whiteSubDomain_${subDomain}_${i}`;
// SQL is 1 based, so we need '+ 1'
qb.orWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain });
i++;
}
}
}));
}
if (blackDomains.length > 0) {
query.andWhere(new Brackets(qb => {
for (const blackDomain of blackDomains) {
qb.andWhere(new Brackets(qb => {
const subDomains = blackDomain.split('.');
let i = 0;
for (const subDomain of subDomains) {
const p = `blackSubDomain_${subDomain}_${i}`;
// 全体で否定できないのでド・モルガンの法則で
// !(P && Q) を !P || !Q で表す
// SQL is 1 based, so we need '+ 1'
qb.orWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain });
i++;
}
}));
}
}));
}
}
const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit!).getMany();
return logs;
});

View File

@ -1,11 +1,7 @@
import * as cluster from 'cluster'; import * as cluster from 'cluster';
import * as os from 'os';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import * as dateformat from 'dateformat'; import * as dateformat from 'dateformat';
import { envOption } from '../env'; import { envOption } from '../env';
import { getRepository } from 'typeorm';
import { Log } from '@/models/entities/log';
import { genId } from '@/misc/gen-id';
import config from '@/config/index'; import config from '@/config/index';
import * as SyslogPro from 'syslog-pro'; import * as SyslogPro from 'syslog-pro';
@ -95,18 +91,6 @@ export default class Logger {
null as never; null as never;
send.bind(this.syslogClient)(message).catch(() => {}); send.bind(this.syslogClient)(message).catch(() => {});
} else {
const Logs = getRepository(Log);
Logs.insert({
id: genId(),
createdAt: new Date(),
machine: os.hostname(),
worker: worker.toString(),
domain: [this.domain].concat(subDomains).map(d => d.name),
level: level,
message: message.substr(0, 1000), // 1024を超えるとログが挿入できずエラーになり無限ループする
data: data,
} as Log).catch(() => {});
} }
} }
} }