로깅과 감사 추적
로깅과 감사 추적은 애플리케이션의 동작을 이해하고, 문제를 진단하며, 보안 사고를 탐지하고 대응하는 데 필수적인 요소입니다.
효과적인 로깅 시스템은 개발자와 운영팀에게 애플리케이션의 상태와 행동에 대한 중요한 통찰력을 제공합니다.
NestJS의 로깅 구현
내장 로거 사용
NestJS는 기본적으로 내장 로거를 제공합니다.
import { Controller, Get, Logger } from '@nestjs/common';
@Controller('cats')
export class CatsController {
private readonly logger = new Logger(CatsController.name);
@Get()
findAll(): string {
this.logger.log('Returning all cats');
return 'This action returns all cats';
}
}
사용자 정의 로거 구현
Winston을 사용한 사용자 정의 로거
import { Injectable, LoggerService } from '@nestjs/common';
import * as winston from 'winston';
@Injectable()
export class CustomLogger implements LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
}
log(message: string) {
this.logger.info(message);
}
error(message: string, trace: string) {
this.logger.error(message, { trace });
}
warn(message: string) {
this.logger.warn(message);
}
debug(message: string) {
this.logger.debug(message);
}
}
로그 레벨의 적절한 사용
- DEBUG : 개발 중 상세한 정보가 필요할 때 사용
- INFO : 일반적인 작업의 성공적인 완료를 기록
- WARN : 잠재적인 문제나 예상치 못한 상황을 표시
- ERROR : 오류 및 예외 상황을 기록
예시
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);
async createUser(userData: CreateUserDto) {
this.logger.debug(`Attempting to create user: ${userData.email}`);
try {
const user = await this.userRepository.create(userData);
this.logger.log(`User created successfully: ${user.id}`);
return user;
} catch (error) {
this.logger.error(`Failed to create user: ${error.message}`, error.stack);
throw error;
}
}
}
구조화된 로깅
구조화된 로깅은 로그 데이터를 쉽게 검색하고 분석할 수 있게 해줍니다.
import { createLogger, format, transports } from 'winston';
const logger = createLogger({
format: format.combine(
format.timestamp(),
format.json()
),
transports: [new transports.Console()],
});
logger.info('User action', {
userId: '12345',
action: 'login',
ipAddress: '192.168.1.1',
});
분산 시스템에서의 로그 관리
ELK 스택을 사용한 로그 집계 및 분석
- Logstash 설정 (logstash.conf)
input {
file {
path => "/path/to/your/nestjs/logs/*.log"
start_position => "beginning"
}
}
filter {
json {
source => "message"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "nestjs-logs-%{+YYYY.MM.dd}"
}
}
- NestJS 애플리케이션에서 Elasticsearch로 직접 로그 전송
import { Client } from '@elastic/elasticsearch';
const client = new Client({ node: 'http://localhost:9200' });
async function log(level: string, message: string, meta: any) {
await client.index({
index: 'nestjs-logs',
body: {
timestamp: new Date(),
level,
message,
...meta,
},
});
}
감사 추적(Audit Trail) 구현
감사 추적은 사용자 활동을 기록하여 보안, 규정 준수, 문제 해결에 활용됩니다.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuditLog } from './audit-log.entity';
@Injectable()
export class AuditService {
constructor(
@InjectRepository(AuditLog)
private auditLogRepository: Repository<AuditLog>,
) {}
async log(userId: string, action: string, details: any) {
const auditLog = new AuditLog();
auditLog.userId = userId;
auditLog.action = action;
auditLog.details = details;
auditLog.timestamp = new Date();
await this.auditLogRepository.save(auditLog);
}
}
사용 예
@Injectable()
export class UserService {
constructor(private auditService: AuditService) {}
async updateUser(userId: string, userData: UpdateUserDto) {
// 사용자 업데이트 로직
await this.auditService.log(userId, 'UPDATE_USER', { newData: userData });
}
}
로그 및 감사 데이터의 보안
1. 민감한 정보 마스킹
const maskSensitiveData = (data: any) => {
const masked = { ...data };
if (masked.password) masked.password = '********';
if (masked.creditCard) masked.creditCard = '**** **** **** ' + masked.creditCard.slice(-4);
return masked;
};
logger.info('User data', maskSensitiveData(userData));
2. GDPR 준수를 위한 로그 관리
- 개인 식별 정보(PII) 최소화
- 로그 보존 기간 설정
- 데이터 주체의 권리 보장 (예 : 삭제 요청 시 로그에서도 개인정보 제거)
로그 로테이션 및 보존 정책
Winston과 winston-daily-rotate-file을 사용한 로그 로테이션
import * as winston from 'winston';
import 'winston-daily-rotate-file';
const logger = winston.createLogger({
transports: [
new winston.transports.DailyRotateFile({
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
})
]
});
Best Practices 및 주의사항
- 일관된 로그 형식 사용 : 구조화된 JSON 형식 권장
- 컨텍스트 정보 포함 : 요청 ID, 사용자 ID, 세션 정보 등
- 적절한 로그 레벨 사용 : 환경별로 로그 레벨 조정
- 에러 스택 트레이스 포함 : 디버깅을 위해 중요
- 성능 고려 : 로깅이 애플리케이션 성능에 미치는 영향 최소화
- 로그 집계 및 검색 도구 활용 : 중앙화된 로그 관리 시스템 구축
- 정기적인 로그 분석 : 패턴 및 이상 징후 탐지
- 보안 고려 : 로그에 민감한 정보 포함 주의
- 로그 보존 정책 수립 : 법적 요구사항 및 스토리지 비용 고려
- 감사 로그의 무결성 보장 : 로그 변조 방지 메커니즘 구현
로깅과 감사 추적 시스템은 지속적인 관리와 개선이 필요합니다.
정기적으로 로그를 분석하고, 새로운 요구사항이나 보안 위협에 대응하여 시스템을 업데이트해야 합니다.
이러한 노력을 통해 NestJS 애플리케이션의 안정성, 보안성, 그리고 운영 효율성을 크게 향상시킬 수 있습니다.