Skip to content

Commit

Permalink
Merge pull request #42 from plus-tdd/feature/add_logger
Browse files Browse the repository at this point in the history
winston + aws sdk cloudwatch log 사용하여 로깅 컨벤션 적용
  • Loading branch information
KJJDSA authored Jul 19, 2023
2 parents 9dc0411 + 67b8834 commit a172dc2
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Create .env.production file
- name: Create .env.develop.production file
run: |
echo "DB_HOST=${{ secrets.DB_HOST }}" >> .env.production
echo "DB_PORT=${{ secrets.DB_PORT }}" >> .env.production
Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ lerna-debug.log*
!.vscode/launch.json
!.vscode/extensions.json

#.env
.env
.env.*
#.env.develop
.env.develop
.env.develop.local
.env.production

./src/__tests__/reservation/*

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- '3000:3000'
# 이 컨테이너에서 사용할 다른 컨테이너(docker compose 의 특장점?)
env_file:
- .env.production
- .env.develop.local
# mysql 먼저 실행하고 nest를 실행하라는 순서
depends_on:
- mysql
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"seed": "ts-node src/seeder.ts"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.370.0",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.0.0",
Expand All @@ -39,14 +40,18 @@
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"fastify-swagger": "^5.2.0",
"moment": "^2.29.4",
"mysql2": "^3.4.1",
"nest-winston": "^1.9.3",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"swagger-ui-express": "^4.6.3",
"typeorm": "^0.3.17"
"typeorm": "^0.3.17",
"winston": "^3.10.0",
"winston-cloudwatch": "^6.1.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { DoctorEntity } from './module/doctor/data/doctor.entity';
password: process.env.DB_PW,
database: process.env.DB_SCHEMA,
synchronize: true,
dropSchema: false,
dropSchema: true,
entities: [
CounselingEntity,
PetEntity,
Expand Down
118 changes: 118 additions & 0 deletions src/cloud-watch-transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// /*
// import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
// import winston from 'winston';
// import * as process from "process";
//
// export class CloudWatchTransport extends winston.transports.Stream {
// private cloudwatchLogs: CloudWatchLogsClient;
// private readonly logGroupName: string;
// private readonly logStreamName: string;
// constructor(options: { stream?: any }) {
// super();
// this.cloudwatchLogs = new CloudWatchLogsClient({
// credentials: {
// accessKeyId: process.env.AWS_ACCESS_KEY_ID,
// secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
// },
// region: process.env.CLOUDWATCH_REGION
// });
// this.logGroupName = process.env.CLOUDWATCH_GROUP_NAME;
// this.logStreamName = process.env.CLOUDWATCH_STREAM_NAME;
// this.stream = options.stream || process.stdout;
// }
//
// async log(info, callback) {
// setImmediate(() => this.emit('logged', info));
//
// const { level, message, ...meta } = info;
//
// const params = {
// logEvents: [
// {
// message: `${level}: ${message} ${JSON.stringify(meta)}`,
// timestamp: new Date().getTime()
// }
// ],
// logGroupName: this.logGroupName,
// logStreamName: this.logStreamName,
// // TODO: sequenceToken should be retrieved and managed.
// };
//
// try {
// await this.cloudwatchLogs.send(new PutLogEventsCommand(params));
// callback();
// } catch (error) {
// callback(error);
// }
// }
// }
//
// const logger = winston.createLogger({
// transports: [
// new CloudWatchTransport({ stream: process.stdout }),
// ],
// });
//
// export default logger;*/
// import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
// import * as winston from 'winston';
//
// export class CloudWatchTransport extends winston.transports.Stream {
// private cloudwatchLogs: CloudWatchLogsClient;
// private readonly logGroupName: string;
// private readonly logStreamName: string;
// private sequenceToken: string;
//
// constructor(options: { stream?: any }) {
// super();
// this.cloudwatchLogs = new CloudWatchLogsClient({
// credentials: {
// accessKeyId: process.env.AWS_ACCESS_KEY_ID,
// secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
// },
// region: process.env.CLOUDWATCH_REGION
// });
// this.logGroupName = process.env.CLOUDWATCH_GROUP_NAME;
// this.logStreamName = process.env.CLOUDWATCH_STREAM_NAME;
// this.sequenceToken = null;
// this.stream = options.stream || new Stream();
// }
//
// async log(info, callback) {
// setImmediate(() => this.emit('logged', info));
//
// const { level, message, ...meta } = info;
//
// const params = {
// logEvents: [
// {
// message: `${level}: ${message} ${JSON.stringify(meta)}`,
// timestamp: new Date().getTime()
// }
// ],
// logGroupName: this.logGroupName,
// logStreamName: this.logStreamName,
// sequenceToken: this.sequenceToken,
// };
//
// try {
// await this.cloudwatchLogs.send(new PutLogEventsCommand(params));
// this.sequenceToken = params.nextSequenceToken;
// callback();
// } catch (error) {
// callback(error);
// }
// }
// }
// class Stream {
// public write(message: string): void {
// console.log(message);
// }
// }
// const logger = winston.createLogger({
// transports: [
// new CloudWatchTransport({ stream: new Stream() }),
// ],
// });
//
// export default logger;
117 changes: 117 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import winston from 'winston'
import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
import * as process from "process";
import moment from 'moment';

