Когда пользователь сообщает о проблеме: когда при использовании определенной онлайн-функции возникает ошибка, как ее можно быстро и точно обнаружить? Как эффективно отслеживать оптимизацию, когда определенный интерфейс запроса медленно возвращает данные?
Как мы все знаем, когда приходит запрос, вероятно, будут созданы следующие журналы:
1. AceesLog: журнал доступа пользователей
2. Exception: журнал исключений кода
3. SQL: журнал sql-запросов
4. ThirdParty: Third-
журналparty service
Как отслеживать все журналы, созданные по запросу?
Общий подход состоит в том, чтобы использовать requestId в качестве уникального идентификатора,
а затем написать промежуточное программное обеспечение для внедрения requestId в контекст. Когда необходимо выполнить журналирование, извлеките его из контекста для печати
в сторонних службах и журналах SQL
., вам также необходимо внедрить requestId в контекст. RequestId передается в соответствующую функцию для печати. Передавать его слой за слоем слишком хлопотно, а код также относительно навязчив.
Наша цель — снизить навязчивость кода, внедрить его один раз и отслеживать автоматически.
После исследования async_hooks может отслеживать жизненный цикл асинхронного поведения. В каждом асинхронном ресурсе (каждый запрос является асинхронным ресурсом) он имеет 2 идентификатора,
а именно asyncId (текущий идентификатор жизненного цикла асинхронного ресурса), trigerAsyncId (родительский асинхронный ресурс). ИДЕНТИФИКАТОР).
async_hooks предоставляет следующие перехватчики жизненного цикла для прослушивания асинхронных ресурсов:
asyncHook = async_hook.createHook({ //Прослушиваем создание асинхронных ресурсов init(asyncId,type,triggerAsyncId,resource){}, // Прежде чем асинхронная функция обратного вызова ресурса начнет выполняться before(asyncId){}, //После начала выполнения функции обратного вызова асинхронного ресурса after(asyncId){}, // Мониторинг уничтожения асинхронных ресурсов Destroy(asyncId){} })
Затем, если мы создадим сопоставление, каждый asyncId сопоставится с хранилищем, а соответствующий requestId будет сохранен в хранилище, тогда requestId можно будет легко получить.
Так уж получилось, что библиотека, подключенная к cls, была инкапсулирована на основе async_hooks, сохраняя копию данных в одном и том же асинхронном ресурсе и сохраняя ее в виде пар ключ-значение. (Примечание: async_hooked необходимо использовать в более поздней версии node>=8.2.1). Конечно, в сообществе есть и другие реализации, такие как cls-session, node-continuation-local-storage и т. д.
Давайте поговорим о примере использования cls-hooked в моем проекте:
/session.js Создать именованное пространство хранения
const createNamespace = require('cls-hooked').createNamespace const session = createNamespace('requestId-store') Module.exports = session
/logger.js печать журнала
const session = require('./session') модуль.экспорт = { информация: (сообщение) => { const requestId = session.get('requestId') console.log(`requestId:${requestId}`, сообщение) }, ошибка: (сообщение) => { const requestId = session.get('requestId') console.error(`requestId:${requestId}`, сообщение) } }
/sequelize.js sql вызывает logger для печати журналов
const logger = require("./logger") newSequelize( ведение журнала: функция (sql, Costtime) { logger.error(`sql exe: ${sql} | Costtime ${costtime} мс`); } )
/app.js Установите requestId, установите requestId для возврата заголовка ответа и распечатайте журнал доступа
const session = require('./session') const logger = require('./logger') асинхронная функция accessHandler(ctx, next) { const requestId = ctx.header['x-request-id'] || uuid() константные параметры = ctx.request.body ? JSON.stringify(ctx.request.body): JSON.stringify(ctx.request.query) //Устанавливаем идентификатор запроса session.run(() => { session.set('requestId', requestId) logger.info(`url:${ctx.request.path}; params:${params}`) next() //Устанавливаем заголовок возвращаемого ответа ctx.res.setHeader('X-Request-Id',requestId) }) }
Давайте посмотрим на журнал, когда путь запроса /home?a=1:
Журнал доступа: requestId:79f422a6-6151-4bfd-93ca-3c6f892fb9ac URL:/home;params:{"a":"1"} SQL-журнал: Идентификатор запроса: 79f422a6-6151-4bfd-93ca-3c6f892fb9ac sql exe: Выполнено (по умолчанию): SELECT `id` FROM t_user.
Вы можете видеть, что идентификатор запроса журнала всей ссылки для одного и того же запроса один и тот же. Если позже на платформу сигналов тревоги будет отправлен сигнал тревоги, мы сможем найти всю ссылку, выполненную этим запросом, на основе идентификатора запроса.
Внимательные студенты могут заметить, что я также устанавливаю requestId в заголовке ответа, возвращаемого интерфейсом. Цель состоит в том, чтобы, если обнаружено, что запрос отвечает медленно или имеет проблемы, requestId можно узнать непосредственно из браузера и проанализировать.
я провел локальный стресс-тест.
Вот сравнение использования памяти:
Примерно на 10% больше, чем без использования async_hook.
Это нормально для нашей системы QPS со 100-уровневой системой, но если это услуга с высокой степенью параллелизма, нам, возможно, придется тщательно ее рассмотреть.
p.s. Если есть ошибки, укажите на них, если не нравятся, не комментируйте.