From fef56ff99417c6b40d1e22f499ba7e8185ba1359 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Tue, 26 Nov 2024 14:59:37 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20[BE]=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20v1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SocketEventLoggerInterceptor.ts | 87 +++++++++++++++++++ BE/src/game/game.gateway.ts | 11 +++ BE/src/game/service/game.service.ts | 6 ++ 3 files changed, 104 insertions(+) create mode 100644 BE/src/common/interceptor/SocketEventLoggerInterceptor.ts diff --git a/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts new file mode 100644 index 00000000..23b9acf0 --- /dev/null +++ b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts @@ -0,0 +1,87 @@ +/** + * @class SocketEventLoggerInterceptor + * @description WebSocket 이벤트 실행 시간과 메서드 정보를 로깅하는 인터셉터 + */ +import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; +import { Observable, tap } from 'rxjs'; +import { Socket } from 'socket.io'; + +interface SocketEventLog { + className: string; + methodName: string; + event: string; + clientId: string; + executionTime: number; + timestamp: string; + payload?: any; +} + +@Injectable() +export class SocketEventLoggerInterceptor implements NestInterceptor { + private readonly logger = new Logger('SocketEventLogger'); + private readonly EXECUTION_TIME_THRESHOLD = 1000; + + intercept(context: ExecutionContext, next: CallHandler): Observable { + if (context.getType() !== 'ws') { + return next.handle(); + } + + const startTime = Date.now(); + const ctx = context.switchToWs(); + const client: Socket = ctx.getClient(); + const event = ctx.getData(); + + // 현재 실행 중인 클래스와 메서드 정보 가져오기 + const className = context.getClass().name; + const methodName = context.getHandler().name; + + return next.handle().pipe( + tap({ + next: (data) => { + const executionTime = Date.now() - startTime; + + const logData: SocketEventLog = { + className, + methodName, + event: typeof event === 'object' ? JSON.stringify(event) : event, + clientId: client.id, + executionTime, + timestamp: new Date().toISOString(), + payload: data + }; + + if (executionTime >= this.EXECUTION_TIME_THRESHOLD) { + this.logger.warn( + '🐢 Slow Socket Event Detected!\n' + + `Class: ${logData.className}\n` + + `Method: ${logData.methodName}\n` + + `Event: ${logData.event}\n` + + `Client: ${logData.clientId}\n` + + `Execution Time: ${logData.executionTime}ms\n` + + `Timestamp: ${logData.timestamp}` + ); + } else { + this.logger.log( + '🚀 Socket Event Processed\n' + + `Class: ${logData.className}\n` + + `Method: ${logData.methodName}\n` + + `Event: ${logData.event}\n` + + `Client: ${logData.clientId}\n` + + `Execution Time: ${logData.executionTime}ms` + ); + } + }, + error: (error) => { + this.logger.error( + '❌ Socket Event Error\n' + + `Class: ${className}\n` + + `Method: ${methodName}\n` + + `Event: ${event}\n` + + `Client: ${client.id}\n` + + `Error: ${error.message}` + ); + } + }) + ); + } +} diff --git a/BE/src/game/game.gateway.ts b/BE/src/game/game.gateway.ts index eaf54b8b..e70a1221 100644 --- a/BE/src/game/game.gateway.ts +++ b/BE/src/game/game.gateway.ts @@ -23,7 +23,9 @@ import { GameRoomService } from './service/game.room.service'; import { WsJwtAuthGuard } from '../auth/guard/ws-jwt-auth.guard'; import { GameActivityInterceptor } from './interceptor/gameActivity.interceptor'; import { KickRoomDto } from './dto/kick-room.dto'; +import { SocketEventLoggerInterceptor } from '../common/interceptor/SocketEventLoggerInterceptor'; +@UseInterceptors(SocketEventLoggerInterceptor) @UseInterceptors(GameActivityInterceptor) @UseFilters(new WsExceptionFilter()) @WebSocketGateway({ @@ -43,6 +45,15 @@ export class GameGateway { private readonly gameRoomService: GameRoomService ) {} + @SubscribeMessage('slowEvent') + async handleSlowEvent(@ConnectedSocket() client: Socket): Promise { + // 의도적으로 지연 발생시키는 테스트 코드 + await this.gameService.longBusinessLogic(); + await new Promise((resolve) => setTimeout(resolve, 1500)); + // 실제 로직 + // ... + } + @SubscribeMessage(SocketEvents.CREATE_ROOM) @UsePipes(new GameValidationPipe(SocketEvents.CREATE_ROOM)) async handleCreateRoom( diff --git a/BE/src/game/service/game.service.ts b/BE/src/game/service/game.service.ts index 89a27863..b7f969f4 100644 --- a/BE/src/game/service/game.service.ts +++ b/BE/src/game/service/game.service.ts @@ -158,4 +158,10 @@ export class GameService { await this.redis.del(roomLeaderboardKey); } } + + async longBusinessLogic() { + this.logger.verbose('longBusinessLogic start'); + await new Promise((resolve) => setTimeout(resolve, 1000)); + this.logger.verbose('longBusinessLogic end'); + } } From 752b6a7a5990f09af68bb07d92ba517c00597e22 Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Tue, 26 Nov 2024 15:39:28 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20[BE]=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B5=AC=ED=98=84=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SocketEventLoggerInterceptor.ts | 224 ++++++++++++++---- BE/src/game/service/game.room.service.ts | 3 + BE/src/game/service/game.service.ts | 2 + 3 files changed, 180 insertions(+), 49 deletions(-) diff --git a/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts index 23b9acf0..5d6f067e 100644 --- a/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts +++ b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts @@ -1,26 +1,61 @@ -/** - * @class SocketEventLoggerInterceptor - * @description WebSocket 이벤트 실행 시간과 메서드 정보를 로깅하는 인터셉터 - */ +import { firstValueFrom, Observable } from 'rxjs'; import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; -import { Observable, tap } from 'rxjs'; +import { ModuleRef } from '@nestjs/core'; import { Socket } from 'socket.io'; +import { AsyncLocalStorage } from 'async_hooks'; // 이 부분 추가 + +/** + * @class TraceStore + * @description 함수 호출 추적을 위한 저장소 + */ +export class TraceStore { + private static instance = new AsyncLocalStorage(); -interface SocketEventLog { - className: string; - methodName: string; - event: string; - clientId: string; - executionTime: number; - timestamp: string; - payload?: any; + static getStore() { + return this.instance; + } } +/** + * @class TraceContext + * @description 추적 컨텍스트 + */ +class TraceContext { + private depth = 0; + private logs: string[] = []; + + increaseDepth() { + this.depth++; + } + + decreaseDepth() { + this.depth--; + } + + addLog(message: string) { + const indent = ' '.repeat(this.depth); + this.logs.push(`${indent}${message}`); + } + + getLogs(): string[] { + return this.logs; + } +} + +// 전역 AsyncLocalStorage 인스턴스 +export const traceStore = new AsyncLocalStorage(); + +/** + * @class SocketEventLoggerInterceptor + * @description WebSocket 이벤트와 서비스 호출을 로깅하는 인터셉터 + */ @Injectable() export class SocketEventLoggerInterceptor implements NestInterceptor { private readonly logger = new Logger('SocketEventLogger'); private readonly EXECUTION_TIME_THRESHOLD = 1000; + constructor(private readonly moduleRef: ModuleRef) {} + intercept(context: ExecutionContext, next: CallHandler): Observable { if (context.getType() !== 'ws') { return next.handle(); @@ -30,58 +65,149 @@ export class SocketEventLoggerInterceptor implements NestInterceptor { const ctx = context.switchToWs(); const client: Socket = ctx.getClient(); const event = ctx.getData(); - - // 현재 실행 중인 클래스와 메서드 정보 가져오기 const className = context.getClass().name; const methodName = context.getHandler().name; - return next.handle().pipe( - tap({ - next: (data) => { - const executionTime = Date.now() - startTime; + // 새로운 추적 컨텍스트 시작 + const traceContext = new TraceContext(); - const logData: SocketEventLog = { - className, - methodName, - event: typeof event === 'object' ? JSON.stringify(event) : event, - clientId: client.id, - executionTime, - timestamp: new Date().toISOString(), - payload: data - }; + return new Observable((subscriber) => { + // AsyncLocalStorage를 사용하여 추적 컨텍스트 설정 + TraceStore.getStore().run(traceContext, async () => { + try { + // 핸들러 실행 전 로그 + traceContext.addLog(`[${className}.${methodName}] Started`); + + // 원본 핸들러 실행 + const result = await firstValueFrom(next.handle()); + + const executionTime = Date.now() - startTime; + const logs = traceContext.getLogs(); if (executionTime >= this.EXECUTION_TIME_THRESHOLD) { this.logger.warn( '🐢 Slow Socket Event Detected!\n' + - `Class: ${logData.className}\n` + - `Method: ${logData.methodName}\n` + - `Event: ${logData.event}\n` + - `Client: ${logData.clientId}\n` + - `Execution Time: ${logData.executionTime}ms\n` + - `Timestamp: ${logData.timestamp}` + logs.join('\n') + + `\nTotal Execution Time: ${executionTime}ms` ); } else { this.logger.log( '🚀 Socket Event Processed\n' + - `Class: ${logData.className}\n` + - `Method: ${logData.methodName}\n` + - `Event: ${logData.event}\n` + - `Client: ${logData.clientId}\n` + - `Execution Time: ${logData.executionTime}ms` + logs.join('\n') + + `\nTotal Execution Time: ${executionTime}ms` ); } - }, - error: (error) => { + + subscriber.next(result); + subscriber.complete(); + } catch (error) { + const logs = traceContext.getLogs(); this.logger.error( - '❌ Socket Event Error\n' + - `Class: ${className}\n` + - `Method: ${methodName}\n` + - `Event: ${event}\n` + - `Client: ${client.id}\n` + - `Error: ${error.message}` + '❌ Socket Event Error\n' + logs.join('\n') + `\nError: ${error.message}` ); + subscriber.error(error); } - }) - ); + }); + }); } } + +/** + * @function Trace + * @description 서비스 메서드 추적을 위한 데코레이터 + */ +export function Trace() { + return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const className = target.constructor.name; + const startTime = Date.now(); + + const traceContext = TraceStore.getStore().getStore(); + if (traceContext) { + traceContext.increaseDepth(); + traceContext.addLog(`[${className}.${propertyKey}] Started`); + } + + try { + const result = await originalMethod.apply(this, args); + + if (traceContext) { + const executionTime = Date.now() - startTime; + traceContext.addLog(`[${className}.${propertyKey}] Completed (${executionTime}ms)`); + traceContext.decreaseDepth(); + } + + return result; + } catch (error) { + if (traceContext) { + const executionTime = Date.now() - startTime; + traceContext.addLog( + `[${className}.${propertyKey}] Failed (${executionTime}ms): ${error.message}` + ); + traceContext.decreaseDepth(); + } + throw error; + } + }; + + return descriptor; + }; +} + +/** + * @function TraceClass + * @description 클래스의 모든 메서드에 추적을 적용하는 데코레이터 + */ +export function TraceClass() { + return function (constructor: T) { + // 프로토타입의 모든 메서드를 가져옴 + const methods = Object.getOwnPropertyNames(constructor.prototype); + + methods.forEach((methodName) => { + // constructor와 private/protected 메서드 제외 + if (methodName === 'constructor' || methodName.startsWith('_')) { + return; + } + + const descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, methodName); + + if (descriptor && typeof descriptor.value === 'function') { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const traceContext = traceStore.getStore(); + if (!traceContext) { + return originalMethod.apply(this, args); + } + + const startTime = Date.now(); + const className = constructor.name; + + traceContext.increaseDepth(); + traceContext.addLog(`[${className}.${methodName}] Started`); + + try { + const result = await originalMethod.apply(this, args); + const executionTime = Date.now() - startTime; + traceContext.addLog(`[${className}.${methodName}] Completed (${executionTime}ms)`); + traceContext.decreaseDepth(); + return result; + } catch (error) { + const executionTime = Date.now() - startTime; + traceContext.addLog( + `[${className}.${methodName}] Failed (${executionTime}ms): ${error.message}` + ); + traceContext.decreaseDepth(); + throw error; + } + }; + + Object.defineProperty(constructor.prototype, methodName, descriptor); + } + }); + + return constructor; + }; +} diff --git a/BE/src/game/service/game.room.service.ts b/BE/src/game/service/game.room.service.ts index af4c4ef5..489c83c2 100644 --- a/BE/src/game/service/game.room.service.ts +++ b/BE/src/game/service/game.room.service.ts @@ -12,6 +12,7 @@ import { UpdateRoomQuizsetDto } from '../dto/update-room-quizset.dto'; import { Cron, CronExpression } from '@nestjs/schedule'; import { Socket } from 'socket.io'; import { KickRoomDto } from '../dto/kick-room.dto'; +import { Trace } from '../../common/interceptor/SocketEventLoggerInterceptor'; @Injectable() export class GameRoomService { @@ -24,6 +25,7 @@ export class GameRoomService { private readonly gameValidator: GameValidator ) {} + @Trace() async createRoom(gameConfig: CreateGameDto, clientId: string): Promise { const currentRoomPins = await this.redis.smembers(REDIS_KEY.ACTIVE_ROOMS); const roomId = generateUniquePin(currentRoomPins); @@ -49,6 +51,7 @@ export class GameRoomService { return roomId; } + @Trace() async joinRoom(client: Socket, dto: JoinRoomDto, clientId: string) { const roomKey = REDIS_KEY.ROOM(dto.gameId); const room = await this.redis.hgetall(roomKey); diff --git a/BE/src/game/service/game.service.ts b/BE/src/game/service/game.service.ts index b7f969f4..fab72e1b 100644 --- a/BE/src/game/service/game.service.ts +++ b/BE/src/game/service/game.service.ts @@ -10,6 +10,7 @@ import { Server } from 'socket.io'; import { mockQuizData } from '../../../test/mocks/quiz-data.mock'; import { QuizCacheService } from './quiz.cache.service'; import { RedisSubscriberService } from '../redis/redis-subscriber.service'; +import { Trace } from '../../common/interceptor/SocketEventLoggerInterceptor'; @Injectable() export class GameService { @@ -159,6 +160,7 @@ export class GameService { } } + @Trace() async longBusinessLogic() { this.logger.verbose('longBusinessLogic start'); await new Promise((resolve) => setTimeout(resolve, 1000)); From 69cef1e8cba30603d62ddcc946788a6a6c48590e Mon Sep 17 00:00:00 2001 From: DongHoonYu96 Date: Tue, 26 Nov 2024 15:59:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20[BE]=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B5=AC=ED=98=84=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클래스단위로 적용가능 --- .../SocketEventLoggerInterceptor.ts | 87 +++++++++++-------- BE/src/game/service/game.chat.service.ts | 18 ++-- BE/src/game/service/game.room.service.ts | 5 +- BE/src/game/service/game.service.ts | 3 +- BE/src/game/service/quiz.cache.service.ts | 2 + 5 files changed, 66 insertions(+), 49 deletions(-) diff --git a/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts index 5d6f067e..e8da0326 100644 --- a/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts +++ b/BE/src/common/interceptor/SocketEventLoggerInterceptor.ts @@ -43,7 +43,7 @@ class TraceContext { } // 전역 AsyncLocalStorage 인스턴스 -export const traceStore = new AsyncLocalStorage(); +// export const traceStore = new AsyncLocalStorage(); /** * @class SocketEventLoggerInterceptor @@ -160,52 +160,63 @@ export function Trace() { * @function TraceClass * @description 클래스의 모든 메서드에 추적을 적용하는 데코레이터 */ -export function TraceClass() { - return function (constructor: T) { - // 프로토타입의 모든 메서드를 가져옴 - const methods = Object.getOwnPropertyNames(constructor.prototype); - - methods.forEach((methodName) => { - // constructor와 private/protected 메서드 제외 - if (methodName === 'constructor' || methodName.startsWith('_')) { +/** + * @class TraceClass + * @description 클래스의 모든 메서드에 추적을 적용하는 데코레이터 + */ +export function TraceClass( + options: Partial<{ excludeMethods: string[]; includePrivateMethods: boolean }> = {} +) { + return function classDecorator(constructor: T) { + const originalPrototype = constructor.prototype; + + Object.getOwnPropertyNames(originalPrototype).forEach((methodName) => { + // 제외할 메서드 체크 + if ( + methodName === 'constructor' || + (!options.includePrivateMethods && methodName.startsWith('_')) || + options.excludeMethods?.includes(methodName) + ) { return; } - const descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, methodName); + const descriptor = Object.getOwnPropertyDescriptor(originalPrototype, methodName); + if (!descriptor || typeof descriptor.value !== 'function') { + return; + } - if (descriptor && typeof descriptor.value === 'function') { - const originalMethod = descriptor.value; + const originalMethod = descriptor.value; - descriptor.value = async function (...args: any[]) { - const traceContext = traceStore.getStore(); - if (!traceContext) { - return originalMethod.apply(this, args); - } + descriptor.value = async function (...args: any[]) { + const traceContext = TraceStore.getStore().getStore(); + if (!traceContext) { + return originalMethod.apply(this, args); + } - const startTime = Date.now(); - const className = constructor.name; + const startTime = Date.now(); - traceContext.increaseDepth(); - traceContext.addLog(`[${className}.${methodName}] Started`); + traceContext.increaseDepth(); + traceContext.addLog(`[${constructor.name}.${methodName}] Started`); - try { - const result = await originalMethod.apply(this, args); - const executionTime = Date.now() - startTime; - traceContext.addLog(`[${className}.${methodName}] Completed (${executionTime}ms)`); - traceContext.decreaseDepth(); - return result; - } catch (error) { - const executionTime = Date.now() - startTime; - traceContext.addLog( - `[${className}.${methodName}] Failed (${executionTime}ms): ${error.message}` - ); - traceContext.decreaseDepth(); - throw error; - } - }; + try { + const result = await originalMethod.apply(this, args); + const executionTime = Date.now() - startTime; - Object.defineProperty(constructor.prototype, methodName, descriptor); - } + traceContext.addLog(`[${constructor.name}.${methodName}] Completed (${executionTime}ms)`); + traceContext.decreaseDepth(); + + return result; + } catch (error) { + const executionTime = Date.now() - startTime; + traceContext.addLog( + `[${constructor.name}.${methodName}] Failed (${executionTime}ms): ${error.message}` + ); + traceContext.decreaseDepth(); + throw error; + } + }; + + Object.defineProperty(originalPrototype, methodName, descriptor); }); return constructor; diff --git a/BE/src/game/service/game.chat.service.ts b/BE/src/game/service/game.chat.service.ts index 740ac415..4b49bb34 100644 --- a/BE/src/game/service/game.chat.service.ts +++ b/BE/src/game/service/game.chat.service.ts @@ -6,7 +6,9 @@ import { ChatMessageDto } from '../dto/chat-message.dto'; import { REDIS_KEY } from '../../common/constants/redis-key.constant'; import SocketEvents from '../../common/constants/socket-events'; import { Server } from 'socket.io'; +import { TraceClass } from '../../common/interceptor/SocketEventLoggerInterceptor'; +@TraceClass() @Injectable() export class GameChatService { private readonly logger = new Logger(GameChatService.name); @@ -61,14 +63,16 @@ export class GameChatService { // 죽은 사람의 채팅은 죽은 사람끼리만 볼 수 있도록 처리 const players = await this.redis.smembers(REDIS_KEY.ROOM_PLAYERS(gameId)); - await Promise.all(players.map(async (playerId) => { - const playerKey = REDIS_KEY.PLAYER(playerId); - const isAlive = await this.redis.hget(playerKey, 'isAlive'); + await Promise.all( + players.map(async (playerId) => { + const playerKey = REDIS_KEY.PLAYER(playerId); + const isAlive = await this.redis.hget(playerKey, 'isAlive'); - if (isAlive === '0') { - server.to(playerId).emit(SocketEvents.CHAT_MESSAGE, chatMessage); - } - })); + if (isAlive === '0') { + server.to(playerId).emit(SocketEvents.CHAT_MESSAGE, chatMessage); + } + }) + ); }); } } diff --git a/BE/src/game/service/game.room.service.ts b/BE/src/game/service/game.room.service.ts index 489c83c2..738a6fb5 100644 --- a/BE/src/game/service/game.room.service.ts +++ b/BE/src/game/service/game.room.service.ts @@ -12,8 +12,9 @@ import { UpdateRoomQuizsetDto } from '../dto/update-room-quizset.dto'; import { Cron, CronExpression } from '@nestjs/schedule'; import { Socket } from 'socket.io'; import { KickRoomDto } from '../dto/kick-room.dto'; -import { Trace } from '../../common/interceptor/SocketEventLoggerInterceptor'; +import { TraceClass } from '../../common/interceptor/SocketEventLoggerInterceptor'; +@TraceClass() @Injectable() export class GameRoomService { private readonly logger = new Logger(GameRoomService.name); @@ -25,7 +26,6 @@ export class GameRoomService { private readonly gameValidator: GameValidator ) {} - @Trace() async createRoom(gameConfig: CreateGameDto, clientId: string): Promise { const currentRoomPins = await this.redis.smembers(REDIS_KEY.ACTIVE_ROOMS); const roomId = generateUniquePin(currentRoomPins); @@ -51,7 +51,6 @@ export class GameRoomService { return roomId; } - @Trace() async joinRoom(client: Socket, dto: JoinRoomDto, clientId: string) { const roomKey = REDIS_KEY.ROOM(dto.gameId); const room = await this.redis.hgetall(roomKey); diff --git a/BE/src/game/service/game.service.ts b/BE/src/game/service/game.service.ts index fab72e1b..7bab95fc 100644 --- a/BE/src/game/service/game.service.ts +++ b/BE/src/game/service/game.service.ts @@ -10,8 +10,9 @@ import { Server } from 'socket.io'; import { mockQuizData } from '../../../test/mocks/quiz-data.mock'; import { QuizCacheService } from './quiz.cache.service'; import { RedisSubscriberService } from '../redis/redis-subscriber.service'; -import { Trace } from '../../common/interceptor/SocketEventLoggerInterceptor'; +import { Trace, TraceClass } from '../../common/interceptor/SocketEventLoggerInterceptor'; +@TraceClass() @Injectable() export class GameService { private readonly logger = new Logger(GameService.name); diff --git a/BE/src/game/service/quiz.cache.service.ts b/BE/src/game/service/quiz.cache.service.ts index 644e2ea8..0942ca43 100644 --- a/BE/src/game/service/quiz.cache.service.ts +++ b/BE/src/game/service/quiz.cache.service.ts @@ -5,7 +5,9 @@ import { Redis } from 'ioredis'; import { mockQuizData } from '../../../test/mocks/quiz-data.mock'; import { REDIS_KEY } from '../../common/constants/redis-key.constant'; import { QuizSetService } from '../../quiz-set/service/quiz-set.service'; +import { TraceClass } from '../../common/interceptor/SocketEventLoggerInterceptor'; +@TraceClass() @Injectable() export class QuizCacheService { private readonly quizCache = new Map();