Winston: ๊ธฐ๋Šฅ์š”์ฒญ: ๊ธฐ๋ณธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ํ•จ์ˆ˜

์— ๋งŒ๋“  2019๋…„ 03์›” 29์ผ  ยท  3์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: winstonjs/winston

์—ฌ๊ธฐ์˜ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋Š” ๋‹ค์†Œ ๋ณต์žกํ•˜๋ฏ€๋กœ ์—ฌ๊ธฐ์—์„œ ์šฐ๋ฆฌ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ์„ ์š”์•ฝํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค -- ์šฐ๋ฆฌ๋Š” defaultMetadata ๊ฐ€ ํ•„๋“œ๋กœ ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋ณด๋Š” ๋ฐ ๊ด€์‹ฌ์ด ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

const logger = createLogger({
  defaultMeta: {
    // Function to be evaluated every time a log is written.
    timeWritten: () => { return `Date.now()`; }
  },
  // ...
});

์ „์ฒด ๊ทผ๊ฑฐ๋Š” ์•„๋ž˜๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

๊ท€ํ•˜์˜ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.

  • _ winston ๋ฒ„์ „?_

    • [ ] winston@2

    • [x] winston@3

  • _ node -v ์ถœ๋ ฅ:_ v10.15.2
  • _์šด์˜ ์ฒด์ œ?_ macOS(๊ด€๋ จ ์—†์Œ)
  • _์–ธ์–ด?_ ES7(๊ด€๋ จ ์—†์Œ)

๋ฌธ์ œ๊ฐ€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

Winston์šฉ Stackdriver Logging Transport์˜ ์ž‘์„ฑ์ž๋กœ์„œ ์šฐ๋ฆฌ๋Š” ๋กœ๊ทธ ์š”์ฒญ ์ƒ๊ด€ ๊ด€๊ณ„๋ฅผ ์ง€์›ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. async_hooks ๋ชจ๋“ˆ์€ ์š”์ฒญ ID์™€ ๊ฐ™์€ ์š”์ฒญ๋ณ„ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Transport ์ธก์—์„œ ์•ˆ์ •์ ์œผ๋กœ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜๋‹จ์ด ์—†์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์˜ˆ๋ฅผ ํ†ตํ•ด ๊ฐ€์žฅ ์ž˜ ์„ค๋ช…๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋Š” ~100๊ฐœ์˜ ์š”์ฒญ์ด ์—ฐ์†์ ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ด๋ฃจ์–ด์งˆ ๋•Œ ์ด ์ถœ๋ ฅ ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

const ah = require('async_hooks');
const express = require('express');
const { createLogger } = require('winston');
const TransportStream = require('winston-transport');

// Activate async hooks.
let requestIdHighWater = 0;
const requestIdTable = {};
ah.createHook({
  init: (child, resource, parent) => {
    requestIdTable[child] = requestIdTable[parent];
  }
}).enable();

// OUR CODE

class MyTransport extends TransportStream {
  log(info, next) {
    // From the transport, the request ID is not reliable.
    const actualRequestId = requestIdTable[ah.executionAsyncId()];
    console.log(`Expected: ${info.expectedRequestId}, Actual: ${actualRequestId}`);
    setImmediate(next);
  }
}

// OUR USER'S CODE

const logger = createLogger({
  transports: [new MyTransport()]
});

const app = express();

app.get('/', async (req, res) => {
  // Store a request ID.
  const requestId = requestIdHighWater++;
  requestIdTable[ah.executionAsyncId()] = requestId;
  logger.info('hello', { expectedRequestId: requestId });
  res.sendStatus(200);
});

app.listen(3000);

"์˜ˆ์ƒ" ๋ฐ "์‹ค์ œ" ์š”์ฒญ ID์˜ ๋ถˆ์ผ์น˜์— ์œ ์˜ํ•˜์‹ญ์‹œ์˜ค. ์šฐ๋ฆฌ๊ฐ€ MyTransport ์˜ ์ž‘์„ฑ์ž๋ผ๋ฉด ํ˜„์žฌ ์‹คํ–‰ ID("์‹ค์ œ")๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์˜ฌ๋ฐ”๋ฅธ ์š”์ฒญ ID๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ("์˜ˆ์ƒ")๋กœ ์˜ฌ๋ฐ”๋ฅธ ์š”์ฒญ ID๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์‚ฌ์šฉ์ž์— ์˜์กดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Transport๊ฐ€ log ์ฝœ๋ฐฑ ํ˜ธ์ถœ์„ ์—ฐ๊ธฐํ•  ๋•Œ Winston 3๊ฐ€ ๋กœ๊ทธ๋ฅผ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค( readable-stream ๋ฅผ ํ†ตํ•ด).

