From 3c44673b8df30409d568ad01f60ca4da35d6f8f6 Mon Sep 17 00:00:00 2001 From: Mavrik Date: Sat, 11 May 2024 10:48:18 +0200 Subject: [PATCH] fix: remove logs from gitignore --- backend/.gitignore | 1 - backend/src/logs/entities/log.entity.ts | 26 +++++ backend/src/logs/logs.controller.ts | 33 ++++++ backend/src/logs/logs.module.ts | 14 +++ backend/src/logs/logs.service.ts | 100 ++++++++++++++++++ .../src/logs/tests/logs.controller.spec.ts | 18 ++++ backend/src/logs/tests/logs.service.spec.ts | 18 ++++ 7 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 backend/src/logs/entities/log.entity.ts create mode 100644 backend/src/logs/logs.controller.ts create mode 100644 backend/src/logs/logs.module.ts create mode 100644 backend/src/logs/logs.service.ts create mode 100644 backend/src/logs/tests/logs.controller.spec.ts create mode 100644 backend/src/logs/tests/logs.service.spec.ts diff --git a/backend/.gitignore b/backend/.gitignore index 00a87549..93842551 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -4,7 +4,6 @@ /build # Logs -logs *.log npm-debug.log* pnpm-debug.log* diff --git a/backend/src/logs/entities/log.entity.ts b/backend/src/logs/entities/log.entity.ts new file mode 100644 index 00000000..73328477 --- /dev/null +++ b/backend/src/logs/entities/log.entity.ts @@ -0,0 +1,26 @@ +import {Table, Column, Model, PrimaryKey, AutoIncrement, Unique} from 'sequelize-typescript' +import { LogLevels, LogType } from '../../../../src/types'; + + +@Table({ + tableName: 'logs', +}) +export class Log extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + type: LogType; + + @Column + level: LogLevels; + + @Unique('compositeIndex') + @Column + data: string; + + @Column + isHidden: boolean +} diff --git a/backend/src/logs/logs.controller.ts b/backend/src/logs/logs.controller.ts new file mode 100644 index 00000000..78498620 --- /dev/null +++ b/backend/src/logs/logs.controller.ts @@ -0,0 +1,33 @@ +// src/logs/logs.controller.ts +import { Controller, Get, Res, Req, Param, UseGuards } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { LogsService } from './logs.service'; +import { SessionGuard } from '../session.guard'; + +@Controller('logs') +@UseGuards(SessionGuard) +export class LogsController { + constructor(private logsService: LogsService) {} + + @Get('validator') + getValidatorLogs(@Res() res: Response, @Req() req: Request) { + const validatorUrl = process.env.VALIDATOR_URL; + this.logsService.getSseStream(req, res, `${validatorUrl}/lighthouse/logs`); + } + + @Get('beacon') + getBeaconLogs(@Res() res: Response, @Req() req: Request) { + const beaconUrl = process.env.BEACON_URL; + this.logsService.getSseStream(req, res, `${beaconUrl}/lighthouse/logs`); + } + + @Get('metrics') + getLogMetrics() { + return this.logsService.readLogMetrics() + } + + @Get('dismiss/:index') + dismissLogAlert(@Param('index') index: string) { + return this.logsService.dismissLog(index) + } +} diff --git a/backend/src/logs/logs.module.ts b/backend/src/logs/logs.module.ts new file mode 100644 index 00000000..b07e9061 --- /dev/null +++ b/backend/src/logs/logs.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { LogsController } from './logs.controller'; +import { UtilsModule } from '../utils/utils.module'; +import { LogsService } from './logs.service'; +import { SequelizeModule } from '@nestjs/sequelize'; +import { Log } from './entities/log.entity'; + +@Module({ + imports: [UtilsModule, SequelizeModule.forFeature([Log])], + controllers: [LogsController], + providers: [LogsService], + exports: [LogsService] +}) +export class LogsModule {} diff --git a/backend/src/logs/logs.service.ts b/backend/src/logs/logs.service.ts new file mode 100644 index 00000000..4becb054 --- /dev/null +++ b/backend/src/logs/logs.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common'; +import {Subject} from 'rxjs'; +import { Request, Response } from 'express'; +import * as EventSource from 'eventsource'; +import { LogLevels, LogType, SSELog } from '../../../src/types'; +import { InjectModel } from '@nestjs/sequelize'; +import { Log } from './entities/log.entity'; + +@Injectable() +export class LogsService { + constructor( + @InjectModel(Log) + private logRepository: typeof Log + ) {} + + private sseStreams: Map> = new Map(); + + public async startSse(url: string, type: LogType) { + console.log(`starting sse ${url}, ${type}...`) + const eventSource = new EventSource(url); + + const sseStream: Subject = new Subject(); + this.sseStreams.set(url, sseStream) + + + eventSource.onmessage = (event) => { + let newData + + try { + newData = JSON.parse(JSON.parse(event.data)) + } catch (e) { + newData = JSON.parse(event.data) as SSELog + } + + const { level } = newData + + if(level !== LogLevels.INFO) { + this.logRepository.create({type, level, data: JSON.stringify(newData), isHidden: false}, {ignoreDuplicates: true}) + console.log(newData, type,'------------------------------------------ log --------------------------------------') + } + + sseStream.next(event.data); + }; + } + + public getSseStream(req: Request, res: Response, url: string) { + const sseStream = this.sseStreams.get(url); + if (sseStream) { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no', + }); + res.flushHeaders(); + + sseStream.subscribe(data => { + res.write(`data: ${data}\n\n`); + }); + + const heartbeatInterval = setInterval(() => { + res.write(': keep-alive\n\n'); + }, 10000); + + req.on('close', () => { + clearInterval(heartbeatInterval); + res.end(); + }); + } else { + console.error('SSE stream not found for URL:', url); + res.status(404).end(); + } + } + + async readLogMetrics(type?: LogType) { + let warnOptions = { where: { level: LogLevels.WARN } } as any + let errorOptions = { where: { level: LogLevels.ERRO } } as any + let critOptions = { where: { level: LogLevels.CRIT } } as any + + if(type) { + warnOptions.where.type = type + errorOptions.where.type = type + critOptions.where.type = type + } + + const warningLogs = (await this.logRepository.findAll(warnOptions)).map(data => data.dataValues) + const errorLogs = (await this.logRepository.findAll(errorOptions)).map(data => data.dataValues) + const criticalLogs = (await this.logRepository.findAll(critOptions)).map(data => data.dataValues) + + return { + warningLogs, + errorLogs, + criticalLogs + } + } + + async dismissLog(id: string) { + return await this.logRepository.update({isHidden: true}, {where: {id}}) + } +} diff --git a/backend/src/logs/tests/logs.controller.spec.ts b/backend/src/logs/tests/logs.controller.spec.ts new file mode 100644 index 00000000..f45c2b47 --- /dev/null +++ b/backend/src/logs/tests/logs.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LogsController } from '../logs.controller'; + +describe('LogsController', () => { + let controller: LogsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [LogsController], + }).compile(); + + controller = module.get(LogsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/backend/src/logs/tests/logs.service.spec.ts b/backend/src/logs/tests/logs.service.spec.ts new file mode 100644 index 00000000..9287879b --- /dev/null +++ b/backend/src/logs/tests/logs.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LogsService } from '../logs.service'; + +describe('LogsService', () => { + let service: LogsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LogsService], + }).compile(); + + service = module.get(LogsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +});