parent
81a0ee4b2d
commit
632af91878
9 changed files with 17 additions and 295 deletions
|
@ -27,6 +27,10 @@
|
|||
- クライアント: リモートノートで意図せずローカルカスタム絵文字が使われてしまうことがあるのを修正
|
||||
- ActivityPub: not reacted な Undo.Like がinboxに滞留するのを修正
|
||||
|
||||
### Changes
|
||||
- データベースにログを保存しないようになりました
|
||||
- ログを永続化したい場合はsyslogを利用してください
|
||||
|
||||
## 12.92.0 (2021/10/16)
|
||||
|
||||
### Improvements
|
||||
|
|
13
migration/1634902659689-delete-log.ts
Normal file
13
migration/1634902659689-delete-log.ts
Normal 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> {
|
||||
}
|
||||
|
||||
}
|
|
@ -201,11 +201,6 @@ export default defineComponent({
|
|||
text: i18n.locale.database,
|
||||
to: '/admin/database',
|
||||
active: page.value === 'database',
|
||||
}, {
|
||||
icon: 'fas fa-stream',
|
||||
text: i18n.locale.logs,
|
||||
to: '/admin/logs',
|
||||
active: page.value === 'logs',
|
||||
}],
|
||||
}]);
|
||||
const component = computed(() => {
|
||||
|
@ -220,7 +215,6 @@ export default defineComponent({
|
|||
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
|
||||
case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
|
||||
case 'database': return defineAsyncComponent(() => import('./database.vue'));
|
||||
case 'logs': return defineAsyncComponent(() => import('./logs.vue'));
|
||||
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
|
||||
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
|
||||
case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));
|
||||
|
|
|
@ -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>
|
|
@ -8,7 +8,6 @@ import { entities as charts } from '@/services/chart/entities';
|
|||
import { dbLogger } from './logger';
|
||||
import * as highlight from 'cli-highlight';
|
||||
|
||||
import { Log } from '@/models/entities/log';
|
||||
import { User } from '@/models/entities/user';
|
||||
import { DriveFile } from '@/models/entities/drive-file';
|
||||
import { DriveFolder } from '@/models/entities/drive-folder';
|
||||
|
@ -144,7 +143,6 @@ export const entities = [
|
|||
PageLike,
|
||||
GalleryPost,
|
||||
GalleryLike,
|
||||
Log,
|
||||
DriveFile,
|
||||
DriveFolder,
|
||||
Poll,
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -13,7 +13,6 @@ import { UserRepository } from './repositories/user';
|
|||
import { NoteRepository } from './repositories/note';
|
||||
import { DriveFileRepository } from './repositories/drive-file';
|
||||
import { DriveFolderRepository } from './repositories/drive-folder';
|
||||
import { Log } from './entities/log';
|
||||
import { AccessToken } from './entities/access-token';
|
||||
import { UserNotePining } from './entities/user-note-pining';
|
||||
import { SigninRepository } from './repositories/signin';
|
||||
|
@ -108,7 +107,6 @@ export const Signins = getCustomRepository(SigninRepository);
|
|||
export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
|
||||
export const ReversiGames = getCustomRepository(ReversiGameRepository);
|
||||
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
|
||||
export const Logs = getRepository(Log);
|
||||
export const Pages = getCustomRepository(PageRepository);
|
||||
export const PageLikes = getCustomRepository(PageLikeRepository);
|
||||
export const GalleryPosts = getCustomRepository(GalleryPostRepository);
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -1,11 +1,7 @@
|
|||
import * as cluster from 'cluster';
|
||||
import * as os from 'os';
|
||||
import * as chalk from 'chalk';
|
||||
import * as dateformat from 'dateformat';
|
||||
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 * as SyslogPro from 'syslog-pro';
|
||||
|
@ -95,18 +91,6 @@ export default class Logger {
|
|||
null as never;
|
||||
|
||||
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(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue