https://stackoverflow.com/questions/32719923/redirecting-stdout-to-file-nodejs 를 참고했다.
custom-logger.ts
import { MiddlewareHandler } from "hono/types"
const fs = require('fs')
const isDebug: Boolean = process.env.NODE_ENV !== "production";
var myLogFileStream = fs.createWriteStream('./access.log', {flags: 'a'}); //for append
// TODO create <date>.log
var fileLogger = new console.Console(myLogFileStream, myLogFileStream);
export const loggerFunc = (tag:string, message: any, ...rest: any[]) => {
const datetime = new Date().toLocaleString()
fileLogger.log(tag, datetime, message, ...rest);
if (isDebug) {
console.log(tag, datetime, message, ...rest);
}
}
process.on('uncaughtException', function(err) {
const datetime = new Date().toLocaleString()
fileLogger.error("[uncaughtException]", datetime, (err && err.stack) ? err.stack : err);
if (isDebug) {
console.error("[uncaughtException]", datetime, (err && err.stack) ? err.stack : err);
}
});
export const customLogger = (): MiddlewareHandler => {
return async function logger(c, next) {
const { method, path } = c.req
const userAgent = c.req.header('user-agent')
const ip = c.env.requestIP(c.req.raw).address
const datetime = new Date().toLocaleString()
await next()
fileLogger.log("[Request]", datetime, method, path, c.res.status, ip, userAgent)
if (isDebug) {
console.log("[Request]", datetime, method, path, c.res.status, ip, userAgent)
}
}
}
loggerFunc으로 log를 찍을경우 파일로 쓰기가 가능하다. 무한히 log가 커지지 않도록 주기적으로 삭제하거나, 날짜별로 새로 생성하는 것은 고민 필요, log Level에 따라서 여러 파일로 분기하는 것도 고민 필요
index.ts
import { customLogger, loggerFunc } from './custom-logger';
const limiter = rateLimiter({
windowMs: 6 * 1000,
limit: 5, // Limit each IP to 100 requests per `window`
standardHeaders: "draft-6",
keyGenerator: (c) => {
const ip = c.env.requestIP(c.req.raw).address;
return ip;
}, // Method to generate custom identifiers for clients.
handler: async (c, _, options) => {
// TODO: 이겨서 ip를 저장해 두었다가 1시간 동안 request 블럭시키는 것도 가능할 듯.
// 로그가 안 찍혀서 직접 찍는다.
const { method, path } = c.req
const userAgent = c.req.header('user-agent')
const ip = c.env.requestIP(c.req.raw).address
const datetime = new Date().toLocaleString()
loggerFunc("[rateLim]", datetime, method, path, options.statusCode, ip, userAgent)
// Json으로 응답하자
return c.json({code: 429, message: "block too many requests"}, options.statusCode)
}
});
app.onError((err, c) => {
if (err instanceof ErrorCodeException) {
loggerFunc("[onError]", `ErrorCodeException: ${err.code} ${err.message}`);
return c.json({code: err.code, message: err.message}, 500);
}
if (err instanceof HTTPException) {
// Get the custom response
loggerFunc("[onError]", `HTTPException: ${err.message}`);
return c.json({code: err.status, message: err.message}, err.status);
}
//...
})
실행 결과
dajkim76@Kims-Mac-mini ~/test/hono_test % cat access.log
[Request] 7/13/2024, 4:41:01 AM GET /aa 404 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[Request] 7/13/2024, 4:41:02 AM GET /aa 404 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[Request] 7/13/2024, 4:41:02 AM GET /aa 404 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[Request] 7/13/2024, 4:41:03 AM GET /aa 404 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[Request] 7/13/2024, 4:41:03 AM GET /aa 404 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:03 AM 7/13/2024, 4:41:03 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:04 AM 7/13/2024, 4:41:04 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:04 AM 7/13/2024, 4:41:04 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:04 AM 7/13/2024, 4:41:04 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:05 AM 7/13/2024, 4:41:05 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:05 AM 7/13/2024, 4:41:05 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:05 AM 7/13/2024, 4:41:05 AM GET /aa 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:15 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:15 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:19 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:19 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:20 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:20 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:21 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:21 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:21 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:21 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[rateLim] 7/13/2024, 4:41:21 AM 7/13/2024, 4:41:21 AM GET / 429 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:22 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:22 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:22 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:22 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:22 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:22 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:22 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:22 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
[onError] 7/13/2024, 4:41:22 AM ErrorCodeException: 1234 error remote ip ::1
[Request] 7/13/2024, 4:41:22 AM GET / 500 ::1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
=== 7/14일 추가===
log 파일을 날짜별로 생성하고, 7일이 지난 log파일은 삭제하는 코드를 만들었다. 간단하게 만들었다. 이 보다 더 간단하게는 힘들 것 같다.
custom-logger.ts 전체코드
import { MiddlewareHandler } from "hono/types"
const fs = require('node:fs')
const isDebug: Boolean = process.env.NODE_ENV !== "production";
const LOG_MAX_DAYS = 7; // 7일이 지난 파일은 삭제
const logDir = "./logs"
let logDateString: string;
let logFileStream: NodeJS.WritableStream
let fileLogger: Console
function getDateString(date: Date) {
const year = date.getFullYear().toString();
let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString();
if (month.length == 1) month = `0${month}`;
if (day.length == 1) day = `0${day}`
return `${year}-${month}-${day}`;
}
function checkLogFile() {
const nowDate = new Date();
const dateString = getDateString(nowDate);
if (logDateString !== dateString) {
if (!fs.existsSync(logDir)) {
try {
fs.mkdirSync(logDir);
} catch(e) {
if(e.code !== 'EEXIST'){
throw e;
}
}
}
if (logFileStream) {
logFileStream.end();
}
const logPath = logDir + "/" + dateString + ".log";
logFileStream = fs.createWriteStream(logPath, {flags: 'a'});
fileLogger = new console.Console(logFileStream, logFileStream);
logDateString = dateString;
console.log(`log path: ${logPath}`);
// remove old log file
const oldDate = new Date(nowDate);
oldDate.setDate(nowDate.getDate() - LOG_MAX_DAYS);
const oldDateString = getDateString(oldDate);
const oldLogPath = logDir + "/" + oldDateString + ".log";
console.log(`old log path: ${oldLogPath}`);
if (fs.existsSync(oldLogPath)) {
try {
fs.unlinkSync(oldLogPath);
} catch (err) {
console.error(err);
}
}
}
}
checkLogFile();
export const loggerFunc = (tag:string, message: any, ...rest: any[]) => {
const datetime = new Date().toLocaleString()
checkLogFile();
fileLogger.log(tag, datetime, message, ...rest);
if (isDebug) {
console.log(tag, datetime, message, ...rest);
}
}
process.on('uncaughtException', function(err) {
const datetime = new Date().toLocaleString()
checkLogFile();
fileLogger.error("[uncaughtException]", datetime, (err && err.stack) ? err.stack : err);
if (isDebug) {
console.error("[uncaughtException]", datetime, (err && err.stack) ? err.stack : err);
}
});
export const customLogger = (): MiddlewareHandler => {
return async function logger(c, next) {
const { method, path } = c.req
const userAgent = c.req.header('user-agent')
const ip = c.env.requestIP(c.req.raw).address
const datetime = new Date().toLocaleString()
await next()
checkLogFile();
fileLogger.log("[Request]", datetime, method, path, c.res.status, ip, userAgent)
if (isDebug) {
console.log("[Request]", datetime, method, path, c.res.status, ip, userAgent)
}
}
}