๋ฌธ์ œ๋Š” ์šฐ๋ฆฌ๋ฅผ ์œ„ํ•ด ์š”์ฒญ ID๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์‚ฌ์šฉ์ž์— ์˜์กดํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ทธ๊ฒƒ์ด ์ž๋™์œผ๋กœ ์ผ์–ด๋‚˜๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์‚ฌ์šฉ์ž๋Š” ์•„๋งˆ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์“ฐ๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

app.get('/', async (req, res) => {
  logger.info('hello');
  res.sendStatus(200);
});

๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์—๊ฒŒ ๋งก๊ธฐ์‹ญ์‹œ์˜ค.

๊ทธ ๋Œ€์‹  ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜์‹ญ๋‹ˆ๊นŒ?

์—ฐ๊ฒฐ๋œ ์ถœ๋ ฅ์˜ ์˜ˆ์ƒ ๋ฐ ์‹ค์ œ ์š”์ฒญ ID๊ฐ€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐํƒ€ ์ •๋ณด

์‹ค์ œ ์š”์ฒญ ID๊ฐ€ ์ผ์น˜ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ฑฐ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” defaultMetadata ์˜ ํ•„๋“œ๋กœ ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ this ๋กœ ๋ณ€๊ฒฝ๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ ID๋ฅผ ์ธ์‹ํ•˜์ง€ ์•Š๊ณ ๋„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ defaultMetadata ํ•„๋“œ๋กœ ์ฝํฌ๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋Š” ์ž‘์€ ๊ฒฝ๊ณ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์•„๋งˆ๋„ ์šฐ๋ฆฌ ๋ชจ๋“ˆ์€ ์‚ฌ์šฉ).

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์ œ์•ˆ๋œ ์†”๋ฃจ์…˜์ด ๊ตฌํ˜„๋  ๋•Œ๊นŒ์ง€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. getter ์‚ฌ์šฉ:

const logger = createLogger({
  defaultMeta: {
    get timeWritten () { return `Date.now()`; }
  },
  // ...
});

UPD: @mooski๊ฐ€ ์–ธ๊ธ‰ํ•œ ๋ฒ„๊ทธ ์ˆ˜์ •

๋ชจ๋“  3 ๋Œ“๊ธ€

๋”ฐ๋ผ์„œ ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์‚ฌ์šฉ์ž๊ฐ€ defaultMetadata์˜ ํ•„๋“œ๋กœ ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ด๊ฒƒ์ด redux thunks ์™€ ์œ ์‚ฌํ•˜๊ฒŒ ๊ตฌํ˜„๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. defaultMetadata ์— ํ•จ์ˆ˜ ๊ฐ’ ์†์„ฑ์„ ๊ฐ–๋Š” ๋Œ€์‹  defaultMetadata ์ž์ฒด๊ฐ€ ์„ ํƒ์ ์œผ๋กœ ํ•จ์ˆ˜๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝํฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ winston ๋Š” ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ defaultMetadata ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ˜ํ™˜๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด:

const logger = createLogger({
  defaultMeta: () => {
    // Function to be evaluated every time a log is written.
    return {
       timeWritten: Date.now();
       // ..
    }
  }
  // ...
});

์ œ์•ˆ๋œ ์†”๋ฃจ์…˜์ด ๊ตฌํ˜„๋  ๋•Œ๊นŒ์ง€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. getter ์‚ฌ์šฉ:

const logger = createLogger({
  defaultMeta: {
    get timeWritten () { return `Date.now()`; }
  },
  // ...
});

UPD: @mooski๊ฐ€ ์–ธ๊ธ‰ํ•œ ๋ฒ„๊ทธ ์ˆ˜์ •

~ํ›Œ๋ฅญํ•œ ์†”๋ฃจ์…˜ @Alexsey - ์ด ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์€ timeWritten ๋’ค์— ์ฝœ๋ก ์ด ์žˆ์–ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”.~

์—…๋ฐ์ดํŠธ: @Alexsey์— ์˜ํ•ด ์ˆ˜์ •๋จ

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