mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-10-23 10:47:54 +00:00
typeorm logger
This commit is contained in:
@@ -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 { TagEntity } from '../modules/space/entities/tag.entity';
|
||||
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({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
name: 'default',
|
||||
type: 'postgres',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: configService.get('DB_PORT'),
|
||||
username: configService.get('DB_USER'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_NAME'),
|
||||
entities: [
|
||||
NewTagEntity,
|
||||
ProjectEntity,
|
||||
UserEntity,
|
||||
UserSessionEntity,
|
||||
UserOtpEntity,
|
||||
ProductEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
DeviceEntity,
|
||||
PermissionTypeEntity,
|
||||
CommunityEntity,
|
||||
SpaceEntity,
|
||||
SpaceLinkEntity,
|
||||
SubspaceEntity,
|
||||
TagEntity,
|
||||
UserSpaceEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
RoleTypeEntity,
|
||||
UserNotificationEntity,
|
||||
DeviceNotificationEntity,
|
||||
RegionEntity,
|
||||
TimeZoneEntity,
|
||||
VisitorPasswordEntity,
|
||||
DeviceStatusLogEntity,
|
||||
SceneEntity,
|
||||
SceneIconEntity,
|
||||
SceneDeviceEntity,
|
||||
SpaceModelEntity,
|
||||
SubspaceModelEntity,
|
||||
TagModel,
|
||||
InviteUserEntity,
|
||||
InviteUserSpaceEntity,
|
||||
InviteSpaceEntity,
|
||||
AutomationEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
SpaceProductAllocationEntity,
|
||||
SubspaceProductAllocationEntity,
|
||||
ClientEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
logging: false,
|
||||
extra: {
|
||||
charset: 'utf8mb4',
|
||||
max: 20, // set pool max size
|
||||
idleTimeoutMillis: 5000, // close idle clients after 5 second
|
||||
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'))),
|
||||
}),
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const winstonLogger = createLogger(winstonLoggerOptions);
|
||||
const typeOrmLogger = new TypeOrmWinstonLogger(winstonLogger);
|
||||
return {
|
||||
name: 'default',
|
||||
type: 'postgres',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: configService.get('DB_PORT'),
|
||||
username: configService.get('DB_USER'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_NAME'),
|
||||
entities: [
|
||||
NewTagEntity,
|
||||
ProjectEntity,
|
||||
UserEntity,
|
||||
UserSessionEntity,
|
||||
UserOtpEntity,
|
||||
ProductEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
DeviceEntity,
|
||||
PermissionTypeEntity,
|
||||
CommunityEntity,
|
||||
SpaceEntity,
|
||||
SpaceLinkEntity,
|
||||
SubspaceEntity,
|
||||
TagEntity,
|
||||
UserSpaceEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
RoleTypeEntity,
|
||||
UserNotificationEntity,
|
||||
DeviceNotificationEntity,
|
||||
RegionEntity,
|
||||
TimeZoneEntity,
|
||||
VisitorPasswordEntity,
|
||||
DeviceStatusLogEntity,
|
||||
SceneEntity,
|
||||
SceneIconEntity,
|
||||
SceneDeviceEntity,
|
||||
SpaceModelEntity,
|
||||
SubspaceModelEntity,
|
||||
TagModel,
|
||||
InviteUserEntity,
|
||||
InviteUserSpaceEntity,
|
||||
InviteSpaceEntity,
|
||||
AutomationEntity,
|
||||
SpaceModelProductAllocationEntity,
|
||||
SubspaceModelProductAllocationEntity,
|
||||
SpaceProductAllocationEntity,
|
||||
SubspaceProductAllocationEntity,
|
||||
ClientEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
logging: true,
|
||||
logger: typeOrmLogger,
|
||||
extra: {
|
||||
charset: 'utf8mb4',
|
||||
max: 20, // set pool max size
|
||||
idleTimeoutMillis: 5000, // close idle clients after 5 second
|
||||
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 {}
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { utilities as nestWinstonModuleUtilities } from 'nest-winston';
|
||||
import * as winston from 'winston';
|
||||
|
||||
export const winstonLoggerOptions: winston.LoggerOptions = {
|
||||
level: 'debug',
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
nestWinstonModuleUtilities.format.nestLike('MyApp', {
|
||||
prettyPrint: true,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'logs/error.log',
|
||||
level: 'error',
|
||||
format: winston.format.json(),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'logs/combined.log',
|
||||
format: winston.format.json(),
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user