Winston: Пожелание: функции в метаданных по умолчанию

Созданный на 29 мар. 2019  ·  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

    • [х] winston@3

  • _ node -v выходы:_ v10.15.2
  • _Операционная система?_ macOS (не имеет значения)
  • _Язык?_ ES7 (не имеет значения)

В чем проблема?

Как авторы Stackdriver Logging Transport для Winston, мы хотели бы поддерживать корреляцию журналов и запросов. Модуль async_hooks дает нам возможность хранить специфичную для запроса информацию, такую ​​как идентификатор запроса. Однако у нас нет надежного способа получить его на стороне 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);

Обратите внимание на несоответствие «ожидаемых» и «фактических» идентификаторов запросов. Если бы мы были автором MyTransport , у нас не было бы возможности точно получить правильный идентификатор запроса, обратившись к текущему идентификатору выполнения («фактический»). Вместо этого мы должны полагаться на то, что пользователь передает правильный идентификатор запроса в качестве метаданных («ожидаемый»). Это связано с тем, что Winston 3 регистрирует пакеты (через readable-stream ), когда Transport откладывает вызов своего обратного вызова log .

Проблема в том, что мы не хотим полагаться на то, что пользователи передают нам идентификатор запроса; мы хотим, чтобы это происходило автоматически. В конце концов, пользователи, вероятно, захотят просто написать

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

и оставьте отдыхать до нас.

Что вы ожидаете вместо этого?

Ожидаемый и фактический идентификаторы запроса (из связанного вывода) совпадают.

Другая информация

Невозможно исправить код так, чтобы фактические идентификаторы запросов совпадали, поэтому решением этой проблемы будет предоставление пользователям возможности указывать функции в виде полей на defaultMetadata , которые вызываются при вызове регистратора. Таким образом, код изменится на this , что позволит пользователям писать свой код, не зная об идентификаторе запроса, с небольшой оговоркой, что они предоставляют преобразователь в виде поля defaultMetadata (которое, возможно, наш модуль предоставил бы для их использовать).

Самый полезный комментарий

Я нашел обходной путь, который можно использовать до тех пор, пока предложенное решение не будет реализовано - используйте геттеры:

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

UPD: исправлена ​​ошибка, упомянутая @mooski

Все 3 Комментарий

поэтому решением этой проблемы было бы позволить пользователям указывать функции как поля в метаданных по умолчанию.

Я думал, что это можно реализовать аналогично редукционным преобразователям . Вместо того, чтобы иметь свойство с функциональным значением в defaultMetadata , я думал, что defaultMetadata сама по себе может быть опциональной функцией. Как преобразователь, winston будет использовать значение, возвращенное вызовом defaultMetadata в таком случае. Другими словами:

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

Я нашел обходной путь, который можно использовать до тех пор, пока предложенное решение не будет реализовано - используйте геттеры:

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

UPD: исправлена ​​ошибка, упомянутая @mooski

~ Отличное решение @Alexsey - для тех, кто использует этот код, просто имейте в виду, что двоеточия после timeWritten там быть не должно.~

Обновление: исправлено @Alexsey

Была ли эта страница полезной?
0 / 5 - 0 рейтинги