Концепции распределенной трассировки .NET

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

Трассировки и действия

Каждый раз, когда приложение получает новый запрос, его можно связать с трассировкой. В компонентах приложений, написанных на .NET, единицы работы в трассировке представлены экземплярами System.Diagnostics.Activity, а трассировка в целом образует дерево этих действий, которое может охватывать множество различных процессов. Первое действие, созданное для нового запроса, образует корень дерева трассировки и отслеживает общую длительность и успех или неудачу обработки запроса. При необходимости можно создавать дочерние действия, чтобы разделить работу на шаги, которые можно отслеживать по отдельности. Например, для действия, отслеживающего конкретный входящий HTTP-запрос на веб-сервере, можно создать дочерние действия отслеживания каждого запроса к базе данных, потребовавшегося для выполнения запроса. Это позволяет независимо регистрировать длительность и успешность каждого запроса. Действия могут записывать другие сведения для каждой единицы работы, например OperationName, пары "имя — значение" (Tags) и Events. Имя определяет тип выполняемой работы, теги могут записывать описательные параметры работы, а события представляют собой простой механизм ведения журнала для регистрации диагностических сообщений с метками времени.

Примечание.

Другое распространенное отраслевое название для единиц работы в распределенной трассировке — рабочие интервалы. За много лет до того, как для этой концепции закрепилось название "рабочий интервал", в среде .NET применялся термин "действие".

ИД действий

Связи "родитель — потомок" между действиями в дереве распределенной трассировки устанавливаются с помощью уникальных идентификаторов. Реализация распределенной трассировки в .NET поддерживает две схемы идентификаторов: стандарт TraceContext консорциума W3C, используемый по умолчанию в .NET 5 и более поздних версиях, и более старое соглашение .NET, именуемое иерархическим, которое доступно для обеспечения обратной совместимости. Activity.DefaultIdFormat определяет, какая схема идентификаторов используется. В стандарте TraceContext консорциума W3C каждой трассировке присваивается глобально уникальный 16-байтовый идентификатор трассировки (Activity.TraceId), а каждому действию внутри трассировки присваивается уникальный 8-байтовый идентификатор рабочего интервала (Activity.SpanId). Каждое действие регистрирует идентификатор трассировки, собственный идентификатор рабочего интервала и идентификатор рабочего интервала своего родительского объекта (Activity.ParentSpanId). Так как распределенные трассировки могут отслеживать работу между границами процессов, родительские и дочерние действия могут не находиться в одном процессе. Сочетание идентификатора трассировки и идентификатора рабочего интервала родительского объекта позволяет однозначно идентифицировать родительское действие на глобальном уровне, независимо от того, в каком процессе оно находится.

Activity.DefaultIdFormat определяет, какой формат идентификатора используется для запуска новых трассировок, но по умолчанию при добавлении нового действия в существующую трассировку используется формат, используемый родительским действием. Установка для Activity.ForceDefaultIdFormat значения true переопределяет это поведение и обеспечивает создание всех действий с DefaultIdFormat, даже если родительский объект использует другой формат идентификатора.

Запуск и остановка действий

Каждый поток в процессе может иметь соответствующий объект действия, отслеживающий работу в этом потоке, доступ к которому осуществляется через Activity.Current. Текущее действие автоматически проходит по всем синхронным вызовам в потоке, а также следующим асинхронным вызовам, которые обрабатываются в разных потоках. Если действие А является текущим в потоке и код запускает новое действие Б, то Б становится новым текущим действием в этом потоке. По умолчанию действие Б также расценивает действие А как свой родительский объект. Если позднее действие Б остановлено, действие А будет восстановлено в качестве текущего действия в потоке. При запуске действие фиксирует текущее время как Activity.StartTimeUtc. При его остановке вычисляется Activity.Duration как разница между текущим временем и временем начала.

Координация между границами процессов

Для отслеживания работы между границами процессов идентификаторы родительских действий должны передаваться по сети, чтобы принимающий процесс мог создавать ссылающиеся на них действия. При использовании формата идентификатора TraceContext консорциума W3C среда .NET также использует заголовки HTTP, рекомендованные этим стандартом, для передачи этих данных. При использовании формата идентификатора Hierarchical среда .NET использует настраиваемый заголовок HTTP request-id для передачи идентификатора. В отличие от многих других языковых сред выполнения встроенные библиотеки .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 и DiagnosticSource.StartActivity, могут также поддерживать выборку DiagnosticListener путем первичного вызова DiagnosticSource.IsEnabled. Даже при сборе полной диагностической информации реализация .NET имеет высокое быстродействие — при использовании эффективного сборщика и на современном оборудовании действие может быть создано, заполнено и передано за одну микросекунду. Выборка может снизить временные затраты на инструментирование до менее чем 100 наносекунд для каждого действия, которое не записывается.

Следующие шаги

Пример кода для начала работы с распределенной трассировкой в приложениях .NET см. в статье Распределенная трассировка .NET.