From fea9fc36f82be1baf1192e85788dc26b0c6f6b67 Mon Sep 17 00:00:00 2001 From: kjjdsa Date: Wed, 19 Jul 2023 22:05:55 +0900 Subject: [PATCH 1/5] =?UTF-8?q?winston=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A1=9C=EA=B1=B0=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.develop.local | 11 +++++ .env.production | 11 +++++ .github/workflows/cicd.yml | 2 +- .gitignore | 7 +-- docker-compose.yml | 2 +- package.json | 6 ++- src/app.module.ts | 2 +- src/logger.ts | 96 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 .env.develop.local create mode 100644 .env.production create mode 100644 src/logger.ts diff --git a/.env.develop.local b/.env.develop.local new file mode 100644 index 0000000..9d3222d --- /dev/null +++ b/.env.develop.local @@ -0,0 +1,11 @@ +DB_HOST=host.docker.internal +DB_PORT=3306 +DB_USER=user +DB_PW=1080 +DB_SCHEMA=animalnest +JWT_SECRET_KEY=secretsecretsecretsecretsecretsecret +CLOUDWATCH_ACCESS_KEY= +CLOUDWATCH_SECRET_ACCESS_KEY= +CLOUDWATCH_REGION= +CLOUDWATCH_GROUP_NAME= +NAME=develop/local \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..3331ec1 --- /dev/null +++ b/.env.production @@ -0,0 +1,11 @@ +DB_HOST=localhost +DB_PORT=3306 +DB_USER=user +DB_PW=1080 +DB_SCHEMA=animalnest +JWT_SECRET_KEY=secretsecretsecretsecretsecretsecret +CLOUDWATCH_ACCESS_KEY= +CLOUDWATCH_SECRET_ACCESS_KEY= +CLOUDWATCH_REGION= +CLOUDWATCH_GROUP_NAME= +NAME=production \ No newline at end of file diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 9139003..0fd25a8 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -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 diff --git a/.gitignore b/.gitignore index 2d3de6f..80bb81b 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/docker-compose.yml b/docker-compose.yml index f2a93b2..09d3a9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - '3000:3000' # 이 컨테이너에서 사용할 다른 컨테이너(docker compose 의 특장점?) env_file: - - .env.production + - .env.develop.local # mysql 먼저 실행하고 nest를 실행하라는 순서 depends_on: - mysql diff --git a/package.json b/package.json index 86fd6fb..46b43c8 100644 --- a/package.json +++ b/package.json @@ -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", @@ -40,13 +41,16 @@ "dotenv": "^16.3.1", "fastify-swagger": "^5.2.0", "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", diff --git a/src/app.module.ts b/src/app.module.ts index e7e30aa..a22cd56 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -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, diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..797ddad --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,96 @@ +import winston from 'winston' +import { + WinstonModule, + utilities as nestWinstonModuleUtilities, +} from 'nest-winston' +import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs"; +import { stringify } from "ts-jest"; +import * as process from "process"; +import { ConfigService } from "@nestjs/config"; + + +const { createLogger, format, transports } = winston +const { combine, timestamp, colorize, printf, simple } = winston.format + +const logFormat = printf(info => { + return `${info.timestamp} [${info.level}] : ${info.message}` +}) + +export default class Logger { + private logger: winston.Logger + private cloudWatchClient: CloudWatchLogsClient + LogGroupName: string + LogStreamName: string + + constructor(/*private readonly env: ConfigService*/) { + + /** ######### 기존 ########## */ + this.logger = createLogger({ + level: 'info', + format: combine( + timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + logFormat, + ) + }) + // 프로덕션인 경우 + // 실제 클라우드워치에 보내는 역할 + if (process.env.NODE_ENV === 'production') { + // @aws-sdk/client-cloudwatch-logs + /** ######### 신규 클라우드워치 ########## */ + this.cloudWatchClient = new CloudWatchLogsClient({ + credentials: { + accessKeyId: process.env.CLOUDWATCH_ACCESS_KEY, + secretAccessKey: process.env.CLOUDWATCH_SECRET_ACCESS_KEY, + }, + region: process.env.CLOUDWATCH_REGION + }) + this.LogGroupName = process.env.CLOUDWATCH_GROUP_NAME + this.LogStreamName = `${process.env.CLOUDWATCH_GROUP_NAME}-${process.env.NODE_ENV}` + + /** ######### 신규 클라우드워치 끝 ########## */ + } else if (process.env.NODE_ENV === 'debug') { + // 프로덕션이 아닌 경우 콘솔에 출력 + this.logger.add(new transports.Console({ + format: combine( + colorize(), + simple() + ), + })) + } + /** ######### 기존 끝 ########## */ + } + + public info(msg: string) { + this.logger.info(msg) + } + public error(errMsg: string) { + this.logger.error(errMsg) + } + // public debug(debugMsg: string) { + // this.logger.error(debugMsg) + // } + // public warn(warn: string) { + // this.logger.error(warnMsg) + // } + + public getRequestLogger() { + return WinstonModule.forRoot({ + transports: [ + new winston.transports.Console( + { + level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', + format: combine( + colorize(), + timestamp(), + nestWinstonModuleUtilities + .format + .nestLike('SampleApp', { prettyPrint: true }), + ), + } + ), + ], + }) + } +} \ No newline at end of file From 05e84063d795f6b97bb17f975468ad87d95897e5 Mon Sep 17 00:00:00 2001 From: kjjdsa Date: Wed, 19 Jul 2023 22:47:18 +0900 Subject: [PATCH 2/5] =?UTF-8?q?env=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20tran?= =?UTF-8?q?sport=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.develop.local | 11 -- .env.production | 11 -- src/cloud-watch-transport.ts | 54 ++++++++++ src/logger.ts | 201 ++++++++++++++++++----------------- src/main.ts | 3 +- 5 files changed, 161 insertions(+), 119 deletions(-) delete mode 100644 .env.develop.local delete mode 100644 .env.production create mode 100644 src/cloud-watch-transport.ts diff --git a/.env.develop.local b/.env.develop.local deleted file mode 100644 index 9d3222d..0000000 --- a/.env.develop.local +++ /dev/null @@ -1,11 +0,0 @@ -DB_HOST=host.docker.internal -DB_PORT=3306 -DB_USER=user -DB_PW=1080 -DB_SCHEMA=animalnest -JWT_SECRET_KEY=secretsecretsecretsecretsecretsecret -CLOUDWATCH_ACCESS_KEY= -CLOUDWATCH_SECRET_ACCESS_KEY= -CLOUDWATCH_REGION= -CLOUDWATCH_GROUP_NAME= -NAME=develop/local \ No newline at end of file diff --git a/.env.production b/.env.production deleted file mode 100644 index 3331ec1..0000000 --- a/.env.production +++ /dev/null @@ -1,11 +0,0 @@ -DB_HOST=localhost -DB_PORT=3306 -DB_USER=user -DB_PW=1080 -DB_SCHEMA=animalnest -JWT_SECRET_KEY=secretsecretsecretsecretsecretsecret -CLOUDWATCH_ACCESS_KEY= -CLOUDWATCH_SECRET_ACCESS_KEY= -CLOUDWATCH_REGION= -CLOUDWATCH_GROUP_NAME= -NAME=production \ No newline at end of file diff --git a/src/cloud-watch-transport.ts b/src/cloud-watch-transport.ts new file mode 100644 index 0000000..6dc7df3 --- /dev/null +++ b/src/cloud-watch-transport.ts @@ -0,0 +1,54 @@ +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() { + 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; + } + + 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(), + ], +}); + +export default logger; \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index 797ddad..8b825ac 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,96 +1,105 @@ -import winston from 'winston' -import { - WinstonModule, - utilities as nestWinstonModuleUtilities, -} from 'nest-winston' -import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs"; -import { stringify } from "ts-jest"; -import * as process from "process"; -import { ConfigService } from "@nestjs/config"; - - -const { createLogger, format, transports } = winston -const { combine, timestamp, colorize, printf, simple } = winston.format - -const logFormat = printf(info => { - return `${info.timestamp} [${info.level}] : ${info.message}` -}) - -export default class Logger { - private logger: winston.Logger - private cloudWatchClient: CloudWatchLogsClient - LogGroupName: string - LogStreamName: string - - constructor(/*private readonly env: ConfigService*/) { - - /** ######### 기존 ########## */ - this.logger = createLogger({ - level: 'info', - format: combine( - timestamp({ - format: 'YYYY-MM-DD HH:mm:ss', - }), - logFormat, - ) - }) - // 프로덕션인 경우 - // 실제 클라우드워치에 보내는 역할 - if (process.env.NODE_ENV === 'production') { - // @aws-sdk/client-cloudwatch-logs - /** ######### 신규 클라우드워치 ########## */ - this.cloudWatchClient = new CloudWatchLogsClient({ - credentials: { - accessKeyId: process.env.CLOUDWATCH_ACCESS_KEY, - secretAccessKey: process.env.CLOUDWATCH_SECRET_ACCESS_KEY, - }, - region: process.env.CLOUDWATCH_REGION - }) - this.LogGroupName = process.env.CLOUDWATCH_GROUP_NAME - this.LogStreamName = `${process.env.CLOUDWATCH_GROUP_NAME}-${process.env.NODE_ENV}` - - /** ######### 신규 클라우드워치 끝 ########## */ - } else if (process.env.NODE_ENV === 'debug') { - // 프로덕션이 아닌 경우 콘솔에 출력 - this.logger.add(new transports.Console({ - format: combine( - colorize(), - simple() - ), - })) - } - /** ######### 기존 끝 ########## */ - } - - public info(msg: string) { - this.logger.info(msg) - } - public error(errMsg: string) { - this.logger.error(errMsg) - } - // public debug(debugMsg: string) { - // this.logger.error(debugMsg) - // } - // public warn(warn: string) { - // this.logger.error(warnMsg) - // } - - public getRequestLogger() { - return WinstonModule.forRoot({ - transports: [ - new winston.transports.Console( - { - level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', - format: combine( - colorize(), - timestamp(), - nestWinstonModuleUtilities - .format - .nestLike('SampleApp', { prettyPrint: true }), - ), - } - ), - ], - }) - } -} \ No newline at end of file +// import winston from 'winston' +// import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston' +// import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs"; +// import * as process from "process"; +// +// +// const { createLogger, format, transports } = winston +// const { combine, timestamp, colorize, printf, simple } = winston.format +// +// const logFormat = printf(info => { +// return `${info.timestamp} [${info.level}] : ${info.message}` +// }) +// +// export default class Logger { +// private logger: winston.Logger +// private cloudWatchClient: CloudWatchLogsClient +// LogGroupName: string +// LogStreamName: string +// +// constructor() { +// +// /** ######### 기존 ########## */ +// this.logger = createLogger({ +// level: 'info', +// format: combine( +// timestamp({ +// format: 'YYYY-MM-DD HH:mm:ss', +// }), +// logFormat, +// ) +// }) +// // 프로덕션인 경우 +// // 실제 클라우드워치에 보내는 역할 +// if (process.env.NODE_ENV === 'production') { +// // @aws-sdk/client-cloudwatch-logs +// /** ######### 신규 클라우드워치 ########## */ +// 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_GROUP_NAME}-${process.env.NODE_ENV}` +// +// /** ######### 신규 클라우드워치 끝 ########## */ +// +// // // timestamp 는 클라우드워치에 도달할 때 찍히므로 별도 설정 필요 x +// // const cloudwatchConfig = { +// // logGroupName: process.env.CLOUDWATCH_GROUP_NAME, +// // logStreamName: `${process.env.CLOUDWATCH_GROUP_NAME}-${process.env.NODE_ENV}`, +// // awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID, +// // awsSecretKey: process.env.AWS_SECRET_ACCESS_KEY, +// // awsRegion: process.env.CLOUDWATCH_REGION, +// // messageFormatter: ({ level, message, additionalInfo }) => +// // `[${level}] : ${message} \nAdditional Info: ${JSON.stringify(additionalInfo)}}` +// // } +// // const cloudWatchHelper = new WinstonCloudwatch(cloudwatchConfig) +// this.logger.add() +// +// } else if (process.env.NODE_ENV === 'debug') { +// // 프로덕션이 아닌 경우 콘솔에 출력 +// this.logger.add(new transports.Console({ +// format: combine( +// colorize(), +// simple() +// ), +// })) +// } +// /** ######### 기존 끝 ########## */ +// } +// +// public info(msg: string) { +// this.logger.info(msg) +// } +// public error(errMsg: string) { +// this.logger.error(errMsg) +// } +// // public debug(debugMsg: string) { +// // this.logger.error(debugMsg) +// // } +// // public warn(warn: string) { +// // this.logger.error(warnMsg) +// // } +// +// public getRequestLogger() { +// return WinstonModule.forRoot({ +// transports: [ +// new winston.transports.Console( +// { +// level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', +// format: combine( +// colorize(), +// timestamp(), +// nestWinstonModuleUtilities +// .format +// .nestLike('SampleApp', { prettyPrint: true }), +// ), +// } +// ), +// ], +// }) +// } +// } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 50c8938..77bf63f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,14 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import logger from "./cloud-watch-transport"; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); const port = process.env.PORT || 3000; - + logger.info('Hello, World!'); const config = new DocumentBuilder() .setTitle('animalNest Api') .setDescription('동물병원 예약 시나리오 개발을 위한 API문서') From 1aa640befd3f1bfb13deba0bf1f6cf79f872ddae Mon Sep 17 00:00:00 2001 From: kjjdsa Date: Thu, 20 Jul 2023 00:02:27 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=EC=BD=98=EC=86=94=20=EB=A1=9C=EA=B9=85=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cloud-watch-transport.ts | 172 ++++++++++++++++++++++---------- src/logger.ts | 187 +++++++++++++++-------------------- src/main.ts | 5 +- src/seeder.ts | 2 + 4 files changed, 205 insertions(+), 161 deletions(-) diff --git a/src/cloud-watch-transport.ts b/src/cloud-watch-transport.ts index 6dc7df3..67ab23f 100644 --- a/src/cloud-watch-transport.ts +++ b/src/cloud-watch-transport.ts @@ -1,54 +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() { - 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; - } - - 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(), - ], -}); - -export default logger; \ No newline at end of file +// /* +// 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; \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index 8b825ac..1809640 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,105 +1,82 @@ -// import winston from 'winston' -// import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston' -// import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs"; -// import * as process from "process"; -// -// -// const { createLogger, format, transports } = winston -// const { combine, timestamp, colorize, printf, simple } = winston.format -// -// const logFormat = printf(info => { -// return `${info.timestamp} [${info.level}] : ${info.message}` -// }) -// -// export default class Logger { -// private logger: winston.Logger -// private cloudWatchClient: CloudWatchLogsClient -// LogGroupName: string -// LogStreamName: string -// -// constructor() { -// -// /** ######### 기존 ########## */ -// this.logger = createLogger({ -// level: 'info', -// format: combine( -// timestamp({ -// format: 'YYYY-MM-DD HH:mm:ss', -// }), -// logFormat, -// ) -// }) -// // 프로덕션인 경우 -// // 실제 클라우드워치에 보내는 역할 -// if (process.env.NODE_ENV === 'production') { -// // @aws-sdk/client-cloudwatch-logs -// /** ######### 신규 클라우드워치 ########## */ -// 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_GROUP_NAME}-${process.env.NODE_ENV}` -// -// /** ######### 신규 클라우드워치 끝 ########## */ -// -// // // timestamp 는 클라우드워치에 도달할 때 찍히므로 별도 설정 필요 x -// // const cloudwatchConfig = { -// // logGroupName: process.env.CLOUDWATCH_GROUP_NAME, -// // logStreamName: `${process.env.CLOUDWATCH_GROUP_NAME}-${process.env.NODE_ENV}`, -// // awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID, -// // awsSecretKey: process.env.AWS_SECRET_ACCESS_KEY, -// // awsRegion: process.env.CLOUDWATCH_REGION, -// // messageFormatter: ({ level, message, additionalInfo }) => -// // `[${level}] : ${message} \nAdditional Info: ${JSON.stringify(additionalInfo)}}` -// // } -// // const cloudWatchHelper = new WinstonCloudwatch(cloudwatchConfig) -// this.logger.add() -// -// } else if (process.env.NODE_ENV === 'debug') { -// // 프로덕션이 아닌 경우 콘솔에 출력 -// this.logger.add(new transports.Console({ -// format: combine( -// colorize(), -// simple() -// ), -// })) -// } -// /** ######### 기존 끝 ########## */ -// } -// -// public info(msg: string) { -// this.logger.info(msg) -// } -// public error(errMsg: string) { -// this.logger.error(errMsg) -// } -// // public debug(debugMsg: string) { -// // this.logger.error(debugMsg) -// // } -// // public warn(warn: string) { -// // this.logger.error(warnMsg) -// // } -// -// public getRequestLogger() { -// return WinstonModule.forRoot({ -// transports: [ -// new winston.transports.Console( -// { -// level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', -// format: combine( -// colorize(), -// timestamp(), -// nestWinstonModuleUtilities -// .format -// .nestLike('SampleApp', { prettyPrint: true }), -// ), -// } -// ), -// ], -// }) -// } -// } \ No newline at end of file +import winston from 'winston' +import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston' +import { CloudWatchLogsClient, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs"; +import * as process from "process"; + + +const { createLogger, format, transports } = winston +const { combine, timestamp, colorize, printf, simple, } = winston.format + + +export default class Logger { + private logger: winston.Logger + private cloudWatchClient: CloudWatchLogsClient + LogGroupName: string + LogStreamName: string + + constructor(private readonly category : string) { + const logFormat = printf(info => { + return `[${info.timestamp}] [${info.level}] [${this.category}] : ${info.message}` + }) + // 프로덕션인 경우 + // 실제 클라우드워치에 보내는 역할 + this.logger = createLogger({ + level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', + format: combine( + colorize(), + timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + logFormat, + ) + }) + this.logger.add(new transports.Console()) + // 프로덕션일경우 추가적으로 클라우드워치 작업 + if (process.env.NODE_ENV === '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}-${process.env.NODE_ENV}` + } + } + + public info(msg: string) { + this.logger.info(msg) + if (process.env.NODE_ENV === "production") { + this.sendToCloudWatch('Info', msg) + } + } + public error(errMsg: string) { + this.logger.error(errMsg) + if (process.env.NODE_ENV === "production") { + this.sendToCloudWatch('Error', errMsg) + } + } + public debug(debugMsg: string) { + this.logger.debug(debugMsg) + } + public warn(warnMsg: string) { + this.logger.warn(warnMsg) + } + + private sendToCloudWatch(level: string, msg: string) { + const logEvents= [ + { + timestamp: new Date().getTime(), + message: `${level}: ${msg}`, + } + ] + const command = new PutLogEventsCommand({ + logGroupName: process.env.CLOUDWATCH_GROUP_NAME, + logStreamName: process.env.CLOUDWATCH_STREAM_NAME, + logEvents + }) + + this.cloudWatchClient.send(command) + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 77bf63f..49baee4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,14 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import logger from "./cloud-watch-transport"; +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; - logger.info('Hello, World!'); const config = new DocumentBuilder() .setTitle('animalNest Api') .setDescription('동물병원 예약 시나리오 개발을 위한 API문서') @@ -19,6 +19,7 @@ async function bootstrap() { await app.listen(port); console.log(`listening on port ${port}`); + logger.debug('Hello, World!!!!! 로그 성공'); if (module.hot) { module.hot.accept(); diff --git a/src/seeder.ts b/src/seeder.ts index 4f133ee..7b18b75 100644 --- a/src/seeder.ts +++ b/src/seeder.ts @@ -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, }), From 49d57c78e0bc514501d783f258a33eef58771636 Mon Sep 17 00:00:00 2001 From: kjjdsa Date: Thu, 20 Jul 2023 00:17:56 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C?= =?UTF-8?q?=EC=9B=8C=EC=B9=98=20=EC=A0=84=EC=86=A1=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/logger.ts | 14 ++++++++------ src/main.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 1809640..33e1e32 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -13,15 +13,18 @@ export default class Logger { private cloudWatchClient: CloudWatchLogsClient LogGroupName: string LogStreamName: string + private is_production = process.env.NODE_ENV === "production"; constructor(private readonly category : string) { const logFormat = printf(info => { return `[${info.timestamp}] [${info.level}] [${this.category}] : ${info.message}` }) - // 프로덕션인 경우 + + this.is_production = true; + // 실제 클라우드워치에 보내는 역할 this.logger = createLogger({ - level: process.env.NODE_ENV === 'production' ? 'info' : 'silly', + level: this.is_production ? 'info' : 'silly', format: combine( colorize(), timestamp({ @@ -32,7 +35,7 @@ export default class Logger { }) this.logger.add(new transports.Console()) // 프로덕션일경우 추가적으로 클라우드워치 작업 - if (process.env.NODE_ENV === 'production') { + if (this.is_production) { this.cloudWatchClient = new CloudWatchLogsClient({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, @@ -47,13 +50,13 @@ export default class Logger { public info(msg: string) { this.logger.info(msg) - if (process.env.NODE_ENV === "production") { + if (this.is_production) { this.sendToCloudWatch('Info', msg) } } public error(errMsg: string) { this.logger.error(errMsg) - if (process.env.NODE_ENV === "production") { + if (this.is_production) { this.sendToCloudWatch('Error', errMsg) } } @@ -76,7 +79,6 @@ export default class Logger { logStreamName: process.env.CLOUDWATCH_STREAM_NAME, logEvents }) - this.cloudWatchClient.send(command) } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 49baee4..5f53475 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,7 +19,7 @@ async function bootstrap() { await app.listen(port); console.log(`listening on port ${port}`); - logger.debug('Hello, World!!!!! 로그 성공'); + logger.info('Hello, World!!!!! 로그 성공'); if (module.hot) { module.hot.accept(); From 67b883436a54a2fe2f4fac5d945325c12752a080 Mon Sep 17 00:00:00 2001 From: kjjdsa Date: Thu, 20 Jul 2023 00:58:43 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C=20?= =?UTF-8?q?=EC=9B=8C=EC=B9=98=EC=97=90=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/logger.ts | 91 +++++++++++++++++++++++++++++++++++---------------- src/main.ts | 2 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 46b43c8..4c92f8a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "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", diff --git a/src/logger.ts b/src/logger.ts index 33e1e32..4af13d0 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,12 +1,12 @@ import winston from 'winston' -import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-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, } = winston.format +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 @@ -16,24 +16,11 @@ export default class Logger { private is_production = process.env.NODE_ENV === "production"; constructor(private readonly category : string) { - const logFormat = printf(info => { - return `[${info.timestamp}] [${info.level}] [${this.category}] : ${info.message}` - }) - - this.is_production = true; - // 실제 클라우드워치에 보내는 역할 this.logger = createLogger({ - level: this.is_production ? 'info' : 'silly', - format: combine( - colorize(), - timestamp({ - format: 'YYYY-MM-DD HH:mm:ss', - }), - logFormat, - ) + level: this.is_production ? 'info' : 'silly' }) - this.logger.add(new transports.Console()) + // 프로덕션일경우 추가적으로 클라우드워치 작업 if (this.is_production) { this.cloudWatchClient = new CloudWatchLogsClient({ @@ -44,39 +31,85 @@ export default class Logger { region: process.env.CLOUDWATCH_REGION }) this.LogGroupName = process.env.CLOUDWATCH_GROUP_NAME - this.LogStreamName = `${process.env.CLOUDWATCH_STREAM_NAME}-${process.env.NODE_ENV}` + 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) { + public info(msg: string, metadata: string = "") { this.logger.info(msg) if (this.is_production) { - this.sendToCloudWatch('Info', msg) + const info = { + timestamp: now, + level: 'info', + category: this.category, + message: msg, + metadata: metadata + } + this.sendToCloudWatch(info) } } - public error(errMsg: string) { + public error(errMsg: string, metadata: string = "") { this.logger.error(errMsg) if (this.is_production) { - this.sendToCloudWatch('Error', errMsg) + const info = { + timestamp: now, + level: 'error', + category: this.category, + message: errMsg, + metadata: metadata + } + this.sendToCloudWatch(info) } } - public debug(debugMsg: string) { + public debug(debugMsg: string, metadata: string = "") { this.logger.debug(debugMsg) } - public warn(warnMsg: string) { + 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(level: string, msg: string) { + private sendToCloudWatch(info) { const logEvents= [ { timestamp: new Date().getTime(), - message: `${level}: ${msg}`, + message: `[${info.timestamp}] [${info.level}] [${info.category}] ${info.metadata !== "" ? "- " + info.metadata : ''} : ${info.message}`, } ] const command = new PutLogEventsCommand({ - logGroupName: process.env.CLOUDWATCH_GROUP_NAME, - logStreamName: process.env.CLOUDWATCH_STREAM_NAME, + logGroupName: this.LogGroupName, + logStreamName: this.LogStreamName, logEvents }) this.cloudWatchClient.send(command) diff --git a/src/main.ts b/src/main.ts index 5f53475..6f360f7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,7 +19,7 @@ async function bootstrap() { await app.listen(port); console.log(`listening on port ${port}`); - logger.info('Hello, World!!!!! 로그 성공'); + logger.info('Hello, World!!!!! 로그 성공', 'MAIN'); if (module.hot) { module.hot.accept();