diff --git a/src/app.module.ts b/src/app.module.ts index 3ad2139..326879f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import Configuration from './config/configuration'; import { VersioningModule } from './versioning/versioning.module'; import { PerformanceModule } from './performance/performance.module'; +import { LoggingModule } from './logging/logging.module'; @Module({ imports: [ @@ -24,6 +25,7 @@ import { PerformanceModule } from './performance/performance.module'; }), VersioningModule, PerformanceModule, + LoggingModule, ], }) export class AppModule {} diff --git a/src/logging/dto/logentry.dto.ts b/src/logging/dto/logentry.dto.ts new file mode 100644 index 0000000..c69a009 --- /dev/null +++ b/src/logging/dto/logentry.dto.ts @@ -0,0 +1,32 @@ +import { IsNotEmpty } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class LogEntryDto { + @IsNotEmpty() + @ApiProperty({ + description: 'The component which logged the message', + example: 'optimizer', + }) + component: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'The defined log level of this message', + example: 'DEBUG', + }) + level: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'The timestamp when the message was logged', + example: 'Wed, 14 Jun 2017 07:00:00 GMT', + }) + timestamp: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'The logged message', + example: 'This is a log message.', + }) + message: string; +} diff --git a/src/logging/logging.controller.spec.ts b/src/logging/logging.controller.spec.ts new file mode 100644 index 0000000..abfbb80 --- /dev/null +++ b/src/logging/logging.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LoggingController } from './logging.controller'; + +describe('LoggingController', () => { + let controller: LoggingController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [LoggingController], + }).compile(); + + controller = module.get(LoggingController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/logging/logging.controller.ts b/src/logging/logging.controller.ts new file mode 100644 index 0000000..527ccac --- /dev/null +++ b/src/logging/logging.controller.ts @@ -0,0 +1,110 @@ +import { + Controller, + Get, + Delete, + Query, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { ApiQuery, ApiResponse } from '@nestjs/swagger'; +import { LogEntryDto } from './dto/logentry.dto'; +import { LoggingService } from './logging.service'; +import { LogEntry } from './models/logentry.model'; + +@Controller('logging') +export class LoggingController { + constructor(private readonly loggingService: LoggingService) {} + + private static logEntryToDto(entry: LogEntry): LogEntryDto { + return { + component: entry.component, + level: entry.level, + timestamp: entry.timestamp, + message: entry.message, + }; + } + + @Get('/all') + @ApiResponse({ + status: 201, + description: 'All log entries', + type: [LogEntryDto], + }) + @ApiResponse({ + status: 404, + description: 'No messages found', + }) + async allMessages(): Promise { + return this.loggingService.allMessages().then((response) => { + if (response === null) { + throw new HttpException( + 'Unable to find the log messages', + HttpStatus.NOT_FOUND, + ); + } + + const messages: LogEntryDto[] = []; + response.forEach((entry) => + messages.push(LoggingController.logEntryToDto(entry)), + ); + return messages; + }); + } + + @Get('/component') + @ApiQuery({ + name: 'component', + description: 'The component filter for the messages', + type: String, + }) + @ApiResponse({ + status: 201, + description: 'All log entries', + type: [LogEntryDto], + }) + @ApiResponse({ + status: 404, + description: 'No messages found', + }) + async componentMessages( + @Query('component') component: string, + ): Promise { + return this.loggingService.componentMessages(component).then((response) => { + if (response === null) { + throw new HttpException( + 'Unable to find the log messages', + HttpStatus.NOT_FOUND, + ); + } + + const messages: LogEntryDto[] = []; + response.forEach((entry) => + messages.push(LoggingController.logEntryToDto(entry)), + ); + return messages; + }); + } + + @Delete('/deleteAll') + @ApiResponse({ + status: 200, + description: 'All log entries are deleted', + }) + async deleteAll(): Promise { + return this.loggingService.deleteAllMessages(); + } + + @Delete('/deleteComponent') + @ApiQuery({ + name: 'component', + description: 'The component filter for the messages', + type: String, + }) + @ApiResponse({ + status: 200, + description: 'All log entries are deleted', + }) + async deleteComponent(@Query('component') component: string): Promise { + return this.loggingService.deleteComponentMessages(component); + } +} diff --git a/src/logging/logging.module.ts b/src/logging/logging.module.ts new file mode 100644 index 0000000..e0cfcb6 --- /dev/null +++ b/src/logging/logging.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { LoggingService } from './logging.service'; +import { LoggingController } from './logging.controller'; +import { LogEntrySchema } from './models/logentry.model'; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: 'logging', schema: LogEntrySchema }]), + ], + providers: [LoggingService], + controllers: [LoggingController], + exports: [MongooseModule, LoggingService], +}) +export class LoggingModule {} diff --git a/src/logging/logging.service.spec.ts b/src/logging/logging.service.spec.ts new file mode 100644 index 0000000..35b8391 --- /dev/null +++ b/src/logging/logging.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LoggingService } from './logging.service'; + +describe('LoggingService', () => { + let service: LoggingService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LoggingService], + }).compile(); + + service = module.get(LoggingService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/logging/logging.service.ts b/src/logging/logging.service.ts new file mode 100644 index 0000000..d20337b --- /dev/null +++ b/src/logging/logging.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { LogEntry, LogEntryDocument } from './models/logentry.model'; + +@Injectable() +export class LoggingService { + constructor( + @InjectModel('logging') + private readonly logEntryModel: Model, + ) {} + + private async addLogMessage( + component: string, + level: string, + message: string, + ): Promise { + await this.logEntryModel.create({ + component, + level, + timestamp: new Date().toUTCString(), + message, + }); + } + + async addDebugMessage(component: string, message: string): Promise { + await this.addLogMessage(component, 'DEBUG', message); + } + + async addInfoMessage(component: string, message: string): Promise { + await this.addLogMessage(component, 'INFO', message); + } + + async addWarningMessage(component: string, message: string): Promise { + await this.addLogMessage(component, 'WARNING', message); + } + + async addErrorMessage(component: string, message: string): Promise { + await this.addLogMessage(component, 'ERROR', message); + } + + async addCriticalMessage(component: string, message: string): Promise { + await this.addLogMessage(component, 'CRITICAL', message); + } + + async deleteComponentMessages(component: string): Promise { + this.logEntryModel.deleteMany({ component }); + } + + async deleteAllMessages(): Promise { + this.logEntryModel.deleteMany({}); + } + + async componentMessages(component: string): Promise { + return this.logEntryModel.find({ component }); + } + + async allMessages(): Promise { + return this.logEntryModel.find({}); + } +} diff --git a/src/logging/models/logentry.model.ts b/src/logging/models/logentry.model.ts new file mode 100644 index 0000000..418c4e4 --- /dev/null +++ b/src/logging/models/logentry.model.ts @@ -0,0 +1,34 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +export type LogEntryDocument = LogEntry & Document; + +@Schema() +export class LogEntry { + @Prop({ + required: true, + type: String, + }) + component: string; + + @Prop({ + required: true, + enum: ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], + type: String, + }) + level: string; + + @Prop({ + required: true, + type: String, + }) + timestamp: string; + + @Prop({ + required: true, + type: String, + }) + message: string; +} + +export const LogEntrySchema = SchemaFactory.createForClass(LogEntry);