Skip to content

Commit

Permalink
Feat: simplify logging
Browse files Browse the repository at this point in the history
  • Loading branch information
HC-kang committed Oct 23, 2023
1 parent 08f2707 commit 1d3e4d0
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 81 deletions.
7 changes: 5 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { HttpExceptionFilter } from './common/filters/http.exception.filter';
import { FavoriteModule } from './modules/favorite/favorite.module';
import { CartModule } from './modules/cart/cart.module';
import { HttpLoggerInterceptor } from './common/interceptors';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
// import { PaymentsModule } from './modules/payments/payments.module';

@Module({
Expand All @@ -36,7 +36,10 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
CartModule,
],
providers: [
HttpExceptionFilter,
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: HttpLoggerInterceptor,
Expand Down
8 changes: 5 additions & 3 deletions src/common/filters/http.exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
InternalServerErrorException,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { formattedString } from '../utils';

Check warning on line 11 in src/common/filters/http.exception.filter.ts

View workflow job for this annotation

GitHub Actions / test nestjs

'formattedString' is defined but never used

Check warning on line 11 in src/common/filters/http.exception.filter.ts

View workflow job for this annotation

GitHub Actions / test nestjs

'formattedString' is defined but never used

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
Expand All @@ -27,9 +28,10 @@ export class HttpExceptionFilter implements ExceptionFilter {
params: req.params,
};
this.cLogger.error(
`${req.method} ${req.url} ${JSON.stringify(errorReq, null, 2)}`,
`[FILTERED] ${req.method} ${req.url} \n ${
exception.stack
} \n ${JSON.stringify(errorReq)}`,
);
this.cLogger.error(exception.stack);

const status =
exception instanceof HttpException ? exception.getStatus() : 500;
Expand All @@ -56,6 +58,6 @@ export class HttpExceptionFilter implements ExceptionFilter {
path: req.url,
};

res.status(status).json(response);
return res.status(status).json(response);
}
}
59 changes: 37 additions & 22 deletions src/common/interceptors/http-logger.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { catchError, tap } from 'rxjs/operators';
import { formattedString } from '../utils';

@Injectable()
Expand All @@ -15,32 +15,47 @@ export class HttpLoggerInterceptor implements NestInterceptor {

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();

return next.handle().pipe(
tap(() => {
this.logRequest(context, now);
}),
catchError((error) => {
this.logRequest(context, now, error);
throw error;
}),
);
}

private logRequest(
context: ExecutionContext,
startTime: number,
error?: any,
) {
const httpContext = context.switchToHttp();
const req = httpContext.getRequest();
const res = httpContext.getResponse();
const { method, originalUrl, ip, headers: reqHeaders, body: reqBody } = req;
const { method, originalUrl, ip } = req;
const { statusCode } = res;
const contentLength = res.get('content-length') || 0;
const userAgent = req.get('user-agent') || '';

return next.handle().pipe(
tap(() => {
const { statusCode } = res;
const contentLength = res.get('content-length') || 0;
const userAgent = req.get('user-agent') || '';
const logMessage = `${method} ${originalUrl} ${statusCode} ${contentLength} ${
Date.now() - now
}ms - ${userAgent} ${ip}`;
const duration = Date.now() - startTime;
const logMessage = `${method} ${originalUrl} ${statusCode} ${contentLength} ${duration}ms - ${userAgent} ${ip}`;

if (statusCode >= 500) {
this.cLogger.error(formattedString(logMessage));
} else if (statusCode >= 400) {
this.cLogger.warn(formattedString(logMessage));
} else {
this.cLogger.log(formattedString(logMessage));
}
// 오류 발생 시에는 에러 메세지도 같이 로깅
if (error) {
this.cLogger.error(formattedString(`${logMessage} ${error.message}`));
} else if (statusCode >= 500) {
this.cLogger.error(formattedString(logMessage));
} else if (statusCode >= 400) {
this.cLogger.warn(formattedString(logMessage));
} else {
this.cLogger.log(formattedString(logMessage));
}

// 추가적으로 reqHeaders, reqBody를 로깅하고 싶다면 아래 주석을 해제하세요.
// this.cLogger.verbose(`Headers: ${formattedString(reqHeaders)}`);
// this.cLogger.verbose(`Body: ${formattedString(reqBody)}`);
}),
);
// 추가적으로 reqHeaders, reqBody를 로깅하고 싶다면 아래 주석을 해제하세요.
// this.cLogger.verbose(`Headers: ${formattedString(reqHeaders)}`);
// this.cLogger.verbose(`Body: ${formattedString(reqBody)}`);
}
}
11 changes: 6 additions & 5 deletions src/config/winston.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ const customLogFormat = combine(
printf((aLog) => {
const nestReqId = aLog.alsCtx?.nestReqId;
const nestReqIdStr = nestReqId //
? `${nestReqId}`
? `[${nestReqId}]`
: '';
const stackStr =
aLog.stack && aLog.stack[0] !== undefined //
? ` \n ${aLog.stack}`
: '';
return `[${aLog.timestamp}] [${aLog.level}] [${nestReqIdStr}]: ${aLog.message}${stackStr}`;
return `[${aLog.timestamp}] [${aLog.level}] ${nestReqIdStr}: ${aLog.message}${stackStr}`;
}),
);

