반응형
1. 현재 Logger 관련 구성 체크
- common/middlewares/logger.middleware 존재
use(req: Request, res: Response, next: NextFunction) {
res.on('finish', () => {
this.logger.log(
`${req.ip}, ${req.originalUrl}, ${req.method}, ${res.statusCode}`,
);
});
next();
}
2. Cloud Watch 와 API Logger 구성에 대한 고찰
- 현재 미들웨어에서 구성을 하려고 작업 진행중 - 현재 시스템에서 많은 변화를 주는 건 부담이 될듯하여, 기존 미들웨어에서 cloudwatch로 Log 보내느 것을 구현하려고함.
- custom Log 기준
- debug
- verbose
- log
- Warning
- Error
- 개발환경 로그와 라이브 환경의 로그 분리에 대한 고민
3. 개발 프로세스
1) Cloudwatch + log 테스트 연동
- 로그 연동은 성공, 스트림을 설정하지 않고, 로그 보낼시 스트림 에러, 스트림 생성 후 작업 로직 추가 필요
constructor() {
this.cloudWatchLogs = new CloudWatchLogs({
region: 'ap-northeast-2',
accessKeyId : process.env.AWS_CW_ACCESS_KEY,
secretAccessKey : process.env.AWS_CW_SECRET_KEY,
});
this.logGroupName = "babayo-api-logs"
this.logGroupName = "babayo-api-logs"
}
const params = {
logGroupName : "babayo-api-logs",
logStreamName : "test",
logEvents: [
{
message: "연결 성공",
timestamp: Date.now(),
},
],
}
this.cloudWatchLogs.putLogEvents(params).promise();
2) 로그스트림과 로그 생성
- 로그를 생성하기 위해선 로그스트림이라는 것이 먼저 생성되어야함.
// 로그 스트림 생성 기준
// 날짜 기준으로 하루에 하나에 로그스트림을 생성하고, 그 안에 로그를 쌓는 방식으로 진행하려고함.
// 처음 API가 구동 시 로그 스트림 체크를 위해 만들어져있는 로그 스트림을 최신 한개를 가져와 로그스트림의 값과 비교하고 그 이후 전역변수에 담아 체크는 하되, AWS에서 로그 스트림을 가져오는 행위는 하루에 한번 혹은 최초 구동시 한번만 사용가능하게 하기위한 로직을 추가
// 현재 날짜의 스트림이 있는지 체크
// 최초 서버가 실행될때만 체크하기 위한 로직 추가
async cloudWatchStreamCheck() {
try {
return await this.cloudWatchLogs.describeLogStreams({
logGroupName : "babayo-api-logs",
descending : true,
orderBy: "LogStreamName",
limit : 1
}).promise().then((data) => {
let logStreams = data.logStreams.length != 0 ? data.logStreams[0].logStreamName : ""
return logStreams;
}).catch(err => {
console.log(err)
});
} catch (exception) {
console.log(exception);
}
}
- 로그 스트림이 생성된 후 스트림 안에 로그를 생성해야함.
- 여기서 문제는 로그 스트림을 어떤 기준으로 생성할 것인가?
- 또한 로그 스트림을 만들고 최초 로그를 등록하면, 그 로그에 다시 넣으려고 하면 nextSequenceToken 에러가 나옴. 확인 결과 이미 만들어진 로그에 넣으려면 최초 로그를 만들면 생성되는 SequenceToken을 가지고 있다가 같은 로그에 넣게 될 때 파라미터로 보내주지 않으면 에러남.
3) 로그 생성 과정
- 최초 서버 구동시 로그 스트림을 체크하여, 현재날짜와 비교했을때 다르면 생성 로직 타게 설정
//최초 서버 구동시
let logStreams = "";
let nextSequenceToken = "";
let cloudWatchLogs = new CloudWatchLogs({
region: 'ap-northeast-2',
accessKeyId : process.env.AWS_CW_ACCESS_KEY,
secretAccessKey : process.env.AWS_CW_SECRET_KEY,
});
// 최초 로그 스트림 체크 하여 전역변수에 저장
cloudWatchLogs.describeLogStreams({
logGroupName : "babayo-api-logs",
descending : true,
orderBy: "LogStreamName",
limit : 1
}).promise().then((data) => {
logStreams = data.logStreams.length != 0 ? data.logStreams[0].logStreamName : ""
return logStreams;
}).catch(err => {
console.log(err)
});
async logIntegrate(params: any){
try {
let { logType, message } = params;
const now: string = moment().format('YYYY-MM-DD');
if(logStreams == "" || now != logStreams) {
//해당 스트림명으로 생성
await this.cloudWatchStreamCreate({
now
});
}
await this.cloudWatchLogsInit({
logType, message
});
} catch (exception) {
console.log("logIntegrate() Error : ", exception);
}
}
4. 개발
1) app 실행시 현재 날짜에 로그 스트림이 있는지 확인 후, 글로벌 변수에 저장.(로그 종류는 차차 늘릴 예정)
- DEV : LOG, WARNING, ERROR
- PROD : WARNING, ERROR
// log Stream 체크
// 모듈의 종속성 처리 후 호출
async onModuleInit() {
let logStreamName: string = process.env.MODE == "dev" ? "babayo-api-logs" : "babayo-api-logs-live";
global.logStreams = "";
let cloudWatchLogs = new CloudWatchLogs({
region: 'ap-northeast-2',
accessKeyId : process.env.AWS_CW_ACCESS_KEY,
secretAccessKey : process.env.AWS_CW_SECRET_KEY,
});
global.cloudWatchLogs = cloudWatchLogs;
// 최초 로그 스트림 체크 하여 전역변수에 저장
cloudWatchLogs.describeLogStreams({
logGroupName : logStreamName,
descending : true,
orderBy: "LogStreamName",
limit : 1
}).promise().then((data) => {
global.logStreams = data.logStreams.length != 0 ? data.logStreams[0].logStreamName : ""
}).catch(err => {
console.log(err)
});
}
2) Logger Service 구현(하루 기준 로그)
// 기본 로그
log(message: any, stack?:string, context?: string) {
let logType = "LOG";
//super.log(message, context);
if (process.env.MODE == 'dev') {
this.logIntegrate({
logType, message, stack
});
}
};
// warning 로그
warn(message: any, stack?:string, context?: string) {
let logType = "WARNING";
//super.warn(message, context);
this.logIntegrate({
logType, message, stack
});
};
// Error 로그
error(message: any, stack?: string, context?: string) {
let logType = "ERROR";
//super.error(message, stack, context);
this.logIntegrate({
logType, message, stack
});
};
//현재 날짜의 스트림이 없을 시 스트림 생성
async cloudWatchStreamCreate(params: any) {
try {
let { now, logStreamName } = params
return await global.cloudWatchLogs.createLogStream({
logGroupName : logStreamName,
logStreamName : now,
}).promise().then((data) => {
global.logStreams = now;
}).catch(exception => {
global.logStreams = now;
});
} catch (exception) {
console.log("cloudWatchStreamCreate() Error : ", exception);
}
}
// 로그 생성, 현재 날짜로 로그 생성한 후 같은 로그에 넣으려면 nextSequenceToken 필요하기 때문에 전역변수로 선언해놓음.
async cloudWatchLogsInit(params: any) {
try {
let { logType, message, stack, logStreamName } = params;
stack = stack ? stack : "";
let initLogParams: any;
if (nextSequenceToken == "") {
initLogParams = {
logGroupName : logStreamName,
logStreamName : logStreams,
logEvents: [
{
message: `[${logType}] ${message} ${stack}`,
timestamp: Date.now(),
},
],
}
} else {
initLogParams = {
logGroupName : logStreamName,
logStreamName : logStreams,
logEvents: [
{
message: `[${logType}] ${message} ${stack}`,
timestamp: Date.now(),
},
],
sequenceToken : nextSequenceToken
}
}
return await cloudWatchLogs.putLogEvents(initLogParams).promise().then((data) => {
nextSequenceToken = data.nextSequenceToken;
}).catch(err => {
console.log(err)
const now: string = moment().format('YYYY-MM-DD');
this.cloudWatchStreamCreate({
now
});
});
} catch (exception) {
console.log("cloudWatchLogsInit() Error : ", exception);
}
}
async logIntegrate(params: any){
try {
let logStreamName: string = process.env.MODE == "dev" ? "babayo-api-logs" : "babayo-api-logs-live";
logStreams = global.logStreams
let { logType, message, stack } = params;
const now: string = moment().format('YYYY-MM-DD');
if(logStreams == "" || now != logStreams) {
//해당 스트림명으로 생성
await this.cloudWatchStreamCreate({
now, logStreamName
});
}
await this.cloudWatchLogsInit({
logType, message, stack, logStreamName
});
} catch (exception) {
console.log("logIntegrate() Error : ", exception);
}
}
3) Exception 처리 로직
import { ApiLogger } from "@common/logger/logger.service";
import { ArgumentsHost, Catch, ExceptionFilter } from "@nestjs/common";
import { Request, Response } from 'express';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
constructor(private logger:ApiLogger){}
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
const req = ctx.getRequest<Request>();
const status = exception.status == undefined ? "" : exception.response.status;
const userAgent = req.headers["user-agent"] == "" ? "" : req.headers["user-agent"];
const params = req.params;
if(status) {
this.logger.error(`${req.ip} ${req.originalUrl} ${req.method} ${status} ${userAgent} {
"params": ${JSON.stringify(params)},
"exception" : "${exception.stack}"
}`);
res.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: req.url,
exception: exception.message,
});
} else {
this.logger.error(`${req.ip} ${req.originalUrl} ${req.method} ${userAgent} {
"params": ${JSON.stringify(params)},
"exception": "${exception.stack}"
}`);
res.json({
statusCode: 40001,
timestamp: new Date().toISOString(),
path: req.url,
exception: exception.message,
});
}
}
}
4) Controller
- HTTP Exception 적용
@ApiOperation({
summary: '바바요 전체 회원수'
})
@Get('userAllCount')
async getUserAllCount(@Request() req: any){
try {
return await this.userService.getUserAllCount();
} catch (exception) {
throw new HttpException(`[${UserController.name}] ${req.originalUrl} - ${exception}`, 40001);
}
}
5) Service
- Service Unavailable Exception 적용
throw new ServiceUnavailableException(`${AdminService.name}`, err);
5. 추가사항
- CLI 명령어 사용하여, 로그 실시간으로 로컬에서 볼 수 있게 셋팅
- Logs format (콤마 ,) 사용관련 체크
6. AWS CLI
- aws logs tail "babayo-api-logs" --follow
- aws logs tail "babayo-api-logs" --follow --format short --filter-pattern "ERROR”
반응형
'삽집하는 개발들 > AWS' 카테고리의 다른 글
[Git Action][Docker][CD/CI][GitFlow] 구축 (4) | 2023.09.07 |
---|---|
[AWS][Emailer] 회원 Email 전송 MSA 작업 (1) | 2023.09.07 |
운영 자동화를 위한 자동 알림 등록 - AWS Eventbridge (0) | 2023.04.22 |