Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add error log module to save all errors in table. add launch.js… #1445

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Nest Debug",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"start:debug",
"--",
"--inspect-brk"
],
"console": "integratedTerminal",
"restart": true,
"protocol": "auto",
"port": 9229,
"autoAttachChildProcesses": true,
"justMyCode": true
},
]
}
10 changes: 10 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { MailerModule } from './mailer/mailer.module';
import { MongooseModule } from '@nestjs/mongoose';
import { MongooseConfigService } from './database/mongoose-config.service';
import { DatabaseConfig } from './database/config/database-config.type';
import { LogsFilter } from './error-logs/logs.filter';
import { APP_FILTER } from '@nestjs/core';
import { LogsModule } from './error-logs/logs.module';

@Module({
imports: [
Expand Down Expand Up @@ -91,7 +94,14 @@ import { DatabaseConfig } from './database/config/database-config.type';
SessionModule,
MailModule,
MailerModule,
LogsModule,
HomeModule,
],
providers: [
{
provide: APP_FILTER,
useClass: LogsFilter,
},
],
})
export class AppModule {}
15 changes: 15 additions & 0 deletions src/error-logs/domain/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Allow } from 'class-validator';

export class Logs {
@Allow()
id: string;

@Allow()
path: string;

message: JSON;
stack: JSON;
method: string;
payload?: JSON | null;
status: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { LogsSchema, LogsSchemaClass } from './entities/logs.schema';
import { LogsRepository } from '../logs.repository';
import { LogsDocumentRepository } from './repositories/logs.repository';

@Module({
imports: [
MongooseModule.forFeature([
{ name: LogsSchemaClass.name, schema: LogsSchema },
]),
],
providers: [
{
provide: LogsRepository,
useClass: LogsDocumentRepository,
},
],
exports: [LogsRepository],
})
export class DocumentLogsPersistenceModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, SchemaTypes, now } from 'mongoose';
import { EntityDocumentHelper } from 'src/utils/document-entity-helper';
import { Logs } from '@/error-logs/domain/logs';

export type LogsSchemaDocument = HydratedDocument<LogsSchemaClass>;

@Schema({
timestamps: true,
toJSON: {
virtuals: true,
getters: true,
},
})
export class LogsSchemaClass extends EntityDocumentHelper implements Logs {
@Prop({
type: SchemaTypes.String,
unique: true,
})
id: string;

@Prop()
path: string;

@Prop({ type: Object })
message: JSON;

@Prop({ type: Object })
stack: JSON;

@Prop()
method: string;

@Prop({ type: Object })
payload?: JSON | null;

@Prop()
status: number;

@Prop({ default: now })
timestamp: Date;
}

export const LogsSchema = SchemaFactory.createForClass(LogsSchemaClass);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Logs } from "@/error-logs/domain/logs";
import { LogsSchemaClass } from '../entities/logs.schema';

export class LogsMapper {
static toDomain(raw: LogsSchemaClass): Logs {
const logs = new Logs();
logs.id = raw.id;
logs.message = raw.message;
logs.method = raw.method;
logs.path = raw.path;
logs.payload = raw.payload;
logs.stack = raw.stack;
logs.status = raw.status;

return logs;
}

static toPersistence(logs: Logs): LogsSchemaClass {
const logsEntity = new LogsSchemaClass();

logsEntity.message = logs.message;
logsEntity.method = logs.method;
logsEntity.path = logs.path;
logsEntity.stack = logs.stack;
logsEntity.payload = logs.payload;
logsEntity.status = logs.status;

return logsEntity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable } from '@nestjs/common';
import { LogsRepository } from '../../logs.repository';
import { LogsSchemaClass } from '../entities/logs.schema'; // Assuming this is where your entity class is defined
import { Logs } from '@/error-logs/domain/logs';
import { LogsMapper } from '../mappers/logs.mapper';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

@Injectable()
export class LogsDocumentRepository implements LogsRepository {
constructor(
@InjectModel(LogsSchemaClass.name)
private readonly logsModel: Model<LogsSchemaClass>,
) {}

async create(data: Logs): Promise<Logs> {
const persistenceModel = LogsMapper.toPersistence(data);
const createdLog = new this.logsModel(persistenceModel);
const logObject = await createdLog.save();
return LogsMapper.toDomain(logObject);
}
}
7 changes: 7 additions & 0 deletions src/error-logs/infrastructure/persistence/logs.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Logs } from '@/error-logs/domain/logs';

