Я работаю разработчиком полного стека с 2015 года и хорошо разбираюсь в хороших методах ведения журнала, насколько они будут полезны в случае отладки или проверки журнала аудита и т. д.
Итак, в этом посте я хотел бы поделиться мои мысли о том, что все должно быть включено в журналы и как построить один такой регистратор, чтобы было полезно и легко отлаживать приложения в масштабе (микросервисы).
Также упомяну, что я использовал пара библиотек, таких как morgan (регистратор API), tracer (журналы с источником журнала), log4js (очень популярный, похожий на log4j) и еще несколько. Но чего не хватало во всех вышеперечисленных библиотеках, так это того, что не было возможности расширить библиотеку, например: вызвать предупреждение Sentry в журналах уровня ошибок и т. Д. Также мы не могли вмешиваться в формат журналов в соответствии с нашим удобством. Поэтому я решил пойти дальше и создать его для своих собственных целей. (Ссылка доступна в конце этого блога).

С моей точки зрения, не просто достаточно, чтобы библиотека протоколирования позволяла вам записывать журнал с другим уровнем журнала в стандартный вывод, она также должна предоставлять следующие возможности:
1. Время события
2. Происхождение журнала
3. Идентификация журнала
4. Кто/что инициировало событие
5. Описание
6. Возможность отслеживания события
7. Важность журнала
8. Другая информация, если таковая имеется…

Классификация компонентов журнала:

Мы можем разделить ответственность библиотеки журналов на две части:
1. Информация, которая будет добавлена ​​библиотекой
2. Информация, которая должна быть записана разработчиком.

Не существует известного метода (по крайней мере, насколько мне известно), чтобы заставить разработчика писать журналы в определенном формате. Но библиотеку можно настроить для вывода важной информации, связанной с каждым журналом.

Итак, что мы можем ожидать в конце этого блога, так это вывод журнала, похожего на изображение ниже:

2019-03-31T17:39:33.544Z [DEBUG] <test.js:32:16> test...

В приведенной выше строке журнала пока нет ничего особенного, потому что почти каждая библиотека ведения журнала предлагает один и тот же формат, но то, что интересно в пользовательской библиотеке, находится в разделе ниже.

Ведение журнала сервера

Это век микросервисов, поэтому очень важно связать каждый журнал с request_id, который инициирует действие. Если это любой другой язык, такой как java, python и т. д., это не будет проблемой, но Nodejs является однопоточным, и каждый процесс выполняется асинхронно, поэтому мы не можем поддерживать контекст относительно запроса, такого как request_it. Но благодаря Nodejs Async Hooks и библиотеке async-local-storage, которая создает очень простой интерфейс для Async Hooks.

Примечание. Фрагменты кода ниже будут написаны для Koa (инфраструктура Nodejs), вы можете реализовать что-то подобное в соответствии с вашими требованиями.

Сначала нам нужно промежуточное ПО, создающее request_id:

export default function RequestId() {
  return async (ctx, next) => {
    ctx.state.id = uuid.v1();
    ctx.req.id = ctx.state.id;
    ctx.set('X-Request-Id', ctx.state.id);
    await next();
  };
}

Теперь с помощью библиотеки async-local-storage мы будем ассоциировать этот request_id на протяжении всего жизненного цикла запроса:

import als from 'async-local-storage';
als.enable();
export default function LogReqId() {
  return async (ctx, next) => {
    als.scope();
    als.set('reqId', ctx.state.id);
    await next();
  };
}

Убедитесь, что вы разместили ПО промежуточного слоя в том же порядке, что и выше.
Теперь пришло время создать собственный регистратор или написать оболочку вокруг вашей любимой библиотеки журналов, чтобы включить request_id в каждый журнал.
Ниже я используйте tracer (один из моих любимых!) и создайте вокруг него оболочку:

import tracer from 'tracer';
import als from 'async-local-storage';
const tlogger = tracer.colorConsole({
  level: 'debug',
  format: '{{timestamp}} <{{title}}> {{file}}:{{line}} {{message}}',
  stackIndex: 1
});
const getReqId = () => als.get('reqId') || '';
const logger = {
  trace(...args) {
    tlogger.trace(getReqId(), ...args);
  },
  debug(...args) {
    tlogger.debug(getReqId(), ...args);
  },
  info(...args) {
    tlogger.info(getReqId(), ...args);
  },
  warn(...args) {
    tlogger.warn(getReqId(), ...args);
  },
  error(...args) {
    tlogger.error(getReqId(), ...args);
  },
};
export default logger;

С приведенной выше настройкой ваши журналы будут выглядеть следующим образом:

2019-03-31T09:33:52+0000 <debug> auth.js:36 18ab1370-5398-11e9-ac88-133bbfd057ff Authenticated!

Но этого все же недостаточно, потому что очень часто в продакшене предлагается иметь систему уведомлений об ошибках, чтобы разработчик был уведомлен, когда на сервере происходит что-то плохое. Обычно что-то плохое регистрируется с ошибкой logLevel:. Почему бы нам не соединить их, заменив созданную выше функцию error этой:

error(...args) {
    tlogger.error(getReqId(), ...args);
    const err = args.find(e => e instanceof Error);
    err && Sentry.captureException(err)
},

Таким образом, каждый раз, когда регистрируется журнал с уровнем: ошибка, оповещения Sentry также срабатывают параллельно.

Для тех, кто ищет дополнительные преимущества в настройке вашей библиотеки журналов, ознакомьтесь с logler, который обеспечивает полную гибкость с точки зрения создания вашей собственной библиотеки, которая удовлетворит потребности каждого человека.

Так что, если вам понравился пост, пожалуйста, хлопните в ладоши, чтобы другие тоже его прочитали. Спасибо :)