9장: 설정 및 로깅 - 애플리케이션 환경 관리하기
프로덕션 수준의 애플리케이션은 다양한 환경(개발, 스테이징, 프로덕션)에서 동작해야 하며, 각 환경에 맞는 설정 값을 가져야 합니다. 또한, 애플리케이션의 동작 상태를 추적하고 디버깅하기 위한 로깅(Logging) 시스템도 필수적입니다. NestJS에서 이를 효과적으로 관리하는 방법을 배웁니다.
1. 설정 관리 (@nestjs/config)
@nestjs/config는 NestJS 공식 설정 관리 모듈로, .env 파일이나 환경 변수를 애플리케이션에서 쉽게 사용할 수 있도록 도와줍니다.
라이브러리 설치:
npm install @nestjs/config.env파일 생성: 프로젝트 루트에.env파일을 만들고 환경별 설정 값을 정의합니다..gitignore에.env를 추가하는 것을 잊지 마세요.# .env NODE_ENV=development PORT=3000 DB_HOST=localhost DB_PORT=5432 DB_USERNAME=myuser DB_PASSWORD=mypassword DB_DATABASE=mydb JWT_SECRET=my-super-secret-key-for-devConfigModule설정 (app.module.ts): 루트 모듈에ConfigModule.forRoot()를 등록합니다.// src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; // ... @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // ConfigModule을 전역 모듈로 설정 envFilePath: `.env.${process.env.NODE_ENV}` // NODE_ENV 값에 따라 .env.development, .env.production 등을 로드 (선택적) }), // ... 다른 모듈들 ], controllers: [AppController], // ... }) export class AppModule {}isGlobal: true로 설정하면, 다른 모듈에서ConfigModule을import하지 않아도ConfigService를 주입받아 사용할 수 있습니다.
ConfigService사용: 서비스나 다른 프로바이더에서ConfigService를 주입받아 환경 변수 값을 가져옵니다.// src/app.service.ts import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class AppService { constructor(private configService: ConfigService) { // get<T>() 메소드로 타입 추론과 함께 값을 가져옴 const dbHost = this.configService.get<string>('DB_HOST'); console.log('Database Host:', dbHost); } getHello(): string { const port = this.configService.get('PORT'); return `Hello World! I am running on port ${port}`; } }TypeORM과 연동:
TypeOrmModule.forRootAsync를 사용하면ConfigService를 주입받아 동적으로 데이터베이스 연결 설정을 구성할 수 있습니다.// src/app.module.ts TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ type: 'postgres', host: configService.get<string>('DB_HOST'), port: configService.get<number>('DB_PORT'), // ... 등등 }), inject: [ConfigService], }),
2. 로깅 (Logging)
NestJS는 console과 유사한 인터페이스를 가진 내장 로거(Logger)를 제공합니다. 이 로거는 로그 레벨(log, error, warn, debug, verbose)을 지원하며, 로그 메시지에 컨텍스트와 타임스탬프를 추가해줍니다.
예제 1: 내장 Logger 사용하기
// src/users/users.service.ts
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class UsersService {
// Logger 인스턴스를 생성하고, 컨텍스트(context)를 전달
private readonly logger = new Logger(UsersService.name);
findAll() {
// 각 로그 레벨에 맞는 메소드 사용
this.logger.log('Finding all users...');
try {
// ... 로직 ...
this.logger.verbose('User search completed successfully.');
return [];
} catch (error) {
this.logger.error('Failed to find users', error.stack);
throw error;
}
}
}
- 로그 출력:
[Nest] 12345 - 01/01/2024, 12:00:00 PM LOG [UsersService] Finding all users...와 같은 형식으로 출력됩니다.
예제 2: 커스텀 로거 만들기
LoggerService 인터페이스를 구현하여 자신만의 커스텀 로거를 만들 수 있습니다. 이를 통해 로그를 파일에 저장하거나, 외부 로깅 서비스(예: Sentry, Datadog)로 전송할 수 있습니다.
// src/common/logging/my-logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
@Injectable()
export class MyLogger implements LoggerService {
log(message: string, context: string) {
console.log(`[${context}] - ${message}`);
}
error(message: string, trace: string, context: string) {
console.error(`[${context}] - ${message}`, trace);
}
warn(message: string, context: string) {
console.warn(`[${context}] - ${message}`);
}
// ... debug, verbose 등 구현
}
main.ts에서 커스텀 로거 적용:// src/main.ts async function bootstrap() { const app = await NestFactory.create(AppModule, { // NestJS의 기본 로거 대신 커스텀 로거를 사용하도록 설정 logger: new MyLogger(), }); await app.listen(3000); }
3. 연습 문제
문제 1: 유효성 검사를 통한 설정 관리
- 요구사항:
@nestjs/config가.env파일을 로드할 때, 필수 환경 변수가 존재하는지, 그리고 올바른 타입인지 검증하는 로직을 추가하세요. - 세부사항:
joi라이브러리를 설치합니다. (npm install joi)ConfigModule.forRoot()의 옵션 객체에validationSchema속성을 추가합니다.Joi.object({ ... })를 사용하여 스키마를 정의합니다. 예를 들어NODE_ENV는'development','production'중 하나여야 하고,PORT는 필수 숫자여야 한다는 규칙을 정의합니다.
- 장점: 필수 환경 변수가 누락된 채로 애플리케이션이 실행되는 것을 방지하여, 런타임 오류를 미리 막을 수 있습니다.
문제 1 정답 예시
// src/app.module.ts
import * as Joi from 'joi';
// ...
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
JWT_SECRET: Joi.string().required(),
}),
}),
// ...
],
})
export class AppModule {}
문제 2: 요청 ID 로깅하기
- 요구사항: 모든 요청에 고유한 ID를 부여하고, 해당 요청과 관련된 모든 로그에 이 ID가 포함되도록 로깅 시스템을 개선하세요.
- 세부사항:
- 모든 요청을 처리하는 미들웨어를 만듭니다.
- 미들웨어에서
uuid같은 라이브러리(npm install uuid)를 사용하여 고유한 요청 ID를 생성하고,req.id = generatedId와 같이 요청 객체에 저장합니다. - 요청 스코프(Request Scope)를 가진 로거 서비스를 만듭니다. 이 서비스는 생성될 때
REQUEST객체를 주입받아 요청 ID를 멤버 변수로 저장합니다. - 이제 이 로거를 사용하여 로그를 남기면, 모든 로그 메시지에 해당 요청의 고유 ID를 함께 출력할 수 있습니다.
- 장점: 분산 시스템 환경이나 비동기 처리가 많은 복잡한 요청의 흐름을 추적하고 디버깅하는 데 매우 유용합니다.
문제 2 힌트
// 1. 요청 ID를 주입하는 미들웨어
// ...
import { v4 as uuidv4 } from 'uuid';
// ...
app.use((req, res, next) => {
req.id = uuidv4();
next();
});
// 2. 요청 스코프 로거
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedLogger {
constructor(@Inject(REQUEST) private readonly request: Request) {}
log(message: string) {
console.log(`[${this.request.id}] - ${message}`);
}
}
'백엔드 > 네스트' 카테고리의 다른 글
| [NestJS] 10장: 테스팅 - 견고한 애플리케이션 만들기 (0) | 2025.09.19 |
|---|---|
| [NestJS] 8장: 인증 - 사용자를 확인하고 보호하기 (0) | 2025.09.19 |
| [NestJS] 7장: 실전 프로젝트 - Todo 리스트 API 서버 만들기 (0) | 2025.09.19 |
| [NestJS] 6장: 데이터베이스 연동 (with TypeORM) (0) | 2025.09.19 |
| [NestJS] 5장: 미들웨어, 파이프, 가드 - 요청 처리의 수문장들 (0) | 2025.09.19 |
| [NestJS] 4장: 모듈 - 코드의 체계적인 정리와 캡슐화 (0) | 2025.09.19 |
| [NestJS] 3장: 프로바이더와 의존성 주입 - 로직의 분리와 재사용 (0) | 2025.09.18 |
| [NestJS] 2장: 컨트롤러와 라우팅 - 요청을 받아들이는 관문 (0) | 2025.09.18 |