Skip to content

yooseungmo/blanc-logger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

14 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Blanc Logger

Advanced Winston logger with TypeORM support & structured logging.

Blanc LoggerλŠ” NestJS 및 TypeORM μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μœ„ν•œ κ³ κΈ‰ λ‘œκΉ… λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€.
이 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” Winston을 기반으둜 μ½˜μ†” 및 파일 λ‘œκΉ…μ„ μ§€μ›ν•˜λ©°,
μžλ™ κ΅¬μ‘°ν™”λœ JSON 포맷, SQL 쿼리 ν•˜μ΄λΌμ΄νŒ…, λͺ¨λ“ˆ 기반 둜그 좔적 λ“± λ‹€μ–‘ν•œ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

npm version Node.js TypeScript License: MIT


Installation

npm install blanc-logger

Usage

1. NestJS μ „μ—­ Logger 적용 (main.ts)

Blanc Loggerλ₯Ό μ „μ—­ 둜거둜 μ μš©ν•˜λ©΄, μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ „μ²΄μ—μ„œ λ™μΌν•œ λ‘œκΉ… 섀정을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

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 쿼리 및 λ°μ΄ν„°λ² μ΄μŠ€ κ΄€λ ¨ 둜그λ₯Ό 효과적으둜 기둝할 수 μžˆμŠ΅λ‹ˆλ‹€.

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. κΈ°μ‘΄ μ½”λ“œ

// κΈ°μ‘΄ NestJS λ‚΄μž₯ 둜거 μ‚¬μš©:
this.logger.error('Error message', error.stack);
this.logger.log('Log message');

2. Blanc Logger μ‚¬μš©

// μ—λŸ¬ λ°œμƒ μ‹œ, μŠ€νƒ 정보와 ν•¨κ»˜ 기둝
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 폴더 μ•„λž˜μ— λ‹€μŒκ³Ό 같이 μŒ“μž…λ‹ˆλ‹€.

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 파일):

