Советы, как не пропустить лес за деревьями

Что такое ведение журнала приложений?

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

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

Вход в систему на разных языках программирования

Хотя многие принципы и советы в этом посте применимы к большинству языков программирования, в своих примерах я буду использовать JavaScript/TypeScript, так как это язык для начинающих и опытный язык, который пользуется постоянно растущей популярностью.

Ведение журнала против отладки

Прежде всего, давайте быстро рассмотрим отладку и регистрацию в современных приложениях JavaScript. Некоторые пользователи, которые работали с JavaScript до того, как отладчики стали популярными (или даже были доступны), могут все еще использовать операторы console.log во время разработки для отладки проблем. Другие могут полагаться исключительно на модульное тестирование и, возможно, отладчик во время разработки для реализации своего компонента и вообще не использовать ведение журнала.

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

Эффективное ведение журнала

Будучи профессиональным разработчиком программного обеспечения, архитектором и менеджером с более чем 20-летним стажем, я выбрал несколько лучших практик, которым хотел бы следовать сам, когда дело доходит до ведения журнала:

  • Добавить временные метки к каждому выводу журнала. Правильное время имеет решающее значение для понимания правильной последовательности событий, особенно если вам нужно собрать воедино отдельные журналы разных систем. Отдавайте предпочтение временным меткам UTC, чтобы не иметь дело с часовыми поясами, особенно если у вас есть системы, работающие в разных зонах доступности.
  • Используйте удобочитаемое сообщение. Не просто записывайте значения, а убедитесь, что любой член вашей команды, который позже будет анализировать ваши журналы, понимает их контекст. Будьте как можно более описательными (и разумными) в строке, т. е. используйте такие строки, как «Приложение запущено с настройками по умолчанию» или «База данных успешно обновлена», а не просто «запущено» и «готово».
  • Зарегистрируйте источник событий. Включите источник события журнала, например класс, функцию или имя файла. Это значительно облегчит определение места возникновения события, поскольку сообщения журнала часто очень похожи друг на друга. Если ваша структура ведения журналов поддерживает что-то вроде меток или тегов, используйте их, чтобы различать разные части вашей кодовой базы. Совет: используйте __filename, чтобы автоматически установить текущее имя файла в качестве источника, что упрощает поиск источника журнала.
  • Используйте разные уровни журнала. Хотя изначально у вас может возникнуть соблазн просто записывать все одним и тем же способом, категоризация журналов по разным уровням серьезности, таким как INFO, WARN и ERROR, позволяет вам иметь лучший обзор и выделять вещи, требующие особого внимания. Подробнее о том, как выбрать правильный уровень, в главах ниже.
  • Используйте стандартные имена полей и согласованные типы данных. Стандартизация схемы ваших журналов гарантирует, что команда будет знать, на что обращать внимание при анализе журналов. Последовательность имеет решающее значение!
  • Храните данные полезной нагрузки и сообщения журнала отдельно. Избегайте использования строк шаблона и регистрируйте любые полезные данные как отдельный объект. т.е. вместо записи строки Created 5 records запишите строку Created records и { count: 5 } как отдельную полезную нагрузку. Подобную статическую строку гораздо быстрее и проще искать, чем ту, которая изменяется динамически. Никто не любит поиск по регулярным выражениям.
  • Записывать структуры данных в формате JSON. JSON — это распространенный машиночитаемый формат, который упростит обработку ваших журналов в дальнейшем.
  • Предоставить стандартную конфигурацию для всех инженеров. Избегайте хаоса по мере роста команды. Начните с лучшей практики и отклоняйтесь по мере необходимости. В своих собственных проектах я обычно сначала настраиваю ведение журнала, даже до того, как подключаю остальную часть команды.

Давайте углубимся в каждую из этих рекомендаций в следующих главах.

Используйте структуру ведения журналов и установите стандарты

Хотя console.log часто используется для ведения журнала, особенно в начале нового проекта из-за отсутствия другой среды ведения журнала, это не инструмент ведения журнала, а просто способ вывода информации на консоль. Самый большой недостаток использования console.log (и родственных ему команд console.info, console.warn, console.error) заключается в том, что он не обеспечивает общий формат. Вы можете регистрировать что угодно и как угодно, что может привести к проблемам в будущем.

