import { loggerConfig } from '@/config/logger.config';
import { LogBehavior } from './log-behavior.decorator';
class LoggerService {
private static instance: LoggerService;
private readonly logger;
private constructor() {
this.logger = loggerConfig;
}
static getInstance(): LoggerService {
if (!LoggerService.instance) {
LoggerService.instance = new LoggerService();
}
return LoggerService.instance;
}
@LogBehavior()
error(message: string, context?: any, stack?: any) {
this.logger.error({ message, context, stack });
}
@LogBehavior()
log(message: string, context?: any) {
this.logger.info({ message, context });
}
@LogBehavior()
warn(message: string, context?: any) {
this.logger.warn({ message, context });
}
debug(message: string, context?: any) {
this.logger.debug({ message, context });
}
verbose(message: string, context?: any) {
this.logger.trace({ message, context });
}
}
export const logger = LoggerService.getInstance();
import { CloudWatchLogSender } from '../infra/cloud-watch';
import { LogSender } from '../interface/log-sender.interface';
const logSender: LogSender = CloudWatchLogSender.getInstance();
export function LogBehavior(): MethodDecorator {
return function (target: any, propertyKey: LogLevel, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const message = args[0];
const context = args[1];
const stack = args[2];
logAction(propertyKey, message, context, stack);
return originalMethod.apply(this, args);
};
};
}
function logAction(level: LogLevel, message: string, context: any, stack: any) {
if (process.env.NODE_ENV === 'test') {
['error', 'log', 'warn', 'debug', 'verbose'].includes(level) && console.log(message, context);
return;
}
message && logSender?.sendLog(`[${level}] ${message}`, context, stack);
}
type LogLevel = 'error' | 'log' | 'warn' | 'debug' | 'verbose';
logSender라는 인터페이스를 만들어 언제든 클라우드워치와 ELK를 바꿔끼울 수 있게 만들었다.
export interface LogSender {
sendLog(message: string, context: any, stack: any): void;
}
아래는 클라우드 워치 구현
import { ConfigService } from '@nestjs/config';
import AWS from 'aws-sdk';
import { LogSender } from '@shared/interface/log-sender.interface';
export class CloudWatchLogSender implements LogSender {
private cloudWatchLogs: AWS.CloudWatchLogs;
private configService: ConfigService;
private static instance: CloudWatchLogSender;
private constructor() {
this.cloudWatchLogs = new AWS.CloudWatchLogs();
this.configService = new ConfigService();
}
static getInstance(): CloudWatchLogSender {
if (!CloudWatchLogSender.instance) {
CloudWatchLogSender.instance = new CloudWatchLogSender();
}
return CloudWatchLogSender.instance;
}
async sendLog(message: string, context: any, stack: any): Promise<void> {
const logGroupName = this.configService.get<string>('AWS_CLOUDWATCH_LOG_GROUP_NAME');
const logStreamName = this.configService.get<string>('AWS_CLOUDWATCH_LOG_STREAM_NAME');
const logEvent: AWS.CloudWatchLogs.Types.InputLogEvent = {
message: JSON.stringify({
message,
...(context && { context }),
...(stack && { stack }),
}),
timestamp: Date.now(),
};
const params = {
logGroupName,
logStreamName,
logEvents: [logEvent],
};
try {
await this.cloudWatchLogs.putLogEvents(params).promise();
} catch (error) {
console.error('[CloudWatchLogSender] putLogEvents fail. error:', error);
throw error;
}
}
}
로그 앞에 로그레벨까지 붙여서 완성.