Added custom logger that logs to db
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@
|
|||||||
/build
|
/build
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
# logs # This should be allowed because of the entity called `logs`
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { dataSourceOptions } from './appDataSource';
|
|||||||
import { EntryModule } from './entries/entries.module';
|
import { EntryModule } from './entries/entries.module';
|
||||||
import { DataSource, EntityManager } from 'typeorm';
|
import { DataSource, EntityManager } from 'typeorm';
|
||||||
import { UsersModule } from './users/users.module';
|
import { UsersModule } from './users/users.module';
|
||||||
|
import { LogsModule } from './logs/logs.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -15,6 +16,7 @@ import { UsersModule } from './users/users.module';
|
|||||||
TypeOrmModule.forRoot(dataSourceOptions),
|
TypeOrmModule.forRoot(dataSourceOptions),
|
||||||
EntryModule,
|
EntryModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
|
LogsModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Initial1770020781006 } from './migrations/1770020781006-initial';
|
|||||||
import { AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 } from './migrations/1770067407619-add_create_update_delete_cols_and_rename_source_to_sourceText';
|
import { AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 } from './migrations/1770067407619-add_create_update_delete_cols_and_rename_source_to_sourceText';
|
||||||
import { User } from './users/users.entity';
|
import { User } from './users/users.entity';
|
||||||
import { AddUserEntity1770147074119 } from './migrations/1770147074119-add_user_entity';
|
import { AddUserEntity1770147074119 } from './migrations/1770147074119-add_user_entity';
|
||||||
|
import { Log } from './logs/logs.entity';
|
||||||
|
import { AddLogEntity1770232943778 } from './migrations/1770232943778-add_log_entity';
|
||||||
|
|
||||||
export const dataSourceOptions: DataSourceOptions = {
|
export const dataSourceOptions: DataSourceOptions = {
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
@@ -12,11 +14,12 @@ export const dataSourceOptions: DataSourceOptions = {
|
|||||||
username: process.env.DB_USERNAME,
|
username: process.env.DB_USERNAME,
|
||||||
password: process.env.DB_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
database: process.env.DB_NAME,
|
database: process.env.DB_NAME,
|
||||||
entities: [Entry, User],
|
entities: [Entry, User, Log],
|
||||||
migrations: [
|
migrations: [
|
||||||
Initial1770020781006,
|
Initial1770020781006,
|
||||||
AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619,
|
AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619,
|
||||||
AddUserEntity1770147074119
|
AddUserEntity1770147074119,
|
||||||
|
AddLogEntity1770232943778,
|
||||||
],
|
],
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { Entry } from './entries.entity';
|
import { Entry } from './entries.entity';
|
||||||
import { EntryService } from './entries.service';
|
import { EntryService } from './entries.service';
|
||||||
import { EntriesController } from './entries.controller';
|
import { EntriesController } from './entries.controller';
|
||||||
|
import { LogsModule } from 'src/logs/logs.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Entry])],
|
imports: [TypeOrmModule.forFeature([Entry]), LogsModule],
|
||||||
exports: [EntryService],
|
exports: [EntryService],
|
||||||
providers: [EntryService],
|
providers: [EntryService],
|
||||||
controllers: [EntriesController],
|
controllers: [EntriesController],
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Entry } from './entries.entity';
|
import { Entry } from './entries.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { EntryDTO } from './entries.dto';
|
import { EntryDTO } from './entries.dto';
|
||||||
import EntryNotFoundException from './exceptions/entryNotFound.exception';
|
import EntryNotFoundException from './exceptions/entryNotFound.exception';
|
||||||
|
import { LogsService } from 'src/logs/logs.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EntryService {
|
export class EntryService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Entry)
|
@InjectRepository(Entry)
|
||||||
private entryRepository: Repository<Entry>,
|
private entryRepository: Repository<Entry>,
|
||||||
|
private readonly logger: LogsService,
|
||||||
) {}
|
) {}
|
||||||
private readonly logger = new Logger(EntryService.name);
|
|
||||||
|
|
||||||
async save(data: EntryDTO): Promise<Entry> {
|
async save(data: EntryDTO): Promise<Entry> {
|
||||||
this.logger.log('Creating entry');
|
await this.logger.log('Creating entry', 'EntryService');
|
||||||
const lEntry = new Entry();
|
const lEntry = new Entry();
|
||||||
lEntry.description = data.description;
|
lEntry.description = data.description;
|
||||||
lEntry.sourceText = data.sourceText;
|
lEntry.sourceText = data.sourceText;
|
||||||
@@ -25,7 +26,7 @@ export class EntryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateByUuid(uuid: string, data: EntryDTO): Promise<Entry> {
|
async updateByUuid(uuid: string, data: EntryDTO): Promise<Entry> {
|
||||||
this.logger.log(`Updating entry with UUID ${uuid}`);
|
await this.logger.log(`Updating entry with UUID ${uuid}`, 'EntryService');
|
||||||
const oldEntry = await this.entryRepository.findOneBy({ uuid: uuid });
|
const oldEntry = await this.entryRepository.findOneBy({ uuid: uuid });
|
||||||
if (!oldEntry) {
|
if (!oldEntry) {
|
||||||
throw new EntryNotFoundException(uuid);
|
throw new EntryNotFoundException(uuid);
|
||||||
@@ -44,38 +45,56 @@ export class EntryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUuid(uuid: string): Promise<Entry | null> {
|
async findOneByUuid(uuid: string): Promise<Entry | null> {
|
||||||
this.logger.log(`Returning entry with UUID ${uuid}`);
|
await this.logger.log(`Returning entry with UUID ${uuid}`, 'EntryService');
|
||||||
return this.entryRepository.findOneByOrFail({ uuid: uuid });
|
return this.entryRepository.findOneByOrFail({ uuid: uuid });
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(): Promise<Entry[]> {
|
async findAll(): Promise<Entry[]> {
|
||||||
this.logger.log('Returning all entries');
|
await this.logger.log('Returning all entries', 'EntryService');
|
||||||
return this.entryRepository.find();
|
return this.entryRepository.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllAndDeleted(): Promise<Entry[]> {
|
async findAllAndDeleted(): Promise<Entry[]> {
|
||||||
this.logger.log('Returning all entries, active and deleted');
|
await this.logger.log(
|
||||||
|
'Returning all entries, active and deleted',
|
||||||
|
'EntryService',
|
||||||
|
);
|
||||||
return this.entryRepository.find({ withDeleted: true });
|
return this.entryRepository.find({ withDeleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDeleteByUuid(uuid: string): Promise<void> {
|
async softDeleteByUuid(uuid: string): Promise<void> {
|
||||||
this.logger.log(`Soft deleting entry with UUID ${uuid}`);
|
await this.logger.log(
|
||||||
|
`Soft deleting entry with UUID ${uuid}`,
|
||||||
|
'EntryService',
|
||||||
|
);
|
||||||
const deleteResponse = await this.entryRepository.softDelete({
|
const deleteResponse = await this.entryRepository.softDelete({
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
});
|
});
|
||||||
if (!deleteResponse.affected) {
|
if (!deleteResponse.affected) {
|
||||||
throw new EntryNotFoundException(uuid);
|
const exception = new EntryNotFoundException(uuid);
|
||||||
|
await this.logger.error(
|
||||||
|
exception.message,
|
||||||
|
exception.stack,
|
||||||
|
'EntryService',
|
||||||
|
);
|
||||||
|
throw exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreDeletedByUuid(uuid: string): Promise<Entry | null> {
|
async restoreDeletedByUuid(uuid: string): Promise<Entry | null> {
|
||||||
this.logger.log(`Restoring entry with UUID ${uuid}`);
|
await this.logger.log(`Restoring entry with UUID ${uuid}`, 'EntryService');
|
||||||
const restoreResponse = await this.entryRepository.restore({
|
const restoreResponse = await this.entryRepository.restore({
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!restoreResponse.affected) {
|
if (!restoreResponse.affected) {
|
||||||
throw new EntryNotFoundException(uuid);
|
const exception = new EntryNotFoundException(uuid);
|
||||||
|
await this.logger.error(
|
||||||
|
exception.message,
|
||||||
|
exception.stack,
|
||||||
|
'EntryService',
|
||||||
|
);
|
||||||
|
throw exception;
|
||||||
} else {
|
} else {
|
||||||
return this.entryRepository.findOneByOrFail({
|
return this.entryRepository.findOneByOrFail({
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
|
|||||||
24
src/logs/logs.entity.ts
Normal file
24
src/logs/logs.entity.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ name: 'logs' })
|
||||||
|
export class Log {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
level: string; // 'log', 'error', 'warn', etc.
|
||||||
|
|
||||||
|
@Column({ type: String })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@Column({ type: String, nullable: true })
|
||||||
|
context?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
12
src/logs/logs.module.ts
Normal file
12
src/logs/logs.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// src/logs/logs.module.ts
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Log } from './logs.entity';
|
||||||
|
import { LogsService } from './logs.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Log])],
|
||||||
|
exports: [LogsService],
|
||||||
|
providers: [LogsService],
|
||||||
|
})
|
||||||
|
export class LogsModule {}
|
||||||
46
src/logs/logs.service.ts
Normal file
46
src/logs/logs.service.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable, Logger, LoggerService } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Log } from './logs.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogsService extends Logger implements LoggerService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Log)
|
||||||
|
private readonly logRepository: Repository<Log>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async log(message: string, context?: string) {
|
||||||
|
// output to console using default Logger
|
||||||
|
super.log(message, context);
|
||||||
|
// Save in DB using TypeORM
|
||||||
|
await this.saveLog('log', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
async error(message: string, stack?: string, context?: string) {
|
||||||
|
super.error(message, stack, context);
|
||||||
|
await this.saveLog('error', `${message} - ${stack || ''}`, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
async warn(message: string, context?: string) {
|
||||||
|
super.warn(message, context);
|
||||||
|
await this.saveLog('warn', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
async debug(message: string, context?: string) {
|
||||||
|
super.debug(message, context);
|
||||||
|
await this.saveLog('debug', message, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveLog(level: string, message: string, context?: string) {
|
||||||
|
try {
|
||||||
|
const log = this.logRepository.create({ level, message, context });
|
||||||
|
await this.logRepository.save(log);
|
||||||
|
} catch (err) {
|
||||||
|
// maybe have a backup log (local file?) to catch these
|
||||||
|
console.error('Failed to save log to database:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/migrations/1770232943778-add_log_entity.ts
Normal file
15
src/migrations/1770232943778-add_log_entity.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddLogEntity1770232943778 implements MigrationInterface {
|
||||||
|
name = 'AddLogEntity1770232943778';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "logs" ("id" SERIAL NOT NULL, "level" character varying NOT NULL, "message" character varying NOT NULL, "context" character varying, "timestamp" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_fb1b805f2f7795de79fa69340ba" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE "logs"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user