mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 15:17:41 +00:00
typeorm logger
This commit is contained in:
8
libs/common/src/context/request-context.ts
Normal file
8
libs/common/src/context/request-context.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { AsyncLocalStorage } from 'async_hooks';
|
||||||
|
|
||||||
|
export interface RequestContextStore {
|
||||||
|
requestId?: string;
|
||||||
|
userId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestContext = new AsyncLocalStorage<RequestContextStore>();
|
@ -43,73 +43,82 @@ import { SubspaceProductAllocationEntity } from '../modules/space/entities/subsp
|
|||||||
import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity';
|
import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity';
|
||||||
import { TagEntity } from '../modules/space/entities/tag.entity';
|
import { TagEntity } from '../modules/space/entities/tag.entity';
|
||||||
import { ClientEntity } from '../modules/client/entities';
|
import { ClientEntity } from '../modules/client/entities';
|
||||||
|
import { TypeOrmWinstonLogger } from '@app/common/logger/services/typeorm.logger';
|
||||||
|
import { createLogger } from 'winston';
|
||||||
|
import { winstonLoggerOptions } from '../logger/services/winston.logger';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: (configService: ConfigService) => ({
|
useFactory: (configService: ConfigService) => {
|
||||||
name: 'default',
|
const winstonLogger = createLogger(winstonLoggerOptions);
|
||||||
type: 'postgres',
|
const typeOrmLogger = new TypeOrmWinstonLogger(winstonLogger);
|
||||||
host: configService.get('DB_HOST'),
|
return {
|
||||||
port: configService.get('DB_PORT'),
|
name: 'default',
|
||||||
username: configService.get('DB_USER'),
|
type: 'postgres',
|
||||||
password: configService.get('DB_PASSWORD'),
|
host: configService.get('DB_HOST'),
|
||||||
database: configService.get('DB_NAME'),
|
port: configService.get('DB_PORT'),
|
||||||
entities: [
|
username: configService.get('DB_USER'),
|
||||||
NewTagEntity,
|
password: configService.get('DB_PASSWORD'),
|
||||||
ProjectEntity,
|
database: configService.get('DB_NAME'),
|
||||||
UserEntity,
|
entities: [
|
||||||
UserSessionEntity,
|
NewTagEntity,
|
||||||
UserOtpEntity,
|
ProjectEntity,
|
||||||
ProductEntity,
|
UserEntity,
|
||||||
DeviceUserPermissionEntity,
|
UserSessionEntity,
|
||||||
DeviceEntity,
|
UserOtpEntity,
|
||||||
PermissionTypeEntity,
|
ProductEntity,
|
||||||
CommunityEntity,
|
DeviceUserPermissionEntity,
|
||||||
SpaceEntity,
|
DeviceEntity,
|
||||||
SpaceLinkEntity,
|
PermissionTypeEntity,
|
||||||
SubspaceEntity,
|
CommunityEntity,
|
||||||
TagEntity,
|
SpaceEntity,
|
||||||
UserSpaceEntity,
|
SpaceLinkEntity,
|
||||||
DeviceUserPermissionEntity,
|
SubspaceEntity,
|
||||||
RoleTypeEntity,
|
TagEntity,
|
||||||
UserNotificationEntity,
|
UserSpaceEntity,
|
||||||
DeviceNotificationEntity,
|
DeviceUserPermissionEntity,
|
||||||
RegionEntity,
|
RoleTypeEntity,
|
||||||
TimeZoneEntity,
|
UserNotificationEntity,
|
||||||
VisitorPasswordEntity,
|
DeviceNotificationEntity,
|
||||||
DeviceStatusLogEntity,
|
RegionEntity,
|
||||||
SceneEntity,
|
TimeZoneEntity,
|
||||||
SceneIconEntity,
|
VisitorPasswordEntity,
|
||||||
SceneDeviceEntity,
|
DeviceStatusLogEntity,
|
||||||
SpaceModelEntity,
|
SceneEntity,
|
||||||
SubspaceModelEntity,
|
SceneIconEntity,
|
||||||
TagModel,
|
SceneDeviceEntity,
|
||||||
InviteUserEntity,
|
SpaceModelEntity,
|
||||||
InviteUserSpaceEntity,
|
SubspaceModelEntity,
|
||||||
InviteSpaceEntity,
|
TagModel,
|
||||||
AutomationEntity,
|
InviteUserEntity,
|
||||||
SpaceModelProductAllocationEntity,
|
InviteUserSpaceEntity,
|
||||||
SubspaceModelProductAllocationEntity,
|
InviteSpaceEntity,
|
||||||
SpaceProductAllocationEntity,
|
AutomationEntity,
|
||||||
SubspaceProductAllocationEntity,
|
SpaceModelProductAllocationEntity,
|
||||||
ClientEntity,
|
SubspaceModelProductAllocationEntity,
|
||||||
],
|
SpaceProductAllocationEntity,
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
SubspaceProductAllocationEntity,
|
||||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
ClientEntity,
|
||||||
logging: false,
|
],
|
||||||
extra: {
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
charset: 'utf8mb4',
|
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||||
max: 20, // set pool max size
|
logging: true,
|
||||||
idleTimeoutMillis: 5000, // close idle clients after 5 second
|
logger: typeOrmLogger,
|
||||||
connectionTimeoutMillis: 11_000, // return an error after 11 second if connection could not be established
|
extra: {
|
||||||
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)
|
charset: 'utf8mb4',
|
||||||
},
|
max: 20, // set pool max size
|
||||||
continuationLocalStorage: true,
|
idleTimeoutMillis: 5000, // close idle clients after 5 second
|
||||||
ssl: Boolean(JSON.parse(configService.get('DB_SSL'))),
|
connectionTimeoutMillis: 11_000, // return an error after 11 second if connection could not be established
|
||||||
}),
|
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)
|
||||||
|
},
|
||||||
|
continuationLocalStorage: true,
|
||||||
|
ssl: Boolean(JSON.parse(configService.get('DB_SSL'))),
|
||||||
|
};
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
providers: [TypeOrmWinstonLogger],
|
||||||
})
|
})
|
||||||
export class DatabaseModule {}
|
export class DatabaseModule {}
|
||||||
|
12
libs/common/src/logger/logger.module.ts
Normal file
12
libs/common/src/logger/logger.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// src/common/logger/logger.module.ts
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { WinstonModule } from 'nest-winston';
|
||||||
|
import { winstonLoggerOptions } from './services/winston.logger';
|
||||||
|
import { TypeOrmWinstonLogger } from './services/typeorm.logger';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [WinstonModule.forRoot(winstonLoggerOptions)],
|
||||||
|
providers: [TypeOrmWinstonLogger],
|
||||||
|
exports: [TypeOrmWinstonLogger],
|
||||||
|
})
|
||||||
|
export class LoggerModule {}
|
64
libs/common/src/logger/services/typeorm.logger.ts
Normal file
64
libs/common/src/logger/services/typeorm.logger.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Logger as WinstonLogger } from 'winston';
|
||||||
|
import { Logger as TypeOrmLogger } from 'typeorm';
|
||||||
|
import { requestContext } from '@app/common/context/request-context';
|
||||||
|
|
||||||
|
export class TypeOrmWinstonLogger implements TypeOrmLogger {
|
||||||
|
constructor(
|
||||||
|
private readonly logger: WinstonLogger,
|
||||||
|
private readonly slowQueryThreshold = 500, // ms
|
||||||
|
) {}
|
||||||
|
|
||||||
|
logQuery(query: string, parameters?: any[]) {
|
||||||
|
const context = requestContext.getStore();
|
||||||
|
const requestId = context?.requestId ?? 'N/A';
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
|
||||||
|
const isSlow = duration > this.slowQueryThreshold;
|
||||||
|
this.logger[isSlow ? 'warn' : 'debug'](`[DB][QUERY] ${query}`, {
|
||||||
|
requestId,
|
||||||
|
parameters,
|
||||||
|
duration: `${duration}ms`,
|
||||||
|
isSlow,
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Just ensures the setTimeout fires after this function returns
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
logQueryError(error: string | Error, query: string, parameters?: any[]) {
|
||||||
|
const requestId = requestContext.getStore()?.requestId ?? 'N/A';
|
||||||
|
this.logger.error(`[DB][ERROR] ${query}`, {
|
||||||
|
requestId,
|
||||||
|
parameters,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logQuerySlow(time: number, query: string, parameters?: any[]) {
|
||||||
|
const requestId = requestContext.getStore()?.requestId ?? 'N/A';
|
||||||
|
this.logger.warn(`🔥 [DB][SLOW > ${time}ms] ${query}`, {
|
||||||
|
requestId,
|
||||||
|
parameters,
|
||||||
|
time,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logSchemaBuild(message: string) {
|
||||||
|
this.logger.info(`[DB][SCHEMA] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logMigration(message: string) {
|
||||||
|
this.logger.info(`[DB][MIGRATION] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(level: 'log' | 'info' | 'warn', message: any) {
|
||||||
|
this.logger.log({
|
||||||
|
level,
|
||||||
|
message: `[DB] ${message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { utilities as nestWinstonModuleUtilities } from 'nest-winston';
|
|||||||
import * as winston from 'winston';
|
import * as winston from 'winston';
|
||||||
|
|
||||||
export const winstonLoggerOptions: winston.LoggerOptions = {
|
export const winstonLoggerOptions: winston.LoggerOptions = {
|
||||||
|
level: 'debug',
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
14
libs/common/src/middleware/request-context.middleware.ts
Normal file
14
libs/common/src/middleware/request-context.middleware.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||||
|
import { requestContext } from '../context/request-context';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestContextMiddleware implements NestMiddleware {
|
||||||
|
use(req: any, res: any, next: () => void) {
|
||||||
|
const context = {
|
||||||
|
requestId: req.headers['x-request-id'] || uuidv4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
requestContext.run(context, () => next());
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,7 @@ import { ClientModule } from './client/client.module';
|
|||||||
import { DeviceCommissionModule } from './commission-device/commission-device.module';
|
import { DeviceCommissionModule } from './commission-device/commission-device.module';
|
||||||
import { PowerClampModule } from './power-clamp/power-clamp.module';
|
import { PowerClampModule } from './power-clamp/power-clamp.module';
|
||||||
import { WinstonModule } from 'nest-winston';
|
import { WinstonModule } from 'nest-winston';
|
||||||
import { winstonLoggerOptions } from './common/filters/http-exception/logger/winston.logger';
|
import { winstonLoggerOptions } from '../libs/common/src/logger/services/winston.logger';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
|
13
src/main.ts
13
src/main.ts
@ -7,6 +7,9 @@ import { ValidationPipe } from '@nestjs/common';
|
|||||||
import { json, urlencoded } from 'body-parser';
|
import { json, urlencoded } from 'body-parser';
|
||||||
import { SeederService } from '@app/common/seed/services/seeder.service';
|
import { SeederService } from '@app/common/seed/services/seeder.service';
|
||||||
import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter';
|
import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
|
||||||
|
import { RequestContextMiddleware } from '@app/common/middleware/request-context.middleware';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
@ -18,6 +21,8 @@ async function bootstrap() {
|
|||||||
app.use(urlencoded({ limit: '1mb', extended: true }));
|
app.use(urlencoded({ limit: '1mb', extended: true }));
|
||||||
app.useGlobalFilters(new HttpExceptionFilter());
|
app.useGlobalFilters(new HttpExceptionFilter());
|
||||||
|
|
||||||
|
app.use(new RequestContextMiddleware().use);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 5 * 60 * 1000,
|
windowMs: 5 * 60 * 1000,
|
||||||
@ -43,14 +48,16 @@ async function bootstrap() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const seederService = app.get(SeederService);
|
const seederService = app.get(SeederService);
|
||||||
|
const logger = app.get<Logger>(WINSTON_MODULE_NEST_PROVIDER);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await seederService.seed();
|
await seederService.seed();
|
||||||
console.log('Seeding complete!');
|
logger.log('Seeding complete!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Seeding failed!', error);
|
logger.error('Seeding failed!', error.stack || error);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Starting auth at port ...', process.env.PORT || 4000);
|
logger.log('Starting auth at port ...', process.env.PORT || 4000);
|
||||||
await app.listen(process.env.PORT || 4000);
|
await app.listen(process.env.PORT || 4000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
Reference in New Issue
Block a user