parent
81a0ee4b2d
commit
632af91878
@ -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
|
||||||
|
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,
|
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'));
|
||||||
|
@ -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 { 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,
|
||||||
|
@ -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 { 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);
|
||||||
|
@ -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 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(() => {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user