ClsService for async request messages & logging

This commit is contained in:
Andreas Knuth 2024-09-23 11:20:00 +02:00
parent 860d30b16f
commit 974a6503ef
9 changed files with 55 additions and 65 deletions

View File

@ -36,6 +36,7 @@
"@nestjs/serve-static": "^4.0.1",
"@types/stripe": "^8.0.417",
"body-parser": "^1.20.2",
"cls-hooked": "^4.2.2",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"dotenv-flow": "^4.1.0",
@ -48,6 +49,7 @@
"jwks-rsa": "^3.1.0",
"ky": "^1.4.0",
"nest-winston": "^1.9.4",
"nestjs-cls": "^4.4.1",
"nodemailer": "^6.9.10",
"nodemailer-smtp-transport": "^2.7.4",
"openai": "^4.52.6",

View File

@ -1,4 +1,4 @@
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston';
@ -19,7 +19,7 @@ import { EventModule } from './event/event.module';
import { JwtStrategy } from './jwt.strategy';
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 { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
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));
@Module({
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 }),
MailModule,
AuthModule,
@ -64,10 +71,11 @@ console.log(JSON.stringify(process.env, null, 2));
EventModule,
],
controllers: [AppController, LogController],
providers: [AppService, FileService, JwtStrategy, ContextService, ContextService],
providers: [AppService, FileService, JwtStrategy],
})
export class AppModule {
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(ClsMiddleware).forRoutes('*');
consumer.apply(RequestDurationMiddleware).forRoutes('*');
}
}

View File

@ -1,16 +1,5 @@
import { createParamDecorator, ExecutionContext, 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);
});
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {

View File

@ -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();
}
}

View File

@ -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);
});

View File

@ -2,30 +2,29 @@ import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { ClsService } from 'nestjs-cls';
import pkg from 'pg';
import { ContextService, RequestContext } from 'src/context/context.service';
import { Logger } from 'winston';
import * as schema from './schema';
import { PG_CONNECTION } from './schema';
const { Pool } = pkg;
@Module({
providers: [
ContextService,
{
provide: PG_CONNECTION,
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService],
useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => {
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ClsService],
useFactory: async (configService: ConfigService, logger: Logger, cls: ClsService) => {
const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({
connectionString,
// ssl: true,
// ssl: true, // Falls benötigt
});
// Definiere einen benutzerdefinierten Logger für Drizzle
const drizzleLogger = {
logQuery(query: string, params: unknown[]): void {
const context: RequestContext | undefined = contextService.getContext();
const ip = context?.ip || 'unknown';
const countryCode = context?.countryCode || 'unknown';
const ip = cls.get('ip') || 'unknown';
const countryCode = cls.get('countryCode') || 'unknown';
logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`);
},
};

View File

@ -1,5 +1,5 @@
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 { RealIpInfo } from 'src/models/main.model';
import { EventService } from './event.service';

View File

@ -1,5 +1,5 @@
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 { CountyRequest } from 'src/models/server.model';
import { GeoService } from './geo.service';

View File

@ -1,36 +1,41 @@
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
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';
@Injectable()
export class RequestDurationMiddleware implements NestMiddleware {
private readonly logger = new Logger(RequestDurationMiddleware.name);
constructor(private readonly contextService: ContextService) {}
constructor(private readonly cls: ClsService) {}
use(req: Request, res: Response, next: NextFunction) {
const { ip, countryCode } = getRealIpInfo(req);
// Initialisieren des Kontextes für diese Anfrage
this.contextService.run({ ip, countryCode }, () => {
const start = Date.now();
// Setze die IP-Adresse und den Ländercode im CLS-Kontext
try {
this.cls.set('ip', ip);
this.cls.set('countryCode', countryCode);
} catch (error) {
this.logger.error('Failed to set CLS context', error);
}
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`;
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
if (req.method === 'POST' || req.method === 'PUT') {
const body = JSON.stringify(req.body);
logMessage += ` - Incoming Body: ${body}`;
}
res.on('finish', () => {
const duration = Date.now() - start;
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`;
this.logger.log(logMessage);
});
if (req.method === 'POST' || req.method === 'PUT') {
const body = JSON.stringify(req.body);
logMessage += ` - Incoming Body: ${body}`;
}
next();
this.logger.log(logMessage);
});
next();
}
}