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.700.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
13 changes: 11 additions & 2 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Module } from '@nestjs/common';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
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 +35,12 @@ import { UserModule } from './user/user.module';
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
33 changes: 33 additions & 0 deletions apps/api/src/common/log/logger.batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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');
const today = new Date();
today.setHours(today.getHours() + 9);
today.setDate(today.getDate() - 1);

const logFileName = `application-${today.toISOString().split('T')[0]}.log`;
const localFilePath = path.join(logsDir, logFileName);
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 {}
40 changes: 40 additions & 0 deletions apps/api/src/common/ncp/ncp.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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
const url = `${endpoint}/${bucketName}/${remoteFileName}`;

Check warning on line 34 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 36 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 Expand Up @@ -46,5 +46,5 @@
}

@Post('join')
joinTicle(@Param('ticleId') ticleId: number) {}

Check warning on line 49 in apps/api/src/dashboard/dashboard.controller.ts

View workflow job for this annotation

GitHub Actions / check

'ticleId' is defined but never used
}
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
Loading
Loading