Expand All @@ -67,13 +67,14 @@ const cloudwatchConfig = {
region: process.env.AWS_LOG_REGION,
},
},
retentionInDays: 14,
messageFormatter: (aLog) => {
const { level, message, additionalInfo, alsCtx } = aLog;
const nestReqId = alsCtx?.nestReqId;
const nestReqIdStr = nestReqId //
? `${nestReqId}`
: '';
return `[${level}] [${nestReqIdStr}]: ${message} \nAdditional Info: ${JSON.stringify(
? `[${nestReqId}]`
: '[]';
return `[${level}] ${nestReqIdStr}: ${message} \nAdditional Info: ${JSON.stringify(
additionalInfo,
)}`;
},
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ async function bootstrap() {
app.enableVersioning();
app.enableCors({ origin: '*' });

const httpExceptionFilter = app.get(HttpExceptionFilter);
app.useGlobalFilters(httpExceptionFilter);
// const httpExceptionFilter = app.get(HttpExceptionFilter);
// app.useGlobalFilters(httpExceptionFilter);
app.useGlobalInterceptors(new TransformInterceptor());
app.useGlobalPipes(
new ValidationPipe({
Expand Down
64 changes: 17 additions & 47 deletions src/modules/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,71 +17,41 @@ export class ProductsService {
sellerId: number,
productSpec: ProductSpec,
): Promise<ProductModel> {
try {
const productSpecWithStatus = {
...productSpec,
status: PRODUCT_STATUS.PENDING,
};
return await this.repo.create(sellerId, productSpecWithStatus);
} catch (err) {
this.cLogger.error(err);
throw err;
}
const productSpecWithStatus = {
...productSpec,
status: PRODUCT_STATUS.PENDING,
};
return await this.repo.create(sellerId, productSpecWithStatus);
}

async findAll(): Promise<ProductModel[]> {
try {
return await this.repo.all();
} catch (err) {
this.cLogger.error(err);
throw err;
}
return await this.repo.all();
}

async findOne(id: number): Promise<ProductModel> {
try {
return await this.repo.getByProductId(id);
} catch (err) {
this.cLogger.error(err);
throw err;
}
return await this.repo.getByProductId(id);
}

async update(
id: number,
productSpec: Partial<ProductSpec>,
): Promise<ProductModel> {
try {
return await this.repo.update(id, productSpec);
} catch (err) {
this.cLogger.error(err);
throw err;
}
return await this.repo.update(id, productSpec);
}

async remove(id: number): Promise<ProductModel> {
try {
return await this.repo.remove(id);
} catch (err) {
this.cLogger.error(err);
throw err;
}
return await this.repo.remove(id);
}

async subStock(productId: number, quantity: number): Promise<ProductModel> {
try {
const productModel = await this.repo.getByProductId(productId);
if (!productModel) throw new ProductNotFoundException();
if (productModel.stock < quantity) throw new Error('재고가 부족합니다.');
const updatedProductModel = {
...productModel,
stock: productModel.stock - quantity,
};
return await this.repo.update(productId, updatedProductModel);
} catch (err) {
this.cLogger.error(err);
throw err;
}
const productModel = await this.repo.getByProductId(productId);
if (!productModel) throw new ProductNotFoundException();
if (productModel.stock < quantity) throw new Error('재고가 부족합니다.');
const updatedProductModel = {
...productModel,
stock: productModel.stock - quantity,
};
return await this.repo.update(productId, updatedProductModel);
}

async addStock(productId: number, quantity: number): Promise<ProductModel> {
Expand Down

0 comments on commit 1d3e4d0

Please sign in to comment.