export abstract class LogsRepository {
abstract create(
data: Omit<Logs, 'id' | 'createdAt' | 'deletedAt' | 'updatedAt'>,
): Promise<Logs>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Column, CreateDateColumn, Entity, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
import { EntityRelationalHelper } from 'src/utils/relational-entity-helper';
import { Logs } from '@/error-logs/domain/logs';

@Entity({
name: 'logs',
})
export class LogsEntity extends EntityRelationalHelper implements Logs {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column({ type: 'text' })
path: string;

@Column({ type: 'json' })
message: JSON;

@Column({ type: 'json' })
stack: JSON;

@Column({ type: 'text' })
method: string;

@Column({ type: 'json', nullable: true })
payload?: JSON | null;

@Column({ type: 'integer' })
status: number;

@CreateDateColumn()
timestamp: Date;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Logs } from "@/error-logs/domain/logs";
import { LogsEntity } from "../entities/logs.entity";

export class LogsMapper {
static toDomain(raw: LogsEntity): Logs {
const logs = new Logs();
logs.id = raw.id;
logs.message = raw.message;
logs.method = raw.method;
logs.path = raw.path;
logs.payload = raw.payload;
logs.stack = raw.stack;
logs.status = raw.status;

return logs;
}

static toPersistence(logs: Logs): LogsEntity {
const logsEntity = new LogsEntity();

logsEntity.message = logs.message;
logsEntity.method = logs.method;
logsEntity.path = logs.path;
logsEntity.stack = logs.stack;
logsEntity.payload = logs.payload;
logsEntity.status = logs.status;

return logsEntity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LogsEntity } from './entities/logs.entity';
import { LogsRepository } from '../logs.repository';
import { LogsRelationalRepository } from './repositories/logs.repository';

@Module({
imports: [TypeOrmModule.forFeature([LogsEntity])],
providers: [
{
provide: LogsRepository,
useClass: LogsRelationalRepository,
},
],
exports: [LogsRepository],
})
export class RelationalLogsPersistenceModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { LogsRepository } from '../../logs.repository';
import { LogsEntity } from '../entities/logs.entity';
import { Logs } from '@/error-logs/domain/logs';
import { LogsMapper } from '../mappers/logs.mapper';


@Injectable()
export class LogsRelationalRepository implements LogsRepository {
constructor(
@InjectRepository(LogsEntity)
private readonly logsRepository: Repository<LogsEntity>,
) {}

async create(data: Logs): Promise<Logs> {
const persistenceModel = LogsMapper.toPersistence(data);
const newEntity = await this.logsRepository.save(
this.logsRepository.create(persistenceModel),
);
return LogsMapper.toDomain(newEntity);
}
}
39 changes: 39 additions & 0 deletions src/error-logs/logs.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { HttpException } from '@nestjs/common/exceptions';
import { LogService } from './logs.service';

@Catch()
export class LogsFilter implements ExceptionFilter {
constructor(private readonly errorLoggerService: LogService) {}

async catch(exception: any, host: ArgumentsHost) {
if (exception) {
// Exception occurred, log it
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const message = (exception instanceof HttpException ? exception.getResponse() : 'Internal Server Error') as JSON;
const stack = exception.stack;
const path = request.url;
const requestBody = request.body;
const method = request.method;

// Send the response based on the original exception status
const status = exception instanceof HttpException ? exception.getStatus() : 500;

this.errorLoggerService.logError(path, message, stack, method, status, requestBody);

if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: 'Internal Server Error',
});
} else {
// For non-500 status codes, maintain the original response status
response.status(status).json(exception.getResponse());
}
}
}
}
20 changes: 20 additions & 0 deletions src/error-logs/logs.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import databaseConfig from '@/database/config/database.config';
import { DatabaseConfig } from '@/database/config/database-config.type';
import { LogService } from './logs.service';
import { RelationalLogsPersistenceModule } from './infrastructure/persistence/relational/relational-persistence.module';
import { DocumentLogsPersistenceModule } from './infrastructure/persistence/document/document-persistence.module';


const infrastructurePersistenceModule = (databaseConfig() as DatabaseConfig)
.isDocumentDatabase
? DocumentLogsPersistenceModule
: RelationalLogsPersistenceModule;

@Module({
imports: [infrastructurePersistenceModule],
providers: [LogService],
exports: [LogService, infrastructurePersistenceModule],
})

export class LogsModule {}
22 changes: 22 additions & 0 deletions src/error-logs/logs.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// error-logger.service.ts
import { Injectable } from '@nestjs/common';
import { Logs } from './domain/logs';
import { LogsRepository } from './infrastructure/persistence/logs.repository';

@Injectable()
export class LogService {
constructor(
private readonly logsRepository: LogsRepository,
) {}

async logError(path: string, message: JSON, stack: JSON, method: string, status: number, payload?: JSON): Promise<Logs> {
return await this.logsRepository.create({
path,
message,
stack,
method,
status,
payload
})
}
}
5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"esModuleInterop": true
"esModuleInterop": true,
"paths": {
"@/*": ["src/*"],
}
}
}