{"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) - μ½˜μ†” 좜λ ₯κ³Ό 파일 μ €μž₯을 λ™μ‹œμ— 지원
- 일정 크기(예: 20MB) λ˜λŠ” 일정 μ£ΌκΈ°(예: 15일)에 따라 μžλ™μœΌλ‘œ 둜그 파일 νšŒμ „
- 둜그 파일 μ˜ˆμ‹œ: combined-YYYY-MM-DD.log, error-YYYY-MM-DD.log, exceptions-YYYY-MM-DD.log, rejections-YYYY-MM-DD.log
κ΅¬μ‘°ν™”λœ 둜그 좜λ ₯ (Structured Logging) - UUIDv5λ₯Ό μ‚¬μš©ν•΄ 고유 LogID 생성
- {timestamp, level, message} ν˜•μ‹μ˜ ν‘œμ€€ JSON 포맷으둜 좜λ ₯λ˜μ–΄ μ™ΈλΆ€ μ‹œμŠ€ν…œ 연동 용이
λͺ¨λ“ˆλ³„ μ»¨ν…μŠ€νŠΈ 좔적 (Contextual Tracing) - BlancLoggerMiddlewareλ₯Ό ν™œμš©ν•˜μ—¬ HTTP μš”μ²­μ—μ„œ λͺ¨λ“ˆλͺ… μžλ™ μΆ”μΆœ
- [UserService β†’ AuthModule β†’ Subsystem]κ³Ό 같이 계측 ꡬ쑰둜 ν‘œμ‹œ
동적 둜그 레벨 관리 (Dynamic Log Levels) - 개발 및 운영 ν™˜κ²½μ— 따라 debug, verbose, info, warn, error 둜그 λ ˆλ²¨μ„ μ‹€μ‹œκ°„μœΌλ‘œ 쑰절 κ°€λŠ₯
- ν•„μš”ν•œ μ •λ³΄λ§Œ 선별 κΈ°λ‘ν•˜μ—¬ 둜그의 가독성과 관리 νš¨μœ¨μ„±μ„ ν–₯상
SQL ꡬ문 ν•˜μ΄λΌμ΄νŒ… (SQL Syntax Highlighting) - SQL 쿼리의 ν‚€μ›Œλ“œ, κ°’, ν…Œμ΄λΈ”λͺ…을 색상 κ°•μ‘° 및 λ“€μ—¬μ“°κΈ°λ‘œ ꡬ뢄
- νŒŒλΌλ―Έν„° κ°’ μ»¨ν…μŠ€νŠΈ 인식 κ°•μ‘°
쿼리 μ„±λŠ₯ 뢄석 (Query Analysis) - SQL μ„±λŠ₯ μ €ν•˜ 유발 νŒ¨ν„΄ μžλ™ 감지 (SELECT *, JOIN 쑰건 λˆ„λ½ λ“±)
μ—λŸ¬ 진단 및 μŠ€νƒ 좔적 (Error Diagnostics) - μ—λŸ¬ λ°œμƒ μ‹œ 닀쀑 μŠ€νƒ λ ˆμ΄μ–΄μ™€ μΆ”κ°€ 메타데이터λ₯Ό ν¬ν•¨ν•˜μ—¬ μƒμ„Έν•œ μ—λŸ¬ 둜그 기둝
μ„±λŠ₯ λͺ¨λ‹ˆν„°λ§ (Performance Monitoring) - HTTP μš”μ²­ 처리 μ‹œκ°„ 및 Slow Query(예: 500ms) κ²½κ³  둜그 생성
- μ‹€ν–‰ κ³„νš(Explain Plan) 리포트 μ‹œκ°ν™”
μ»€μŠ€ν„°λ§ˆμ΄μ§• (Customization) - logger-config.yamlνŒŒμΌμ„ 톡해 둜그 μ €μž₯ 경둜, 둜그 레벨, 파일 크기, νšŒμ „ μ£ΌκΈ° 등을 μ‰½κ²Œ μ‘°μ • κ°€λŠ₯

Configuration

Blanc LoggerλŠ” κΈ°λ³Έ 섀정을 μ œκ³΅ν•˜μ§€λ§Œ, ν•„μš”μ— 따라 μ‚¬μš©μž ν™˜κ²½μ— 맞게 μ»€μŠ€ν„°λ§ˆμ΄μ§• ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
섀정을 λ³€κ²½ν•˜λ €λ©΄ ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— logger-config.yaml νŒŒμΌμ„ μƒμ„±ν•˜κ³  μ•„λž˜μ™€ 같이 Overrideν•  수 μžˆμŠ΅λ‹ˆλ‹€.

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 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

blanc-logger-output-log


Console Error Log Example

blanc-logger-error-log


Using Examples

1. Logging Interceptor κ΅¬ν˜„

HTTP μš”μ²­ 처리 μ‹œκ°„μ„ μΈ‘μ •ν•˜μ—¬ 둜그둜 κΈ°λ‘ν•˜λŠ” 인터셉터 μ˜ˆμ œμž…λ‹ˆλ‹€.
BlancLoggerMiddlewareλ₯Ό 톡해 μ„€μ •λœ λͺ¨λ“ˆλͺ… 정보가 μžλ™μœΌλ‘œ ν¬ν•¨λ©λ‹ˆλ‹€.

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에 μ•„λž˜μ™€ 같이 λ“±λ‘ν•©λ‹ˆλ‹€

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) 초과 μ‹œ κ°•μ‘°

blanc-logger-output-log


2. Global Exception Filter κ΅¬ν˜„

Blanc Loggerλ₯Ό μ‚¬μš©ν•˜μ—¬ μ˜ˆμ™Έ λ°œμƒ μ‹œ μƒμ„Έν•œ μ—λŸ¬ 정보λ₯Ό 둜그둜 κΈ°λ‘ν•˜λŠ” μ „μ—­ μ΅μ…‰μ…˜ ν•„ν„° μ˜ˆμ œμž…λ‹ˆλ‹€.
BlancLoggerMiddlewareλ₯Ό 톡해 μ„€μ •λœ λͺ¨λ“ˆλͺ… 정보가 μžλ™μœΌλ‘œ ν¬ν•¨λ©λ‹ˆλ‹€.

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에 μ•„λž˜μ™€ 같이 λ“±λ‘ν•©λ‹ˆλ‹€

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 {}

μ „μ—­ μ΅μ…‰μ…˜ ν•„ν„°λ₯Ό μ μš©ν•œ ν›„, λ‹€μŒκ³Ό 같이 μ˜ˆμ™Έλ₯Ό 던질 경우

throw new ConflictException('DUPLICATION');

Console Output Log Example

blanc-logger-output-log

λ˜ν•œ, λ™μΌν•˜κ²Œ 둜그 νŒŒμΌλ„ μƒμ„±λ©λ‹ˆλ‹€.

{"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에 따라 배포 및 μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.


About

Advanced Winston logger for NestJS & TypeORM with structured logging (npm package).

Resources

License

Stars

Watchers

Forks

Packages

No packages published