const { createLogger, format, transports } = winston
const { combine, timestamp, colorize, printf, simple, json ,logstash} = winston.format

const now = moment().format("YYYY-MM-DD HH:mm:ss");

export default class Logger {
private logger: winston.Logger
private cloudWatchClient: CloudWatchLogsClient
LogGroupName: string
LogStreamName: string
private is_production = process.env.NODE_ENV === "production";

constructor(private readonly category : string) {
// 실제 클라우드워치에 보내는 역할
this.logger = createLogger({
level: this.is_production ? 'info' : 'silly'
})

// 프로덕션일경우 추가적으로 클라우드워치 작업
if (this.is_production) {
this.cloudWatchClient = new CloudWatchLogsClient({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
region: process.env.CLOUDWATCH_REGION
})
this.LogGroupName = process.env.CLOUDWATCH_GROUP_NAME
this.LogStreamName = process.env.CLOUDWATCH_STREAM_NAME

this.logger.add(new transports.Console({
format: combine(
colorize(),
timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
printf(info => {
return `[${info.timestamp}] [${info.level}] [${this.category}] : ${info.message}`
}),
)}))
} else {
this.logger.add(new transports.Console({
format: combine(
colorize(),
timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
printf(info => {
return `[${info.timestamp}] [${info.level}] [${this.category}] : ${info.message}`
}),
)}))
}
}

public info(msg: string, metadata: string = "") {
this.logger.info(msg)
if (this.is_production) {
const info = {
timestamp: now,
level: 'info',
category: this.category,
message: msg,
metadata: metadata
}
this.sendToCloudWatch(info)
}
}
public error(errMsg: string, metadata: string = "") {
this.logger.error(errMsg)
if (this.is_production) {
const info = {
timestamp: now,
level: 'error',
category: this.category,
message: errMsg,
metadata: metadata
}
this.sendToCloudWatch(info)
}
}
public debug(debugMsg: string, metadata: string = "") {
this.logger.debug(debugMsg)
}
public warn(warnMsg: string, metadata: string = "") {
this.logger.warn(warnMsg)
if (this.is_production) {
const info = {
timestamp: now,
level: 'debug',
category: this.category,
message: warnMsg,
metadata: metadata
}
this.sendToCloudWatch(info)
}
}

private sendToCloudWatch(info) {
const logEvents= [
{
timestamp: new Date().getTime(),
message: `[${info.timestamp}] [${info.level}] [${info.category}] ${info.metadata !== "" ? "- " + info.metadata : ''} : ${info.message}`,
}
]
const command = new PutLogEventsCommand({
logGroupName: this.LogGroupName,
logStreamName: this.LogStreamName,
logEvents
})
this.cloudWatchClient.send(command)
}
}
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import Logger from "./logger";

declare const module: any;

async function bootstrap() {
const logger = new Logger('application.main');
const app = await NestFactory.create(AppModule);
const port = process.env.PORT || 3000;

const config = new DocumentBuilder()
.setTitle('animalNest Api')
.setDescription('동물병원 예약 시나리오 개발을 위한 API문서')
Expand All @@ -18,6 +19,7 @@ async function bootstrap() {

await app.listen(port);
console.log(`listening on port ${port}`);
logger.info('Hello, World!!!!! 로그 성공', 'MAIN');

if (module.hot) {
module.hot.accept();
Expand Down
2 changes: 2 additions & 0 deletions src/seeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { PaymentEntity } from './module/payment/data/payment.entity';
import { PetEntity } from './module/pet/data/pet.entity';
import { PetSeeder } from './module/pet/pet.seeder';
import { PetModule } from './module/pet/pet.module';
import Logger from "./logger";

seeder({
imports: [
Logger,
ConfigModule.forRoot({
isGlobal: true,
}),
Expand Down

0 comments on commit a172dc2

Please sign in to comment.