ClsService for async request messages & logging
This commit is contained in:
parent
860d30b16f
commit
974a6503ef
|
|
@ -36,6 +36,7 @@
|
||||||
"@nestjs/serve-static": "^4.0.1",
|
"@nestjs/serve-static": "^4.0.1",
|
||||||
"@types/stripe": "^8.0.417",
|
"@types/stripe": "^8.0.417",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
|
"cls-hooked": "^4.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv-flow": "^4.1.0",
|
"dotenv-flow": "^4.1.0",
|
||||||
|
|
@ -48,6 +49,7 @@
|
||||||
"jwks-rsa": "^3.1.0",
|
"jwks-rsa": "^3.1.0",
|
||||||
"ky": "^1.4.0",
|
"ky": "^1.4.0",
|
||||||
"nest-winston": "^1.9.4",
|
"nest-winston": "^1.9.4",
|
||||||
|
"nestjs-cls": "^4.4.1",
|
||||||
"nodemailer": "^6.9.10",
|
"nodemailer": "^6.9.10",
|
||||||
"nodemailer-smtp-transport": "^2.7.4",
|
"nodemailer-smtp-transport": "^2.7.4",
|
||||||
"openai": "^4.52.6",
|
"openai": "^4.52.6",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston';
|
import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston';
|
||||||
|
|
@ -19,7 +19,7 @@ import { EventModule } from './event/event.module';
|
||||||
import { JwtStrategy } from './jwt.strategy';
|
import { JwtStrategy } from './jwt.strategy';
|
||||||
import { MailModule } from './mail/mail.module';
|
import { MailModule } from './mail/mail.module';
|
||||||
|
|
||||||
import { ContextService } from './context/context.service';
|
import { ClsMiddleware, ClsModule } from 'nestjs-cls';
|
||||||
import { PaymentModule } from './payment/payment.module';
|
import { PaymentModule } from './payment/payment.module';
|
||||||
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
|
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
|
||||||
import { SelectOptionsModule } from './select-options/select-options.module';
|
import { SelectOptionsModule } from './select-options/select-options.module';
|
||||||
|
|
@ -31,6 +31,13 @@ console.log('Loaded environment variables:');
|
||||||
console.log(JSON.stringify(process.env, null, 2));
|
console.log(JSON.stringify(process.env, null, 2));
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
ClsModule.forRoot({
|
||||||
|
global: true, // Macht den ClsService global verfügbar
|
||||||
|
middleware: { mount: true }, // Registriert automatisch die ClsMiddleware
|
||||||
|
// setup: clsService => {
|
||||||
|
// // Optional: zusätzliche Setup-Logik
|
||||||
|
// },
|
||||||
|
}),
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
MailModule,
|
MailModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
|
@ -64,10 +71,11 @@ console.log(JSON.stringify(process.env, null, 2));
|
||||||
EventModule,
|
EventModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, LogController],
|
controllers: [AppController, LogController],
|
||||||
providers: [AppService, FileService, JwtStrategy, ContextService, ContextService],
|
providers: [AppService, FileService, JwtStrategy],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule implements NestModule {
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer.apply(ClsMiddleware).forRoutes('*');
|
||||||
consumer.apply(RequestDurationMiddleware).forRoutes('*');
|
consumer.apply(RequestDurationMiddleware).forRoutes('*');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
import { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { RealIpInfo } from './models/main.model';
|
|
||||||
import { getRealIpInfo } from './utils/ip.util';
|
|
||||||
// export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
|
|
||||||
// const request = ctx.switchToHttp().getRequest();
|
|
||||||
// const ip = request.headers['cf-connecting-ip'] || request.headers['x-real-ip'] || request.headers['x-forwarded-for']?.split(',')[0] || request.connection.remoteAddress;
|
|
||||||
// const countryCode = request.headers['cf-ipcountry'];
|
|
||||||
// return { ip, countryCode };
|
|
||||||
// });
|
|
||||||
export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
|
|
||||||
const request = ctx.switchToHttp().getRequest();
|
|
||||||
return getRealIpInfo(request);
|
|
||||||
});
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// src/context/context.service.ts
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { AsyncLocalStorage } from 'async_hooks';
|
|
||||||
|
|
||||||
export interface RequestContext {
|
|
||||||
ip: string;
|
|
||||||
countryCode?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ContextService {
|
|
||||||
private readonly asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
|
|
||||||
|
|
||||||
run(context: RequestContext, callback: () => void) {
|
|
||||||
this.asyncLocalStorage.run(context, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
getContext(): RequestContext | undefined {
|
|
||||||
return this.asyncLocalStorage.getStore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// src/decorators/real-ip.decorator.ts
|
||||||
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { getRealIpInfo, RealIpInfo } from '../utils/ip.util';
|
||||||
|
|
||||||
|
export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
|
||||||
|
const request = ctx.switchToHttp().getRequest();
|
||||||
|
return getRealIpInfo(request);
|
||||||
|
});
|
||||||
|
|
@ -2,30 +2,29 @@ import { Module } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
import pkg from 'pg';
|
import pkg from 'pg';
|
||||||
import { ContextService, RequestContext } from 'src/context/context.service';
|
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
import { PG_CONNECTION } from './schema';
|
import { PG_CONNECTION } from './schema';
|
||||||
const { Pool } = pkg;
|
const { Pool } = pkg;
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
ContextService,
|
|
||||||
{
|
{
|
||||||
provide: PG_CONNECTION,
|
provide: PG_CONNECTION,
|
||||||
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService],
|
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ClsService],
|
||||||
useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => {
|
useFactory: async (configService: ConfigService, logger: Logger, cls: ClsService) => {
|
||||||
const connectionString = configService.get<string>('DATABASE_URL');
|
const connectionString = configService.get<string>('DATABASE_URL');
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
connectionString,
|
connectionString,
|
||||||
// ssl: true,
|
// ssl: true, // Falls benötigt
|
||||||
});
|
});
|
||||||
|
|
||||||
// Definiere einen benutzerdefinierten Logger für Drizzle
|
// Definiere einen benutzerdefinierten Logger für Drizzle
|
||||||
const drizzleLogger = {
|
const drizzleLogger = {
|
||||||
logQuery(query: string, params: unknown[]): void {
|
logQuery(query: string, params: unknown[]): void {
|
||||||
const context: RequestContext | undefined = contextService.getContext();
|
const ip = cls.get('ip') || 'unknown';
|
||||||
const ip = context?.ip || 'unknown';
|
const countryCode = cls.get('countryCode') || 'unknown';
|
||||||
const countryCode = context?.countryCode || 'unknown';
|
|
||||||
logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`);
|
logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Body, Controller, Headers, Post } from '@nestjs/common';
|
import { Body, Controller, Headers, Post } from '@nestjs/common';
|
||||||
import { RealIp } from 'src/app.service';
|
import { RealIp } from 'src/decorators/real-ip.decorator';
|
||||||
import { ListingEvent } from 'src/models/db.model';
|
import { ListingEvent } from 'src/models/db.model';
|
||||||
import { RealIpInfo } from 'src/models/main.model';
|
import { RealIpInfo } from 'src/models/main.model';
|
||||||
import { EventService } from './event.service';
|
import { EventService } from './event.service';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||||
import { RealIp } from 'src/app.service';
|
import { RealIp } from 'src/decorators/real-ip.decorator';
|
||||||
import { RealIpInfo } from 'src/models/main.model';
|
import { RealIpInfo } from 'src/models/main.model';
|
||||||
import { CountyRequest } from 'src/models/server.model';
|
import { CountyRequest } from 'src/models/server.model';
|
||||||
import { GeoService } from './geo.service';
|
import { GeoService } from './geo.service';
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { ContextService } from 'src/context/context.service';
|
import { ClsService } from 'nestjs-cls';
|
||||||
import { getRealIpInfo } from 'src/utils/ip.util';
|
import { getRealIpInfo } from 'src/utils/ip.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestDurationMiddleware implements NestMiddleware {
|
export class RequestDurationMiddleware implements NestMiddleware {
|
||||||
private readonly logger = new Logger(RequestDurationMiddleware.name);
|
private readonly logger = new Logger(RequestDurationMiddleware.name);
|
||||||
|
|
||||||
constructor(private readonly contextService: ContextService) {}
|
constructor(private readonly cls: ClsService) {}
|
||||||
|
|
||||||
use(req: Request, res: Response, next: NextFunction) {
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
const { ip, countryCode } = getRealIpInfo(req);
|
const { ip, countryCode } = getRealIpInfo(req);
|
||||||
|
|
||||||
// Initialisieren des Kontextes für diese Anfrage
|
// Setze die IP-Adresse und den Ländercode im CLS-Kontext
|
||||||
this.contextService.run({ ip, countryCode }, () => {
|
try {
|
||||||
|
this.cls.set('ip', ip);
|
||||||
|
this.cls.set('countryCode', countryCode);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Failed to set CLS context', error);
|
||||||
|
}
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
|
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
|
||||||
|
|
@ -31,6 +37,5 @@ export class RequestDurationMiddleware implements NestMiddleware {
|
||||||
});
|
});
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue