Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] logger 적용 #333

Merged
merged 14 commits into from
Dec 2, 2024
5 changes: 5 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.701.0",
"@nestjs/common": "^10.4.6",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.1.1",
"@nestjs/swagger": "^8.0.1",
"@nestjs/throttler": "^6.2.1",
"@nestjs/typeorm": "^10.0.2",
Expand All @@ -39,6 +41,7 @@
"eslint-import-resolver-typescript": "^3.6.3",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.11.3",
"nest-winston": "^1.9.7",
"passport": "^0.7.0",
"passport-github2": "^0.1.12",
"passport-google-oauth20": "^2.0.0",
Expand All @@ -47,6 +50,8 @@
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
14 changes: 12 additions & 2 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { Module } from '@nestjs/common';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';

Check warning on line 3 in apps/api/src/app.module.ts

View workflow job for this annotation

GitHub Actions / check

'ScheduleModule' is defined but never used
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3 사용하지않는 IMPORT 제거부탁드립니다!

import { ThrottlerModule } from '@nestjs/throttler';
import { TypeOrmModule } from '@nestjs/typeorm';

import { TypeOrmConfigService } from '@/config/typeorm.config';

import { AuthModule } from './auth/auth.module';
import { LoggerMiddleware } from './common/log/logger.middleware';
import { LoggerModule } from './common/log/logger.module';
import { NcpModule } from './common/ncp/ncp.module';
import { DashboardModule } from './dashboard/dashboard.module';
import { StreamModule } from './stream/stream.module';
import { TicleModule } from './ticle/ticle.module';
Expand All @@ -32,6 +36,12 @@
useClass: TypeOrmConfigService,
}),
DashboardModule,
LoggerModule,
NcpModule,
],
})
export class AppModule {}
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
2 changes: 1 addition & 1 deletion apps/api/src/common/filter/http-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';

import { ErrorResponse } from '@/response.interface';
import { ErrorResponse } from '@/common/Interceptor/response.interface';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
Expand Down
37 changes: 37 additions & 0 deletions apps/api/src/common/log/logger.batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import path from 'path';

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';

import { LoggerService } from './logger.service';
import { NcpService } from '../ncp/ncp.service';

@Injectable()
export class LogBatchService {
constructor(
private readonly ncpService: NcpService,
private readonly loggerService: LoggerService
) {}

@Cron(CronExpression.EVERY_DAY_AT_1AM)
async uploadLogToObjectStorage() {
const logsDir = path.join(__dirname, '../../../logs');
console.log('logsDir : ', logsDir);
const today = new Date();
today.setHours(today.getHours() + 9);
today.setDate(today.getDate() - 1);

console.log('today : ', today);
const logFileName = `application-${today.toISOString().split('T')[0]}.log`;
const localFilePath = path.join(logsDir, logFileName);

console.log('localFilePath : ', localFilePath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3 : 필요없는 log나 주석은 삭제해주셔도 좋을 것 같아요~!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1:
서버측에 로그 생성시 생성되었다는 것을 알리기 위해 console.log를 사용하신 걸까요??

현재 lint 상에 console.log는 오류가 발생하도록 되어있어 CI/CD시 오류가 발생할 것으로 보입니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 테스트하고 그대로 뒀습니다 삭제하겠습니다~!

try {
const remoteFileName = `logs/${logFileName}`;
const result = await this.ncpService.uploadFile(localFilePath, remoteFileName);
this.loggerService.log(`Log file uploaded successfully: ${result}`, 'logBatchService');
} catch (error) {
this.loggerService.log(`Failed to upload log file: ${error}`, 'logBatchService');
}
}
}
26 changes: 26 additions & 0 deletions apps/api/src/common/log/logger.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

import { LoggerService } from './logger.service';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
constructor(private readonly logger: LoggerService) {}

use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl } = req;
const userAgent = req.headers['user-agent'] || 'Unknown';

res.on('finish', () => {
const { statusCode } = res;
const contentLength = res.get('content-length') || 0;

this.logger.log(
`[${method}] ${originalUrl} - ${statusCode} - ${contentLength} bytes - UserAgent: ${userAgent}`,
'HTTP'
);
});

next();
}
}
17 changes: 17 additions & 0 deletions apps/api/src/common/log/logger.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Global, Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { WinstonModule } from 'nest-winston';

import { WinstonConfig } from '@/config/winston.confing';

import { LogBatchService } from './logger.batch';
import { LoggerService } from './logger.service';
import { NcpModule } from '../ncp/ncp.module';

@Global()
@Module({
imports: [WinstonModule.forRoot(WinstonConfig), ScheduleModule.forRoot(), NcpModule],
providers: [LoggerService, LogBatchService],
exports: [LoggerService],
})
export class LoggerModule {}
28 changes: 28 additions & 0 deletions apps/api/src/common/log/logger.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Inject, Injectable, LoggerService as NestLoggerService } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class LoggerService implements NestLoggerService {
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}

log(message: string, context: string) {
this.logger.info(message, { context });
}

error(message: string, trace: string, context: string) {
this.logger.error(message, { trace, context });
}

warn(message: string, context: string) {
this.logger.warn(message, { context });
}

debug(message: string, context: string) {
this.logger.debug(message, { context });
}

verbose(message: string, context: string) {
this.logger.verbose(message, { context });
}
}
11 changes: 11 additions & 0 deletions apps/api/src/common/ncp/ncp.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';

