# Blanc Logger 

Advanced Winston logger with TypeORM support & structured logging.
> **Blanc Logger**는 NestJS 및 TypeORM 애플리케이션을 위한 고급 로깅 라이브러리입니다.  
> 이 라이브러리는 **Winston**을 기반으로 콘솔 및 파일 로깅을 지원하며,  
> 자동 구조화된 JSON 포맷, SQL 쿼리 하이라이팅, 모듈 기반 로그 추적 등 다양한 기능을 제공합니다.

[![npm version](https://img.shields.io/npm/v/blanc-logger.svg?style=flat&color=red)](https://www.npmjs.com/package/blanc-logger)
[![Node.js](https://img.shields.io/badge/Node.js-%3E=18.x-brightgreen?style=flat&logo=node.js)](https://nodejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-%3E=4.0-blue?style=flat&logo=typescript)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/github/license/yooseungmo/blanc-logger?color=blue)](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)에 따라 배포 및 사용이 가능합니다.

---