Поэтому я настоятельно рекомендую использовать фреймворк ведения журнала. Одним из популярных для Node.js и одним из моих любимых является Pino, небольшой и быстрый регистратор JSON. Вот краткий пример того, как это может выглядеть:

Обратите внимание, что мы установили тег name в текущее имя файла, чтобы упростить определение источника сообщения журнала. В первом вызове logger.info мы предоставляем только простую строку, во втором мы передаем методу имя и фамилию пользователя как отдельный объект полезной нагрузки. Как было сказано ранее, я рекомендую разделять полезную нагрузку и сообщение и избегать использования шаблонных строк. Вам также не нужно передавать метку времени, так как Pino автоматически добавит ее, а также некоторую другую полезную информацию в журнал самостоятельно.

Бесстыдный плагин: если вы ищете что-то портативное и легкое альтернативное решение с нулевыми зависимостями от третьих сторон, проверьте следующий Gist, который я использую в качестве отправной точки для своих собственных проектов: https://gist.github.com/ arabold/3a56def70f451c74956c26e1720a9778.

Выберите формат журнала

Существует множество различных распространенных форматов сообщений журнала. Существует даже формат, известный как Общий формат журнала, который поддерживается многими веб-серверами и другими службами Linux. Это выглядит примерно так:

127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326

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

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

{
  "timestamp": "2020-04-12T23:26:32.013Z",
  "level": "INFO",
  "message": "Application started",
  "meta": {
    "tag": "MyExampleApp"
  }
}

На практике большинство фреймворков ведения журналов позволяют вам выбирать, как выводить сообщения журнала на консоль или в файл. Таким образом, обычно это то, о чем вам нужно беспокоиться, когда вы впервые настраиваете ведение журнала для нового проекта. Обратите внимание, что если мы записываем журналы в файл, обычно предпочтительнее избегать использования символов новой строки и разрыва строки и записывать один объект JSON в каждой строке (он же JSON Lines или jsonl). ).

Перейдите к Форматы журналов — (почти) полное руководство, если вы хотите узнать больше о различных форматах журналов и о том, где они используются. И тогда придерживайтесь только JSON;)

Иметь чистую схему

Однако я думаю, что наиболее важным советом является последовательность в наименовании свойств и типов данных.

  • По возможности избегайте использования разных имен для одних и тех же свойств. Например, используйте либо userId, либо user_id, но не взаимозаменяемо! Используйте описательные имена, чтобы избежать путаницы.
  • Поддерживайте согласованность типов данных и особенно избегайте случайного смешивания строк и чисел для одного и того же свойства. Поиск и фильтрация позже будет намного сложнее, чем если бы вы сохранили только один способ.

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

Несколько целей ведения журнала

Большинство фреймворков ведения журналов позволяют вам контролировать, как журналы отображаются на экране или при записи в файл. Используйте эту функциональность и соответствующим образом настройте свою среду! Например, во время разработки большинство сообщений журнала могут оказаться в терминале или консоли браузера инженера. Так что предпочтительнее будет удобочитаемый формат, возможно, даже с цветовым кодированием. Позже, когда ваше приложение будет развернуто на сервере, используйте JSON, чтобы сделать ваши журналы легко читаемыми машиной. И если ваше приложение работает в веб-браузере в системе вашего конечного пользователя, вы можете вообще отключить журналы консоли.

Pino, например, поддерживает форматирование вывода для консоли с помощью pino-pretty. Обновите свой сценарий запуска pacakge.json на что-то вроде строк node src/index.js | pino-pretty, чтобы автоматически применять его при каждом локальном запуске вашего приложения.

Или, если вы используете Gist, на который я ссылался выше, формат вывода (JSON или консоль) будет определяться автоматически в зависимости от вашей среды, а журналы будут полностью отключены во время модульных тестов, чтобы не мешать другому выводу. Хотя это несколько самоуверенно, я нашел это очень простым и удобным подходом, который мне нравится использовать во всех моих личных и профессиональных проектах.

Когда вести журнал — добавление журналов в правильном месте

После того, как вы выбрали платформу ведения журналов и все настроили, следующим важным вопросом является когда вы хотите что-то регистрировать.

