Концепции распределенной трассировки .NET
Распределенная трассировка — это метод диагностики, который помогает инженерам локализовать ошибки и проблемы с производительностью в приложениях, особенно те, которые могут быть распределены между несколькими компьютерами или процессами. См. Обзор распределенной трассировки для получения общей информации о том, где полезна распределенная трассировка.
Отслеживание и действия
Каждый раз, когда приложение получает новый запрос, этот запрос можно связать с трассировкой. В компонентах приложения, написанных в .NET, единицы работы в трассировке представлены экземплярами System.Diagnostics.Activity, а трассировка в целом формирует дерево этих действий, потенциально охватывая множество различных процессов. Первое действие, созданное для нового запроса, формирует корень дерева трассировки и отслеживает общую длительность и успешность или сбой обработки запроса. Дочерние действия можно создать по желанию, чтобы разделить работу на различные шаги, которые можно отслеживать по отдельности. Например, учитывая действие, отслеживающее определенный входящий HTTP-запрос на веб-сервере, можно создать дочерние действия для отслеживания каждого из запросов базы данных, необходимых для выполнения запроса. Это позволяет записывать продолжительность и успешность каждого запроса независимо. Действия могут записывать другие сведения для каждой единицы работы, такие как OperationName, пары "имя-значение", именуемые Tags, и Events. Имя определяет тип выполняемой работы, теги могут записывать описательные параметры работы, а события — простой механизм ведения журнала для записи меток времени диагностических сообщений.
Заметка
В распределенной трассировке другой распространенный термин для единиц работы - это "Спаны". .NET принял термин "Активность" много лет назад, до того как название "Span" стало общепринятым для этого понятия.
Идентификаторы активностей
Parent-Child связи между активностями в распределенном дереве трассировки устанавливаются с помощью уникальных идентификаторов. Реализация распределенной трассировки в .NET поддерживает две схемы идентификаторов: стандарт W3C TraceContext, используемый по умолчанию в .NET 5+, и более старую схему .NET под названием "Иерархическая", доступную для обратной совместимости. Activity.DefaultIdFormat определяет, какая схема идентификатора используется. В стандарте W3C TraceContext каждой трассировке назначается глобально уникальный 16-байтовый идентификатор трассы (trace-id) (Activity.TraceId), а каждой активности в этой трассировке присваивается уникальный 8-байтовый идентификатор интервала (span-id) (Activity.SpanId). Каждое действие записывает trace-id, свой собственный span-id и span-id родительского элемента (Activity.ParentSpanId). Так как распределенные трассировки могут отслеживать работу между границами процесса, родительские и дочерние действия могут не находиться в одном процессе. Сочетание идентификатора трассировки и идентификатора родительского диапазона может однозначно и глобально идентифицировать родительское действие, независимо от того, в каком процессе она находится.
Activity.DefaultIdFormat определяет формат идентификатора, используемый для запуска новых трассировок, но по умолчанию добавление нового действия в существующую трассировку использует любой формат родительского действия. Установка Activity.ForceDefaultIdFormat на значение true переопределяет это поведение и создает все новые действия с параметром DefaultIdFormat, даже если родитель использует другой формат идентификатора.
Запуск и остановка действий
Каждый поток в процессе может иметь соответствующий объект Activity, который отслеживает работу в этом потоке, доступную через Activity.Current. Текущая активность автоматически отслеживает все синхронные вызовы на потоке и следует за асинхронными вызовами, обрабатываемыми на разных потоках. Если действие A является текущим действием в потоке и коде запускает новое действие B, то B становится новым текущим действием в этом потоке. По умолчанию активность B также будет рассматривать активность A как свою родительскую. После остановки действия B действие А будет восстановлено как текущее действие в потоке. При запуске действия оно фиксирует текущее время в качестве Activity.StartTimeUtc. При остановке Activity.Duration вычисляется как разница между текущим временем и временем начала.
Координата между границами процесса
Чтобы отслеживать работу по границам процесса, родительские идентификаторы действий необходимо передавать по сети, чтобы принимающий процесс мог создавать Действия, ссылающиеся на них. При использовании формата идентификатора TraceContext W3C .NET также использует заголовки HTTP, рекомендуемые стандартной для передачи этих сведений. При использовании формата идентификатора Hierarchical .NET использует настраиваемый HTTP-заголовок для идентификатора запроса, чтобы передать идентификатор. В отличие от многих других языковых сред выполнения, встроенные библиотеки .NET, такие как веб-сервер ASP.NET и System.Net.Http изначально понимают, как декодировать и кодировать идентификаторы действий в HTTP-сообщениях. Среда выполнения также понимает, как передавать идентификатор через синхронные и асинхронные вызовы. Это означает, что приложения .NET, получающие и выдавающие HTTP-сообщения, участвуют в автоматическом потоке идентификаторов распределенной трассировки без специального написания кода разработчиком приложения или сторонними зависимостями библиотеки. Сторонние библиотеки могут добавлять поддержку передачи идентификаторов через протоколы сообщений, отличных от HTTP, или поддерживать пользовательские соглашения о кодировке для HTTP.
Сбор трассировок
Инструментируемый код может создавать объекты Activity как часть распределенной трассировки, но сведения в этих объектах необходимо передавать и сериализовать в централизованном постоянном хранилище данных, чтобы всю трассировку можно было бы эффективно просмотреть позже. Существует несколько библиотек сбора данных телеметрии, которые могут выполнять эту задачу, например Application Insights, OpenTelemetryили библиотеку, предоставляемую сторонним поставщиком телеметрии или поставщиком APM. Кроме того, разработчики могут создавать собственную пользовательскую коллекцию телеметрии действий с помощью System.Diagnostics.ActivityListener или System.Diagnostics.DiagnosticListener. ActivityListener поддерживает наблюдение за любым действием независимо от того, имеет ли разработчик какие-либо предварительные знания об этом. Это делает ActivityListener простым и гибким решением общего назначения. В отличие от этого, использование DiagnosticListener представляет собой более сложный сценарий, требующий от инструментированного кода активации путем вызова DiagnosticSource.StartActivity, при этом библиотеке коллекций необходимо знать точные сведения об именах, которые использовал инструментированный код при запуске. Использование DiagnosticSource и DiagnosticListener позволяет создателю и прослушивателю обмениваться произвольными объектами .NET и устанавливать настраиваемые соглашения о передаче информации.
Выборка
Для повышения производительности в приложениях с высокой пропускной способностью распределенная трассировка в .NET поддерживает выборку только подмножества трассировок, а не запись всех из них. Для операций, созданных с помощью рекомендуемого API ActivitySource.StartActivity, библиотеки сбора данных телеметрии могут управлять выбором данных с помощью обратного вызова ActivityListener.Sample. Библиотека ведения журнала может выбрать не создавать объект Activity вообще, создавать его с минимально необходимыми сведениями для передачи идентификаторов трассировки либо заполнить его полными диагностическими данными. Эти решения представляют собой компромисс между увеличением нагрузки на производительность и повышением полезности диагностики. Действия, запущенные с использованием устаревшего шаблона вызова Activity.Activity и DiagnosticSource.StartActivity, также могут поддерживать опробование DiagnosticListener, сначала вызывая DiagnosticSource.IsEnabled. Даже при записи полной диагностической информации реализация .NET спроектирована для быстрой работы — в сочетании с эффективным сборщиком, Activity может быть создан, заполнен и передан примерно за одну микросекунду на современном оборудовании. Выборка может снизить затраты на инструментирование до менее 100 наносекунд для каждого действия, которое не записано.
Дальнейшие действия
Пример кода, чтобы начать использовать распределенную трассировку в приложениях .NET, см. в разделе Инструментирование распределенной трассировки.
Список действий, испускаемых нативно .NET, см. в разделе Встроенные действия в .NET.