Seize the day

POST : Backend study

Honojs file logger 초간단 구현

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)
      }        
    }
  }
top

posted at

2024. 7. 13. 04:49


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크