Обычно я рекомендую добавлять оператор журнала до того, что что-то произойдет, а также протоколировать результаты после этого. Большинство систем в любом случае автоматически регистрируют непредвиденные ошибки или исключения. Таким образом, простое добавление журнала после операции не даст нам никакой дополнительной информации, кроме того, что операция завершена. Однако часто нас волнуют не случаи успеха, а случаи ошибок! Итак, что происходит, если в нашей операции возникает исключение? Затем сообщение об успешном выполнении пропускается и вместо него регистрируется ошибка. Большой! Но что вызвало ошибку, остается загадкой, если мы не записали в журнал, какую операцию мы пытались выполнить в первую очередь. Поэтому добавляйте сообщение журнала в любое время перед выполнением важной операции, т. е. перед выполнением вызова API к серверной части или перед сохранением записи. в базе, а не только потом!

TL;DR.: Запишите, что произойдет перед важной операцией, включая переданные входные параметры.

Что регистрировать — выбор правильного уровня логирования

Регистратор обычно различает набор различных уровней журнала, которые классифицируют сообщения журнала по их серьезности. Эти уровни часто называются примерно так: «debug», «info», «error» и т. д. Вы должны выбирать уровень с умом и, как правило, по умолчанию использовать «debug» для большей части выходных данных журнала, при этом зарезервировав «info» для основных соединений в вашем поток данных приложения.

Здесь важно отметить, что error следует использовать только для ошибок приложения, а не для ошибок пользователя. Так что это значит? Например, ввод пользователем неправильного пароля является простой ошибкой пользователя и не гарантирует уровень журнала error. Вы или ваша команда мало что можете и будете делать с ошибочным вводом данных пользователем. Вместо этого должно быть достаточно простого info. Таким образом, вы знаете, что произошло, на случай, если вам когда-нибудь в будущем придется просматривать журналы, но не будете бить тревогу. Используйте error только для (непредвиденных) серьезных проблем в логике вашего приложения, чтобы избежать так называемой усталости предупреждений.

Ведение журнала во время разработки

Я вижу, что многие разработчики все еще добавляют операторы console.log в свой код во время разработки, несмотря на то, что среда ведения журналов уже доступна. Постарайтесь избежать этого! Я бы даже зашел так далеко и включил no-console правило ESLint, чтобы никто его не использовал.

Вместо этого я хотел бы призвать вас использовать журналы debug (или silly/verbose, если они предусмотрены вашей инфраструктурой), чтобы делать то же самое и сохранять журналы в приложении даже после завершения работы компонента. Если вы обнаружите, что ваши журналы достаточно важны при разработке компонента, нет причин полагать, что та же информация не станет столь же важной позже. Таким образом, вместо того, чтобы начинать с console.log и затем снова удалять журналы, просто сразу используйте подходящий регистратор. Вы можете поблагодарить меня позже ;)

Бонус: отправьте свои журналы в службу ведения журналов

Предположим, вы следовали этим рекомендациям и настроили все в соответствии с лучшими практиками. Последняя часть головоломки — как вы используете свои журналы? Во время локальной разработки вы, вероятно, просто открываете консоль и проверяете журналы в реальном времени. Но как только приложение развернуто, все становится сложнее. Используя Amazon Cloudwatch или Azure Monitor, вы можете легко обрабатывать свои журналы JSON и искать записи журнала по отметке времени, сообщению или значениям определенных свойств. Если вы использовали согласованную схему для своих объектов полезной нагрузки, вы даже можете запустить сложную аналитику, о которой вы, возможно, даже не подумали, добавляя журналы в первую очередь.

Существует несколько сторонних сервисов, которые еще больше упрощают использование журналов и упрощают доступ к ним для вашей инженерной группы в режиме реального времени, в то время как ваша система используется реальными пользователями. Попробуйте Loggly, Papertrail или Logz.io для решений, ориентированных на ведение журналов, или такие сервисы, как DataDog и Splunk для более полных аналитических платформ.

Я также являюсь поклонником использования служб отчетов об ошибках, таких как Sentry.io или Rollbar, которые очень хорошо сочетаются с ведением журнала приложений. Например, просто отправьте все свои журналы ERROR в одну из этих служб, и вы будете получать уведомления всякий раз, когда в вашей системе происходит что-то неожиданное.

Что дальше?

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

Удачи!