import { NcpConfig } from '@/config/ncp.config';

import { NcpService } from './ncp.service';

@Module({
providers: [NcpService, NcpConfig],
exports: [NcpService],
})
export class NcpModule {}
42 changes: 42 additions & 0 deletions apps/api/src/common/ncp/ncp.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as fs from 'fs';

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ErrorMessage } from '@repo/types';

import { NcpConfig } from '@/config/ncp.config';

@Injectable()
export class NcpService {
private s3: S3Client;

constructor(
private ncpConfig: NcpConfig,
private configService: ConfigService
) {
this.s3 = ncpConfig.s3Client;
}

async uploadFile(localFilePath: string, remoteFileName: string): Promise<string> {
const bucketName = this.configService.get<string>('NCP_OBJECT_STORAGE_BUCKET');
const endpoint = this.configService.get<string>('NCP_OBJECT_STORAGE_ENDPOINT');

const fileStream = fs.createReadStream(localFilePath);
const params = {
Bucket: bucketName,
Key: remoteFileName,
Body: fileStream,
};

try {
const uploadResponse = await this.s3.send(new PutObjectCommand(params));

Check warning on line 33 in apps/api/src/common/ncp/ncp.service.ts

View workflow job for this annotation

GitHub Actions / check

'uploadResponse' is assigned a value but never used
// console.log('File uploaded:', uploadResponse);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도용


const url = `${endpoint}/${bucketName}/${remoteFileName}`;

Check warning on line 36 in apps/api/src/common/ncp/ncp.service.ts

View workflow job for this annotation

GitHub Actions / check

'url' is assigned a value but never used
return remoteFileName;
} catch (error) {

Check warning on line 38 in apps/api/src/common/ncp/ncp.service.ts

View workflow job for this annotation

GitHub Actions / check

'error' is defined but never used
throw new Error(ErrorMessage.FILE_UPLOAD_FAILED);
}
}
}
File renamed without changes.
25 changes: 25 additions & 0 deletions apps/api/src/config/ncp.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { S3Client } from '@aws-sdk/client-s3';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class NcpConfig {
s3Client: S3Client;

constructor(private configService: ConfigService) {
const accessKeyId = this.configService.get<string>('NCP_ACCESS_KEY');
const secretAccessKey = this.configService.get<string>('NCP_SECRET_KEY');
const region = this.configService.get<string>('NCP_OBJECT_STORAGE_REGION');
const endpoint = this.configService.get<string>('NCP_OBJECT_STORAGE_ENDPOINT');

this.s3Client = new S3Client({
region: region,
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
},
endpoint: endpoint,
forcePathStyle: true,
});
}
}
26 changes: 26 additions & 0 deletions apps/api/src/config/winston.confing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from 'path';

import * as winston from 'winston';
import 'winston-daily-rotate-file';

export const WinstonConfig = {
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.colorize(),
winston.format.printf(({ level, message, timestamp }) => {
return `[${timestamp}] ${level}: ${message}`;
})
),
}),
new winston.transports.DailyRotateFile({
dirname: path.join(__dirname, '../../logs'),
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
}),
],
};
2 changes: 1 addition & 1 deletion apps/api/src/dashboard/dashboard.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { JwtAuthGuard } from '@/auth/jwt/jwt-auth.guard';
import { GetUserId } from '@/common/decorator/get-userId.decorator';
import { ZodValidationPipe } from '@/zodValidationPipe';
import { ZodValidationPipe } from '@/common/pipe/zodValidationPipe';

import { DashboardService } from './dashboard.service';
import { GetDashboardListQueryDto } from './dto/getDashboardListQueryDto';
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { AppModule } from '@/app.module';

import { DBExceptionFilter } from './common/filter/db-exception.filter';
import { HttpExceptionFilter } from './common/filter/http-exception.filter';
import { ResponseInterceptor } from './response.interceptor';
import { ResponseInterceptor } from './common/Interceptor/response.interceptor';
import { LoggerService } from './common/log/logger.service';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
Expand All @@ -30,6 +31,7 @@ async function bootstrap() {
})
);

app.useLogger(app.get(LoggerService));
app.useGlobalInterceptors(new ResponseInterceptor());
app.useGlobalFilters(new HttpExceptionFilter(), new DBExceptionFilter());
app.setGlobalPrefix('api');
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/ticle/ticle.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CreateTicleSchema } from '@repo/types';

import { JwtAuthGuard } from '@/auth/jwt/jwt-auth.guard';
import { GetUserId } from '@/common/decorator/get-userId.decorator';
import { ZodValidationPipe } from '@/zodValidationPipe';
import { ZodValidationPipe } from '@/common/pipe/zodValidationPipe';

import { CreateTicleDto } from './dto/createTicleDto';
import { GetTicleListQueryDto } from './dto/getTicleListQueryDto';
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ErrorMessage = {
ROOM_NOT_FOUND: '방이 존재하지 않습니다',
TRANSPORT_NOT_FOUND: 'transport가 존재하지 않습니다',
PEER_ALREADY_EXISTS_IN_ROOM: '이미 방에 존재하는 Peer입니다',
FILE_UPLOAD_FAILED: '파일 업로드에 실패했습니다',
} as const;

export type ErrorMessage = (typeof ErrorMessage)[keyof typeof ErrorMessage];
Loading
Loading