blanc-logger
Version:
Advanced Winston logger for NestJS & TypeORM with structured logging.
361 lines (279 loc) • 13.5 kB
Markdown
# Blanc Logger
Advanced Winston logger with TypeORM support & structured logging.
> **Blanc Logger**는 NestJS 및 TypeORM 애플리케이션을 위한 고급 로깅 라이브러리입니다.
> 이 라이브러리는 **Winston**을 기반으로 콘솔 및 파일 로깅을 지원하며,
> 자동 구조화된 JSON 포맷, SQL 쿼리 하이라이팅, 모듈 기반 로그 추적 등 다양한 기능을 제공합니다.
[](https://www.npmjs.com/package/blanc-logger)
[](https://nodejs.org/)
[](https://www.typescriptlang.org/)
[](https://github.com/yooseungmo/blanc-logger/blob/main/LICENSE)
---
## Installation
```sh
npm install blanc-logger
```
---
## Usage
### 1. NestJS 전역 Logger 적용 (`main.ts`)
> _**Blanc Logger를 전역 로거**로 적용하면, 애플리케이션 전체에서 **동일한 로깅 설정**을 사용할 수 있습니다._
```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { blancLogger, BlancLoggerMiddleware } from 'blanc-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: blancLogger, // 전역 Logger 적용
});
// 미들웨어를 적용하여 요청 시 모듈명 등 컨텍스트 정보를 추가
app.use(new BlancLoggerMiddleware().use);
await app.listen(3000);
}
bootstrap();
```
### 2. TypeORM Logger 적용 (`AppModule`)
> _TypeORM 설정에서 Blanc Logger의 전용 로거를 사용하여, **SQL 쿼리 및 데이터베이스 관련 로그**를 효과적으로 기록할 수 있습니다._
```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeOrmBlancLogger } from 'blanc-logger';
@Module({
imports: [
TypeOrmModule.forRoot({
// ... DB 설정
logging: true,
logger: new TypeOrmBlancLogger(), // TypeORM용 로거 사용
}),
// 다른 모듈들...
],
})
export class AppModule {}
```
## 기존 Logger 대체 방법
> _Blanc Logger를 사용하여 기존의 NestJS 내장 로거를 대체할 수 있습니다._
### 1. 기존 코드
```typescript
// 기존 NestJS 내장 로거 사용:
this.logger.error('Error message', error.stack);
this.logger.log('Log message');
```
### 2. Blanc Logger 사용
```typescript
// 에러 발생 시, 스택 정보와 함께 기록
blancLogger.error('Error message', { moduleName: 'ModuleName', stack: error.stack });
blancLogger.log('Log message', { moduleName: 'ModuleName' });
blancLogger.warn('Warn message', { moduleName: 'ModuleName' });
blancLogger.verbose('Verbose message', { moduleName: 'ModuleName' });
blancLogger.debug('Debug message', { moduleName: 'ModuleName' });
```
### 3. Logs 파일
> **로그 파일이 자동으로 날짜별로 생성되어 logs 폴더 아래에 다음과 같이 쌓입니다.**
```bash
logs/
├── combined-2025-03-03.log # 모든 로그 메시지
├── error-2025-03-03.log # error 레벨 로그
├── exceptions-2025-03-03.log # 처리되지 않은 예외 로그
└── rejections-2025-03-03.log # 미처리된 Promise 거부 로그
```
> **예시 로그 (error-2025-03-03.log 파일):**
```json
{"level":"error","message":"HTTP Exception: DUPLICATION","stack":[{"moduleName":"user","path":"/api/user/profile","stack":"ConflictException: DUPLICATION\n at UserService.getProfile (/path/to/src/user/user.service.ts:60:13)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"}],"timestamp":"2025-03-03 14:29:51"}
```
---
## Overview
| Feature | Description |
|--------------------------------------------|-------------------------------------------------------------------------|
| **다중 전송 로깅 (Multi-Transport Logging)** | - 콘솔 출력과 파일 저장을 동시에 지원<br>- 일정 크기(예: 20MB) 또는 일정 주기(예: 15일)에 따라 자동으로 로그 파일 회전<br>- 로그 파일 예시: `combined-YYYY-MM-DD.log`, `error-YYYY-MM-DD.log`, `exceptions-YYYY-MM-DD.log`, `rejections-YYYY-MM-DD.log`|
| **구조화된 로그 출력 (Structured Logging)** | - UUIDv5를 사용해 고유 LogID 생성<br>- `{timestamp, level, message}` 형식의 표준 JSON 포맷으로 출력되어 외부 시스템 연동 용이 |
| **모듈별 컨텍스트 추적 (Contextual Tracing)** | - `BlancLoggerMiddleware`를 활용하여 HTTP 요청에서 모듈명 자동 추출<br>- `[UserService → AuthModule → Subsystem]`과 같이 계층 구조로 표시 |
| **동적 로그 레벨 관리 (Dynamic Log Levels)** | - 개발 및 운영 환경에 따라 `debug, verbose, info, warn, error` 로그 레벨을 실시간으로 조절 가능<br>- 필요한 정보만 선별 기록하여 로그의 가독성과 관리 효율성을 향상 |
| **SQL 구문 하이라이팅 (SQL Syntax Highlighting)** | - SQL 쿼리의 키워드, 값, 테이블명을 색상 강조 및 들여쓰기로 구분<br>- 파라미터 값 컨텍스트 인식 강조 |
| **쿼리 성능 분석 (Query Analysis)** | - SQL 성능 저하 유발 패턴 자동 감지 (`SELECT *`, `JOIN 조건 누락` 등) |
| **에러 진단 및 스택 추적 (Error Diagnostics)** | - 에러 발생 시 다중 스택 레이어와 추가 메타데이터를 포함하여 상세한 에러 로그 기록 |
| **성능 모니터링 (Performance Monitoring)** | - HTTP 요청 처리 시간 및 `Slow Query`(예: 500ms) 경고 로그 생성<br>- 실행 계획(Explain Plan) 리포트 시각화 |
| **커스터마이징 (Customization)** | - `logger-config.yaml`파일을 통해 로그 저장 경로, 로그 레벨, 파일 크기, 회전 주기 등을 쉽게 조정 가능 |
---
## Configuration
> Blanc Logger는 기본 설정을 제공하지만, 필요에 따라 **사용자 환경**에 맞게 커스터마이징 할 수 있습니다.
> 설정을 변경하려면 `프로젝트 루트`에 `logger-config.yaml` 파일을 생성하고 아래와 같이 `Override`할 수 있습니다.
```yaml
LOG_DIR: logs # 로그 파일 저장 경로 (기본: 프로젝트 루트/logs)
CONSOLE_LOG_LEVEL: info # 콘솔 출력 로그 레벨 (debug, verbose, info, warn, error)
FILE_LOG_LEVEL: error # 파일 출력 로그 레벨 (debug, verbose, info, warn, error)
ROTATION_DAYS: 30d # 로그 파일 보관 기간 (예: 30일)
MAX_FILE_SIZE: 20m # 단일 파일 최대 크기 (예: 20MB)
```
---
## Log Output Examples
> **SQL Query Log Example**
```sql
╔═ SQL Query ═════════════════════════════════
SELECT
"user"."id" AS "userId",
"user"."email" AS "userEmail"
FROM "user" "user"
WHERE "user"."age" > $1
╠═ Parameters ═══════════════════════════════
[18]
╠═ Analysis ═════════════════════════════════
⚠️ Avoid SELECT * - specify columns explicitly
╚═════════════════════════════════════════════
```
---
> **Console Output Log Example**
<p align="left">
<img src="https://github.com/user-attachments/assets/78132538-f869-4202-881c-14bce5569f22" alt="blanc-logger-output-log" width="600">
</p>
---
> **Console Error Log Example**
<p align="left">
<img src="https://github.com/user-attachments/assets/7a93475b-d392-4169-9da2-d203f6f23ba2" alt="blanc-logger-error-log" width="600">
</p>
---
## Using Examples
### 1. Logging Interceptor 구현
>_**HTTP 요청 처리 시간을 측정**하여 로그로 기록하는 인터셉터 예제입니다._
>_**BlancLoggerMiddleware**를 통해 설정된 모듈명 정보가 자동으로 포함됩니다._
```typescript
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { blancLogger } from 'blanc-logger';
import * as chalk from 'chalk';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const startTime = Date.now();
const req = context.switchToHttp().getRequest();
const decodedUrl = decodeURIComponent(req.url);
const moduleName = (req as any).moduleName || 'UnknownModule';
return next.handle().pipe(
tap(() => {
const delay = Date.now() - startTime;
const delayStr =
delay > 500 ? chalk.bold.red(`${delay}ms 🚨`) : chalk.magenta(`${delay}ms`);
const message = `Request processed: ${chalk.yellow(req.method)} ${chalk.green(
decodedUrl,
)} ${delayStr}`;
blancLogger.log(message, moduleName);
}),
);
}
}
```
> **전역 인터셉터로 적용하려면 AppModule에 아래와 같이 등록합니다**
```typescript
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './commons/interceptors/logging.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor, // 전역 인터셉터로 등록
},
],
})
export class AppModule {}
```
> **Console Output Log Example**
> _- 응답 시간 (예: 500ms) 초과 시 강조_
<p align="left">
<img src="https://github.com/user-attachments/assets/315ac55c-7f06-44b6-a4bb-66a465186dd3" alt="blanc-logger-output-log" width="600">
</p>
---
### 2. Global Exception Filter 구현
>_**Blanc Logger를 사용하여 예외 발생 시 상세한 에러 정보를 로그로 기록하는 전역 익셉션 필터 예제입니다.**_
>_**BlancLoggerMiddleware**를 통해 설정된 모듈명 정보가 자동으로 포함됩니다._
```typescript
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
InternalServerErrorException,
} from '@nestjs/common';
import { blancLogger } from 'blanc-logger';
import { Request, Response } from 'express';
interface ExceptionResponse {
status: number;
message: string;
stack?: string;
}
/** 예외 객체를 처리하여 상태, 메시지, 스택을 반환하는 함수 */
const handleException = (exception: unknown, _request: Request): ExceptionResponse => {
if (exception instanceof HttpException) {
const status = exception.getStatus();
const res = exception.getResponse();
const message =
typeof res === 'object' && res !== null
? (res as any).message ?? exception.message
: exception.message;
return {
status,
message: `HTTP Exception: ${message}`,
stack: exception instanceof Error ? exception.stack : '',
};
}
if (exception instanceof Error) {
const status = new InternalServerErrorException().getStatus();
return {
status,
message: `Unhandled exception: ${exception.message}`,
stack: exception.stack,
};
}
return { status: 500, message: 'Unknown error' };
};
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const moduleName = (request as any)?.moduleName ?? 'Global';
const { status, message, stack } = handleException(exception, request);
blancLogger.error(message, {
moduleName,
path: request.url,
stack,
});
response.status(status).json({
statusCode: status,
status: message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
```
> **전역 필터로 적용하려면 AppModule에 아래와 같이 등록합니다**
```typescript
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { GlobalExceptionFilter } from './commons/filters/global-exception.filter';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionFilter, // 전역 필터로 등록
},
],
})
export class AppModule {}
```
> **전역 익셉션 필터를 적용한 후, 다음과 같이 예외를 던질 경우**
```typescript
throw new ConflictException('DUPLICATION');
```
> **Console Output Log Example**
<p align="left">
<img src="https://github.com/user-attachments/assets/ac4dc526-bf24-4caf-b598-b83274d43eac" alt="blanc-logger-output-log" width="600">
</p>
> **또한, 동일하게 로그 파일도 생성됩니다.**
```json
{"level":"error","message":"HTTP Exception: DUPLICATION","stack":[{"moduleName":"user","path":"/api/user/profile","stack":"ConflictException: DUPLICATION\n at UserService.getProfile (/path/to/src/user/user.service.ts:60:13)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"}],"timestamp":"2025-03-03 14:29:51"}
```
---
## License
이 프로젝트는 [MIT License](./LICENSE)에 따라 배포 및 사용이 가능합니다.
---