From b28c34878bb38fd334a06e09533979a41d0ad30f Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Fri, 28 May 2021 16:26:36 +0700 Subject: [PATCH] added wtf command --- src/api/telegram/index.ts | 28 ++++++++++++++++++++++ src/index.ts | 3 +++ src/service/db/postgres/entities/Log.ts | 23 ++++++++++++++++++ src/service/db/postgres/index.ts | 24 +++++++++++++++++++ src/service/db/postgres/loggerTransport.ts | 21 ++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 src/service/db/postgres/entities/Log.ts create mode 100644 src/service/db/postgres/loggerTransport.ts diff --git a/src/api/telegram/index.ts b/src/api/telegram/index.ts index add2032..5c19fba 100644 --- a/src/api/telegram/index.ts +++ b/src/api/telegram/index.ts @@ -15,6 +15,8 @@ export class TelegramApi { this.telegram.bot.command("ping", TelegramApi.ping); this.telegram.bot.command("config", this.dumpConfig); this.telegram.bot.command("pop", this.pop); + this.telegram.bot.command("wtf", this.wtf); + return; } @@ -76,6 +78,32 @@ export class TelegramApi { return next(); }; + /** + * Sends recent logs + */ + private wtf = async (ctx, next) => { + const username = ctx?.update?.message?.from?.username; + + if (!username || !this.telegram.isOwner(`@${username}`)) { + return; + } + + const logs = await this.db.popLogs(); + if (!logs || !logs.length) { + await ctx.reply(`sorry, no logged errors yet`); + return next(); + } + + const source = JSON.stringify(logs, null, 2); + + await ctx.replyWithDocument({ + source: Readable.from(source), + filename: `logs-${new Date().toISOString()}.txt`, + }); + + return next(); + }; + /** * Probes webhook url and falls back to polling mode on error */ diff --git a/src/index.ts b/src/index.ts index 10cf4c8..a6b801a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { VkService } from "./service/vk"; import { TelegramApi } from "./api/telegram"; import { HttpApi } from "./api/http"; import { PostgresDB } from "./service/db/postgres"; +import { PgTransport } from "./service/db/postgres/loggerTransport"; async function main() { try { @@ -13,6 +14,8 @@ async function main() { const db = new PostgresDB(config.postgres, config.logger); await db.connect(); + logger.add(new PgTransport(db, { level: "warn" })); + const telegram = new TelegramService(config.telegram); const vkService = new VkService(config.vk, telegram, config.templates, db); diff --git a/src/service/db/postgres/entities/Log.ts b/src/service/db/postgres/entities/Log.ts new file mode 100644 index 0000000..b359b1c --- /dev/null +++ b/src/service/db/postgres/entities/Log.ts @@ -0,0 +1,23 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from "typeorm"; + +@Entity() +export class Log { + @PrimaryGeneratedColumn() + id!: number; + @Column() + level!: string; + @Column({ type: "text" }) + message!: string; + @Column("simple-json", { default: {}, nullable: false }) + body!: Record; + @CreateDateColumn() + createdAt!: Date; + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/src/service/db/postgres/index.ts b/src/service/db/postgres/index.ts index 23181d6..0c2cf15 100644 --- a/src/service/db/postgres/index.ts +++ b/src/service/db/postgres/index.ts @@ -9,6 +9,7 @@ import { Event } from "./entities/Event"; import { Post } from "./entities/Post"; import { LoggerConfig } from "../../logger/types"; import { Request } from "./entities/Request"; +import { Log } from "./entities/Log"; const entities = [path.join(__dirname, "./entities/*")]; @@ -18,6 +19,7 @@ export class PostgresDB implements Storage { private likes!: Repository; private posts!: Repository; private requests!: Repository; + private logs!: Repository; constructor( private config: PostgresConfig, @@ -41,6 +43,7 @@ export class PostgresDB implements Storage { this.likes = this.connection.getRepository(Like); this.posts = this.connection.getRepository(Post); this.requests = this.connection.getRepository(Request); + this.logs = this.connection.getRepository(Log); logger.info(`db connected to ${this.config.uri}`); }; @@ -151,6 +154,27 @@ export class PostgresDB implements Storage { return requests[0]; }; + insertLog = async ( + level: string, + message: string, + body: Record + ) => { + return this.logs.save({ message, level, body }); + }; + + /** + * Returns last request with shift + */ + popLogs = async (take: number = 20, skip: number = 0) => { + const requests = await this.logs.find({ + order: { createdAt: "DESC" }, + take, + skip, + }); + + return requests.reverse(); + }; + healthcheck = async () => { try { await this.connection.query("SELECT 1"); diff --git a/src/service/db/postgres/loggerTransport.ts b/src/service/db/postgres/loggerTransport.ts new file mode 100644 index 0000000..272505d --- /dev/null +++ b/src/service/db/postgres/loggerTransport.ts @@ -0,0 +1,21 @@ +import Transport, { TransportStreamOptions } from "winston-transport"; +import { PostgresDB } from "./index"; +import safeJson from "safe-json-stringify"; + +export class PgTransport extends Transport { + constructor(private db: PostgresDB, private props: TransportStreamOptions) { + super(props); + } + + log = async (info, callback) => { + try { + await this.db.insertLog(info.level, info.message, info); + } catch (e) { + await this.db.insertLog(info.level, info.message, { + dump: safeJson(info), + }); + } + + callback(); + }; +}