console.log()에서 컨텍스트를 유지하는 간단한 방법

This post explains a simple way to enhance the use of console.log() in a nodejs http server, with an example for SvelteKit. Of course one may use dedicated logger tools too.



Nodejs http/tcp 서버는 여러 요청을 동시에 처리합니다. 로깅과 관련하여 현재 요청의 식별자(requestId, userId 또는 요청과 관련된 모든 데이터)를 각 로그 메시지 앞에 추가하는 것이 유용합니다.

이 정보의 이름을 지정하겠습니다contextualData .

명확하고 쉬운 방법은 컨텍스트 데이터를 함수에서 함수로 전달하여 코드의 모든 파일에서 console.log(contextualData, xxx)를 호출할 수 있도록 하는 것입니다.

처음에는 간단하고 쉬웠지만 앱이 성장함에 따라 덜 단순하고 거대한 코드베이스로 끔찍합니다.

다른 방법이 있습니까?
실제로 AsyncLocalStorage은 요청 컨텍스트에 있는 스토리지(브라우저의 localStorage와 유사)를 정확하게 제공하는 상대적으로 새롭고 알려지지 않은 Nodejs API입니다.

1단계: AsyncLocalStorage 사용



이 API를 사용하는 것은 매우 간단합니다. 내 의견을 포함하여 정확히 동일한 문서 예제를 살펴보겠습니다.

import http from 'node:http';

// import AsyncLocalStorage API
import { AsyncLocalStorage } from 'node:async_hooks';

// new instance
const asyncLocalStorage = new AsyncLocalStorage();

// this function shows how to retrieve the context
// and preprend it to console.log() messages
function logWithId(msg) {
  const id = asyncLocalStorage.getStore();
  console.log(`${id !== undefined ? id : '-'}:`, msg);
}

let idSeq = 0; // the contextual data of this example

http.createServer((req, res) => {

  // .run(context, callback) runs the callback and makes 'context' 
  // available when calling .getStore() from inside the callback
  // for this example, idSeq is incremented on each request
  asyncLocalStorage.run(idSeq++, () => {

    // starting from here, contextualData is defined

    logWithId('start');

    // Imagine any chain of async operations here
    setImmediate(() => {
      logWithId('finish');
      res.end();
    });
    // until here
  });
}).listen(8080);

http.get('http://localhost:8080');
http.get('http://localhost:8080');
// Prints:
//   0: start
//   1: start
//   0: finish
//   1: finish


다음 단계는 console.log()를 다시 작성하여 사용 가능한 경우 컨텍스트가 있는 메시지를 자동으로 미리 추가하도록 합니다.

2단계: console.log() 재작성



정적 접두사가 있는 예를 들어 보겠습니다.

let _log = null // pointer to console.log base function

const setConsolePrefix = (prefix) => {
  // singleton
  if (_log)
    return
  // keep a pointer to the system console.log function
  _log = console.log
  // rewrite console.log function with prefix
  console.log = () => {
    let args = Array.from(arguments)
    if (prefix)
        // insert prefix as the first argument
        args.unshift(prefix)
    // call system console.log function with updated arguments
    _log.apply(console, log, args)
}

// anywhere in the code
setConsolePrefix("Pulp Fiction:")

// later in the code
console.log("It's Zed's") 
console.log("Who's Zed ?")
// prints :
// Pulp Fiction: It's Zed's
// Pulp Fiction: Who's Zed ?


너무 간단합니다.

물건 모으기



이제 모든 것을 모아서 이 기능을 Context.js 파일에 캡슐화해 보겠습니다.

import { AsyncLocalStorage } from "async_hooks"

export const context = new AsyncLocalStorage()

var __log = null
export function setContextualConsole(){

    // singleton
    if (__log)
        return
    __log = console.log

    console.log = function(){

        let args = Array.from(arguments)

        // retrieve a contextual prefix instead of a static prefix
        const contextualPrefix = context.getStore()

        if (contextualPrefix)
            args.unshift(contextualPrefix )

        __log.apply(console, args)
    }    
}


Sveltekit에서 사용



Sveltekit에서 이를 사용하는 올바른 위치는 hooks.ts 에 정의된 handle() 함수입니다. 이 예제에서 컨텍스트 데이터는 Nodejs AsyncLocalStorage 문서 예제에서와 매우 동일한 seqId입니다.

import {context, setContextualConsole} from 'Context'

// our contextual data
let seqId = 0 

export async function handle({ event, resolve }) {

    seqId++

    // use our new 'context-ready' console.log
    setContextualConsole()

    return await context.run(seqId, async()=>{

        // starting from here, console.log(xxx) prints 'seqId xxx' 

        const response = await resolve(event)

        // until here
        return response
    })
}


노드 js Http 서버에서 사용



베어 nodejs httpServer와 동일:

import http from 'node:http'
import {context, setContextualConsole} from './Context.js'

// our contextual data
let seqId = 0

http.createServer((req, res) => {

    seqId++

    // use our 'context-ready' console
    setContextualConsole()

    context.run(seqId, ()=>{

        // context available from here...
        console.log('start')

        // the timeout simulates request processing time
        // so that we get simultaneous requests for this example
        setTimeout(()=>{

            console.log('end')

            // ... to here
            res.end()

            }, 10)
        })

}).listen(80)


http.get('http://localhost')
http.get('http://localhost')

// prints :
// 1 start
// 2 start
// 1 end
// 2 end


그게 다야!

컨텍스트 데이터는 개체, 배열 또는 요청 처리 중에 저장해야 하는 데이터일 수 있습니다.

SvelteKit에서 내 조언은 로깅 목적으로만 이 트릭을 사용하는 것입니다. event.locals를 대체해서는 안 됩니다.

좋은 웹페이지 즐겨찾기