-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #270 from DongHoonYu96/feature-be-#229
[BE] feat#229 λͺ¨λν°λ§ μΈν°μ ν° κ΅¬ν
- Loading branch information
Showing
6 changed files
with
259 additions
and
7 deletions.
There are no files selected for viewing
224 changes: 224 additions & 0 deletions
224
BE/src/common/interceptor/SocketEventLoggerInterceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
import { firstValueFrom, Observable } from 'rxjs'; | ||
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; | ||
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<TraceContext>(); | ||
|
||
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<TraceContext>(); | ||
|
||
/** | ||
* @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<any> { | ||
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; | ||
|
||
// μλ‘μ΄ μΆμ 컨ν μ€νΈ μμ | ||
const traceContext = new TraceContext(); | ||
|
||
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' + | ||
logs.join('\n') + | ||
`\nTotal Execution Time: ${executionTime}ms` | ||
); | ||
} else { | ||
this.logger.log( | ||
'π Socket Event Processed\n' + | ||
logs.join('\n') + | ||
`\nTotal Execution Time: ${executionTime}ms` | ||
); | ||
} | ||
|
||
subscriber.next(result); | ||
subscriber.complete(); | ||
} catch (error) { | ||
const logs = traceContext.getLogs(); | ||
this.logger.error( | ||
'β 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 ν΄λμ€μ λͺ¨λ λ©μλμ μΆμ μ μ μ©νλ λ°μ½λ μ΄ν° | ||
*/ | ||
/** | ||
* @class TraceClass | ||
* @description ν΄λμ€μ λͺ¨λ λ©μλμ μΆμ μ μ μ©νλ λ°μ½λ μ΄ν° | ||
*/ | ||
export function TraceClass( | ||
options: Partial<{ excludeMethods: string[]; includePrivateMethods: boolean }> = {} | ||
) { | ||
return function classDecorator<T extends { new (...args: any[]): {} }>(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(originalPrototype, methodName); | ||
if (!descriptor || typeof descriptor.value !== 'function') { | ||
return; | ||
} | ||
|
||
const originalMethod = descriptor.value; | ||
|
||
descriptor.value = async function (...args: any[]) { | ||
const traceContext = TraceStore.getStore().getStore(); | ||
if (!traceContext) { | ||
return originalMethod.apply(this, args); | ||
} | ||
|
||
const startTime = Date.now(); | ||
|
||
traceContext.increaseDepth(); | ||
traceContext.addLog(`[${constructor.name}.${methodName}] Started`); | ||
|
||
try { | ||
const result = await originalMethod.apply(this, args); | ||
const executionTime = Date.now() - startTime; | ||
|
||
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; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters