Логирование api что это
Логирование активности с использованием Web Beacon API
Beacon API — это основанный на JavaScript интерфейс для:
отправки небольшого количества данных на сервер с браузера, без ожидания ответа. В этой статье, мы рассмотрим в каких случаях будет полезен Beacon API, чем он отличается от использования XMLHTTPRequest (Ajax) для тех же целей и как его использовать.
Для чего нам очередной API?
Beacon API используется для отправки небольших по объему данных на сервер без ожидания ответа. Последняя часть утверждения является наиболее интересной. Beacon API разработан специально для того, что бы можно было отправить данные и забыть о них. Не нужно ожидать ответ, так как его и не будет.
Метафора с открытками, это такие карточки которые люди посылают/посылали друг другу. Как правило, на них писали небольшой по объему текст («Ты где? А я на море лол.», «Тут у меня шикарная погода, не то что у тебя в офисе»), кидали в почту и забывали. Никто не ожидал ответа по типу «Я уже выехал за тобой», «У меня в офисе чудесно».
Существует множество случаев, когда подход «отправил и забыл» будет уместен.
Отслеживание статистики и Аналитическая информация
Это первое, что приходит на ум. Такие большие решения как Google Analytics могут предоставлять хороший обзор на базовые вещи. Но если мы хотим, что-то более кастомизированное? Нам необходимо написать немного кода для отслеживания того, что происходит на странице (как пользователи взаимодействуют с компонентами, как далеко они скролят, какие страницы были отображены до первой продажи), затем отправить эти данные на сервер когда пользователь покидает страницу. Beacon идеально подходит для решения такой задачи, так как мы просто отправляем данные, и нам ненужен ответ от сервера.
Дебаг и Логирование
Другое применение это логирования информации из JavaScript кода. Представьте себе ситуацию когда у вас большое приложение с богатым UI/UX. Все тесты зеленые, а на проде периодически всплывает ошибка о которой вы знаете, но не можете продебажить ее из за не хватки информации. В данном случае вы можете ипользовать Beacon для диагностики.
На самом деле любая задача с логированиям может быть решешина с использованием Beacon. Это может быть создание точек сохранения в играх, сбор информации об использоании нового функционала, запись результатов тестирования и так далее. Если это, что-то, что происходит в браузере и вы хотите, что бы сервер знал об этом, Beacon это, то, что нужно.
Разве мы не делали этого ранее?
Я знаю о чем вы думаете. Ничто из этого не ново? Мы общаемся с севером посредством XMLHTTPRequest уже более 10 лет. Недавно мы начали использовать Fetch API, что по факту делает то же самое, просто с новым Promise интерфейсом. Так зачем нам еще один Beacon API?
Ключевая особенность в том, что нам не нужен ответ от сервера. Браузер может поставить в очередь запрос и отправить данные не блокируя выполнение какого либо кода. Так как в это впрягается браузер, для нас не важно выполняется ли еще код или нет, браузер просто будет себе тихонько отправлять запросы в фоне.
C Beacon API не нужно дожидаться лучшего момента для CPU, сети. Просто добавить в очередь запрос с помощью beacon практически нечего не стоит.
Для того, что бы понять почему это важно, достаточно взглянуть на то, как и где обычно используются подобная логика. Например для того, что бы измерить как долго пользователь находится на странице, нам необходимо отправить запрос на сервер как можно ближе к концу сессии.
Вы же понимаете насколько HTTP запросы медленные? И последнее, что вы хотите, так это впихивать HTTP запрос между переходами.
Пробуем Beacon API
Базовый пример использования очень прост:
Использование navigator.sendBeacon()
data — этот параметр может принимать несколько форматов данных, все те с которыми работает Fetch API. Это может быть Blob, BufferSource, FormData или URLSearchParams и тд.
Мне нравится использовать FormData для простых key-value данных, это не сложный и простой в использовании класс.
Поддержка браузерами
Поддержка этого API вполне себе солидная. Единственный браузер который не поддерживает, это Internet Explorer (не ожидал я такого) и Opera Mini. Но в Edge все работает. В большинстве случаев поддержка есть, но лучше на всякий случай проверить:
Пример: логируем время проведенное на странице
Для того, что бы увидеть все это дело на практике, давайте создадим простую систему подсчета времени которое пользователь находится на странице. Когда страница загружается мы смотрим на время и когда покидает ее мы отправляем запрос со времени начала просмотра и текущее на сервер.
Так как нас интересует только время проведенное на странице, а не настоящее время, мы можем использовать performance.now() для получения базового timestamp при загрузке страницы:
Давайте обернем небольшой кусочек логики в удобную в использовании функцию:
Когда страница выгружается (или перед этим), наша функция logVisit() будет вызвана и если браузер поддерживает Beacon API, отправит запрос на сервер.
Пару моментов
Так как большая часть проблем для решения которых будет использоваться Beacon API, вертятся вокруг отслеживания активности, будет важным отметить социальную и законную часть всей это кухни.
Просто помните о нем.
DNT: DO NOT TRACK
В дополнение, браузеры имеют опцию которая позволяет пользователям обозначить, что они не хотят, что бы их активность отслеживалась. Do Not Track отправляет HTTP хедер, который выглядит так:
В заключение
Beacon API действительно очень удобный способ для отправки данных на сервер, особенно в контексте логирования. Поддержка браузерами на достаточно хорошем уровне и позволяет вам легко логировать любую информацию без каких либо негативных последсвий для производительности и отзывчивости вашего UI. Non-blocking природа этих запросов играет в этом очень хорошую роль, это горазно быстрей альтернатив XHR и Fetch.
Что такое логирование?
Известно, что программисты проводят много времени, отлаживая свои программы, пытаясь разобраться, почему они не работают — или работают неправильно. Когда говорят про отладку, обычно подразумевают либо отладочную печать, либо использование специальных программ – дебагеров. С их помощью отслеживается выполнение кода по шагам, во время которого видно, как меняется содержимое переменных. Эти способы хорошо работают в небольших программах, но в реальных приложениях быстро становятся неэффективными.
Сложность реальных приложений
Возьмем для примера типичный сайт. Что он в себя включает?
И это только самый простой случай. Реальность же значительно сложнее: множество разноплановых серверов, системы кеширования (ускорения доступа), асинхронный код, очереди, внешние сервисы, облачные сервисы. Все это выглядит как многослойный пирог, внутри которого где-то работает нами написанный код. И этот код составляет лишь небольшую часть всего происходящего. Как в такой ситуации понять, на каком этапе был сбой, или все пошло не по плану? Для этого, как минимум, нужно определить, в каком слое произошла ошибка. Но даже это не самое сложное. Об ошибках в работающем приложении узнают не сразу, а уже потом, — когда ошибка случилась и, иногда, больше не воспроизводится.
Логирование
И для всего этого многообразия систем существует единое решение — логирование. В простейшем случае логирование сводится к файлу на диске, куда разные программы записывают (логируют) свои действия во время работы. Такой файл называют логом или журналом. Как правило, внутри лога одна строчка соответствует одному действию.
Выше небольшой кусок лога веб-сервера Хекслета. Из него видно ip-адрес, с которого выполнялся запрос на страницу и какие ресурсы загружались, метод HTTP, ответ бекенда (кода) и размер тела ответа в HTTP. Очень важно наличие даты. Благодаря ей всегда можно найти лог за конкретный период, например на то время, когда возникла ошибка. Для этого логи грепают:
Когда программисты только начинают свой путь, они, часто не зная причину ошибки, опускают руки и говорят «я не знаю, что случилось, и что делать». Опытный же разработчик всегда первым делом говорит «а что в логах?». Анализировать логи — один из базовых навыков в разработке. В любой непонятной ситуации нужно смотреть логи. Логи пишут все программы без исключения, но делают это по-разному и в разные места. Чтобы точно узнать, куда и как, нужно идти в документацию конкретной программы и читать соответствующий раздел документации. Вот несколько примеров:
Многие программы логируют прямо в консоль, например Webpack показывает процесс и результаты сборки:
Во фронтенде файлов нет, поэтому логируют либо прямо в консоль, либо к себе в бекенды (что сложно), либо в специализированные сервисы, такие как LogRocket.
Уровни логирования
Чем больше информации выводится в логах, тем лучше и проще отладка, но когда данных слишком много, то в них тяжело искать нужное. В особо сложных случаях логи могут генерироваться с огромной скоростью и в гигантских размерах. Работать в такой ситуации нелегко. Чтобы как-то сгладить ситуацию, системы логирования вводят разные уровни. Обычно это:
Поддержка уровней осуществляется двумя способами. Во-первых, внутри самой программы расставляют вызовы библиотеки логирования в соответствии с уровнями. Если произошла ошибка, то логируем как error, если это отладочная информация, которая не нужна в обычной ситуации, то уровень debug.
Во-вторых, во время запуска программы указывается уровень логирования, необходимый в конкретной ситуации. По умолчанию используется уровень info, который используется для описания каких-то ключевых и важных вещей. При таком уровне будут выводиться и warning, и error. Если поставить уровень error, то будут выводиться только ошибки. А если debug, то мы получим лог, максимально наполненный данными. Обычно debug приводит к многократному росту выводимой информации.
Уровни логирования, обычно, выставляются через переменную окружения во время запуска программы. Например, так:
Существует и другой подход, основанный не на уровнях, а на пространствах имен. Этот подход получил широкое распространение в JS-среде, и является там основным. Фактически, он построен вокруг одной единственной библиотеки debug для логирования, которой пронизаны практически все JavaScript-библиотеки как на фронтенде, так и на бекенде.
Принцип работы здесь такой. Под нужную ситуацию создается специализированная функция логирования с указанием пространства имен, которая затем используется для всех событий одного процесса. В итоге библиотека позволяет легко отфильтровать только нужные записи, соответствующие нужному пространству.
Запуск с нужным пространством:
Ротация логов
Со временем количество логов становится большим, и с ними нужно что-то делать. Для этого используется ротация логов. Иногда за это отвечает сама программа, но чаще — внешнее приложение, задачей которого является чистка. Эта программа по необходимости разбивает логи на более мелкие файлы, сжимает, перемещает и, если нужно, удаляет. Подобная система встроена в любую операционную систему для работы с логами самой системы и внешних программ, которые могут встраиваться в нее.
С веб-сайтами все еще сложнее. Даже на небольших проектах используется несколько серверов, на каждом из которых свои логи. А в крупных проектах тысячи серверов. Для управления такими системы созданы специализированные программы, которые следят за логами на всех машинах, скачивают их, складывают в заточенные под логи базы данных и предоставляют удобный способ поиска по ним.
Здесь тоже есть несколько путей. Можно воспользоваться готовыми решениями, такими как DataDog Logging, либо устанавливать и настраивать все самостоятельно через, например, ELK Stack
Логирование как инструмент повышения стабильности веб-приложения
техлид в Dunice
Каждый проект так или иначе имеет жизненные циклы: планирование, разработка MVP, тестирование, доработка функциональности и поддержка. Скорость роста проектов может отличаться, но при этом желание не сбавлять обороты и двигаться только вперёд у всех одинаковые. Перед нами встаёт вопрос: как при работе над крупным проектом минимизировать время на выявление, отладку и устранение ошибок и при этом не потерять в качество?
Существует много различных инструментов для повышения стабильности проекта:
В данной статье я хочу поговорить об одном из таких инструментов — логировании.
Логи — это файлы, содержащие системную информацию о работе сервера или любой другой программы, в которые вносятся определённые действия пользователя или программы.
Логи полезны для отладки различных частей приложения, а также для сбора и анализа информации о работе системы с целью выявления ошибок. Всё это необходимо для контроля работы приложения, так как даже после релиза могут встретиться ошибки, а пользователи не всегда сообщают о багах в техподдержку. Чем больше процессов у вас автоматизировано, тем быстрее будет идти разработка.
Допустим, есть клиентское приложение, балансировщик в лице Nginx, серверное приложение и база данных.
В данном примере не важны язык/фреймворк бэкенда, фронтенда или тип базы данных, а вот про веб-сервер Nginx давайте поговорим. В данный момент Nginx популярнее остальных решений для высоконагруженных сайтов. Среди известных проектов, использующих Nginx: Рамблер, Яндекс, ВКонтакте, Facebook, Netflix, Instagram, Mail.ru и многие другие. Nginx записывает логи по умолчанию, без каких-либо дополнительных настроек.
Логи доступны 2 типов:
Клиент отправляет запрос на сервер, и в данной ситуации Nginx будет записывать все входящие запросы. Если возникнут ошибки при обработке запросов, сервером будет записана ошибка.
2020/04/10 13:20:49 [error] 4891#4891: *25197 connect() failed (111: Connection refused) while connecting to upstream, client: 5.139.64.242, server: app.dunice-testing.com, request: «GET /api/v1/users/levels HTTP/2.0», upstream: «http://127.0.0.1:5000/api/v1/users/levels», host: «app.dunice-testing.com»
Всё, что мы смогли бы узнать в случае возникновения ошибки, — это лишь факт наличия таковой, не более. Это полезная информация, но мы пойдём дальше. В данной ситуации помог Nginx и его настройки по умолчанию. Но что же нужно сделать, чтобы решить проблему раз и навсегда? Необходимо настроить логирование на сервере, так как он является общей точкой для всех клиентов и имеет доступ к базе данных.
Первым делом каждый запрос должен получать свой уникальный идентификатор, что поможет отличить его от других запросов. Для этого используем UUID/v4. На случай возникновения ошибки, каждый обработчик запроса на сервере должен иметь обёртку, которая отловит эти самые ошибки. В этой ситуации может помочь конструкция try/catch, реализация которой есть в большинстве языков.
В конце каждого запроса должен сохраняться лог об успешной обработке запроса или, если произошла ошибка, сервер должен обработать её и записать следующие данные: ID запроса, все заголовки, тело запроса, параметры запроса, отметку времени и информацию об ошибке (имя, сообщение, трассировка стека).
Собранная информация даст не только понимание, где произошла ошибка, но и возможную причину её возникновения. Обычно для решения ошибки информации из лога достаточно, но в некоторых случаях может быть полезен контекст запроса. Для этого необходимо при старте запроса не только генерировать ID запроса, но и сгенерировать контекст, в который мы будем записывать всю информацию по работе сервера, начиная от результата вызова функции и заканчивая результатом запроса к базе данных. Такая реализация даст не только входные данные, но и промежуточные результаты работы сервера, что позволит понять причину появления ошибки.
При микросервисном подходе система не ограничивается одним сервером, и при запросе от клиента происходит взаимодействие нескольких серверов внутри системы. Наша реализация логирования на сервере позволит выявить дефект в работе конкретного ресурса, но не позволит понять, почему запрос вернулся с ошибкой. В данной ситуации поможет трассировка запросов.
Трассировка — процесс пошагового выполнения программы. В режиме трассировки программист видит последовательность выполнения команд и значения переменных на каждом шаге выполнения программы.
В нашем случае требуется передавать метаинформацию о запросе при взаимодействии серверов и записывать логи в единое хранилище (такими могут быть ClickHouse, Apache Cassandra или MongoDB). Такой подход позволит привязать различные контексты серверов к уникальному идентификатору запроса, а отметки времени — понять последовательность и последнюю выполненную операцию. После этого команда разработки сможет приступить к устранению.
В некоторых случаях, которые встречаются крайне редко, к ошибке приводят неочевидные факторы: компилятор, ядро операционной системы, конфигурации сервера, юзабилити, сеть. В таких случаях при возникновении ошибки потребуется дополнительно сохранять переменные окружения, слепок оперативной памяти и дамп базы. Такие случаи настолько редки, что не стоит беспочвенно акцентировать на них внимание.
С сервером разобрались, что же делать, если у нас сбои даёт клиент и запросы просто не приходят? В такой ситуации нам помогут логи на стороне клиента. Все обработчики должны отправлять информацию на сервер с пометкой, что ошибка с клиента, а также общие сведения: версия и тип браузера, тип устройства и версия операционной системы. Данная информация позволит понять, какой участок кода дал сбой и в каком окружении пользователь взаимодействовал с информацией.
Также есть возможность отправлять уведомления на почту разработчикам, если произошли ошибки, что позволит оперативно узнавать о сбоях в системе. Такие подходы активно используются в системах мониторинга и аналитики логов.
Способы, которые мы рассмотрели в статье, помогут следить за качеством продукта и минимизируют затраты на исправление недочётов в системе.
Логирование является очень важным инструментом разработчика, но при создании распределённых систем оно становится камнем, который нужно заложить прямо в фундамент вашего приложения, иначе сложность разработки микросервисов очень быстро даст о себе знать.
В этой статье мы возьмём простое веб-апи приложение и организуем логирование, которое будет
сохранять сквозную корелляцию между логами независимых сервисов так, чтобы можно было легко посмотреть все активности, которые были вызваны конкретным запросом с клиента
иметь единую точку входа с удобным анализом, чтобы инструментом логирования смогла пользоваться даже Поддержка, к которой летят вопросы вроде «у меня тут в приложении выскочила ошибка с таким-то айдишником запроса»
Во-первых, нам необходимо определиться с поставщиком логирования в нашем приложении. Главное требование к современному логированию это структурность, т.е. мы должны работать не с плоскими текстовыми сообщениями, а с объектами. Благодаря таким логам мы можем легко строить представления наших сообщений в разных разрезах и проводить аналитику.
Для нашего приложения мы воспользуемся пакетом Serilog (Серилог), который имеет отличную поддержку структурного логирования и богатую систему дополнений. Я опущу базовые этапы его настройки (вы можете найти большое количество статей на эту тему) и сделаю допущение о том, что
Серилог уже сконфигурирован и является логером по-умолчанию у вашего поставщика внедрения зависимостей
в его конфигурации включено обогащение сообщений свойствами контекста (Enrich.FromLogContext)
Следующим шагом необходимо выбрать в какую систему централизованного сбора логов посылать сообщения из Serilog. Пожалуй, самый распространённый на сегодня вариант из открытого ПО это стек ELK (Elasticsearch, Logstash и Kibana), его и возьмём. Для этого воспользуемся предложением от Logz.IO — после регистрации на бесплатном тарифе в наших руках оказывается вся мощь поискового движка Lucene.
Нам остаётся добавить в наш проект пакет Serilog.Sinks.Logz.Io
И добавить соответствующий энричер в конфигурацию нашего логера, скормив ему токен доступа
Запустив приложение мы сможем наблюдать наши сообщения не только в консоли, но и в Кибане.
Интерфейсы
В приложении сервисного типа можно выделить два главных интерфейса его взаимодействия с внешним миром, обозначим их как вертикальный и горизонтальный. Вертикальный интерфейс — это веб-апи, через который прилетают вызовы от клиентского приложения. Горизонтальный — это брокер сообщений, который используется для обмена данными с другими внутренними сервисами.
Рассмотрим этапы внедрения корелляционности на каждом из этих интерфейсов.
Корелляция в HTTP-запросах
Чтобы получать как можно больше информации нам необходимо генерировать идентификатор корелляции как можно ближе к началу активности, т.е. на шлюзе или прямо на клиенте (мобильном или веб). Поскольку мы сегодня имеем дело с бекендным приложением, то просто обозначим на нём требование обязательного заголовка «X-Correlation-ID» во всех запросах к веб-апи.
Добавляем пакет CorrelationID, функция которого заключается в заборе значения из необходимого нам заголовка
Добавим его в конвейер обработки запроса
Теперь с его помощью сделаем простой action-фильтр:
И добавим его в контроллер
В результате контроллер станет выводить 400 Bad request на все запросы без заголовка с соответствующим идентификатором.
После того, как мы стали получать идентификатор от клиента мы должны добавить его в контекст журналирования, сделаем для этого обрамляющую прослойку:
В нашем приложении мы используем стандартный ILogger из пакета Microsoft.Extensions.Logging.Abstractions, поэтому значение будем добавлять с помощью нехитрого расширения к нему.
Добавляем прослойку в конвейер обработки запроса и получаем нужный результат.
Теперь все активности, которые порождены запросами к нашему веб-апи, содержат корелляционный идентификатор по которому их можно легко связать.
Корелляция в сообщениях брокера
Следующим шагом нам необходимо наладить передачу и приём корелляционного идентификатора через брокер сообщений. В нашем примере мы будем использовать RabbitMQ, а в качестве клиента возьмём фреймворк MassTransit (МассТранзит). Опять же, опустим первоначальную настройку работы с МассТранзита и перейдём сразу к настройке логирования.
Для начала мы можем включить логи самого МассТранзита, для этого добавим в наше приложение пакет MassTransit.SerilogIntegration
Теперь после добавления логера в настройки MassTransit мы сможем видеть логи фреймворка.
Пусть наше приложение в качестве реакции на POST-запрос отправляет событие SomethingHappened со значением «Hello». Контракт такого сообщения можно описать так:
Сообщения МассТранзита по сути являются конвертом, в который вложены сообщения брокера. Выглядит конверт примерно так:
В сообщении видны служебные поля, которые необходимы для работы самого фреймворка, но мы имеем возможность добавлять в этот конверт и собственные дополнительные свойства. Более того, MassTransit имеет встроенные средства работы с некоторыми опциональными полями, более всего из которых нам интересен идентификатор корелляционности CorrelationId.
Добавим к контракту сообщения интерфейс CorrelatedBy:
Реализуем его и будем присваивать значение свойству CorrelationId при создании сообщения:
Если мы посмотрим на обновлённое сообщение, то увидим что идентификатор корелляции стал не только частью нашего сообщения, но и частью конверта — этот идентификатор теперь будет также использоваться во всех логах МассТранзита, а значит нам будет гораздо проще разбираться с проблемами на уровне брокера сообщений.
Нам осталось настроить логирование этих служебных свойств сообщения, для этого добавим в проект пакет Serilog.Enrichers.MassTransitMessage. Пакет добавляет фильтр в конвейер обработки сообщений MassTransit, который складывает контекст сообщения в потокобезопасный стек. Серилог читает контекст из стека и добавляет в наши объекты логов эти дополнительные свойства.
В МассТранзите вставляем фильтр
А в конфигурации Серилога добавляем энричер
Поскольку приложение, которое получает сообщение из очереди RabbitMQ, имеет доступ ко всем свойствам конверта MassTransit, мы можем использовать полученный идентификатор корелляционности внутри приложения-потребителя, а также передавать его дальше по всей цепочке вызовов.
В результате наши логи стали содержать CorrelationId не только в пределах одного сервиса, но и при взаимодействии с другими приложениями.
Разумеется, в таком виде логирование не покроет сложные варианты взаимодействия ваших сервисов и различных внешних систем, но наведение подобного порядка в самом начале развития проекта — это одна из тех вещей, за которые вы сами себе не раз скажете спасибо.