Отслеживание пользовательских операций с помощью пакета SDK Application Insights для .NET

Пакеты SDK Application Insights автоматически отслеживают входящие HTTP-запросы и вызовы зависимых служб, например HTTP-запросы и SQL-запросы. Отслеживание и корреляция запросов и зависимостей позволяет контролировать скорость реагирования приложения в целом, а также надежность всех микрослужб, составляющих это приложение.

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

В этой статье содержатся рекомендации по отслеживанию пользовательских операций с помощью пакета SDK Application Insights. Эта документация предназначена для:

  • Application Insights для .NET (базовый пакет SDK) версии 2.4 или более поздней версии;
  • Application Insights для веб-приложений (выполнение ASP.NET) версии 2.4 или более поздней версии;
  • Application Insights для ASP.NET Core версии 2.1 или более поздней версии.

Примечание.

В следующей документации используется классический API Application Insights. Долгосрочный план Application Insights — сбор данных с помощью OpenTelemetry. Дополнительные сведения см. в статье "Включение Azure Monitor OpenTelemetry для .NET", Node.js, приложений Python и Java и нашей стратегии OpenTelemetry. Рекомендации по миграции доступны для .NET, Node.js и Python.

Обзор

Операция — это логический элемент работы, выполняемый приложением. У нее есть имя, время запуска, длительность, результат и контекст выполнения, такой как имя пользователя, свойства и результат. Если операция A была инициирована операцией B, то операция B является родительской для A. У операции может быть только одна родительская операция, но множество дочерних операций. Дополнительные сведения об операциях и корреляции телеметрии см. в разделе Корреляция телеметрии Application Insights.

В пакете SDK Application Insights для .NET операция описывается абстрактным классом OperationTelemetry и его потомками RequestTelemetry и DependencyTelemetry.

Отслеживание входящих операций

Пакет SDK Application Insights автоматически собирает HTTP-запросы для приложений ASP.NET, которые выполняются в конвейере служб IIS, и всех приложений ASP.NET Core. Существуют решения для других платформ, поддержка которых осуществляется силами сообщества разработчиков. Если приложение не поддерживается ни в одном из стандартных или поддерживаемых сообществом решений, его можно инструментирование вручную.

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

Посмотрим, как можно отследить эти операции.

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

HTTP-запрос в резидентном приложении Owin

В этом примере контекст трассировки распространяется согласно протоколу HTTP для корреляции. Следует ожидать заголовки, которые в нем описаны.

public class ApplicationInsightsMiddleware : OwinMiddleware
{
    // You may create a new TelemetryConfiguration instance, reuse one you already have,
    // or fetch the instance created by Application Insights SDK.
    private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
    private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
    
    public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}

    public override async Task Invoke(IOwinContext context)
    {
        // Let's create and start RequestTelemetry.
        var requestTelemetry = new RequestTelemetry
        {
            Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
        };

        // If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
        if (context.Request.Headers.ContainsKey("Request-Id"))
        {
            var requestId = context.Request.Headers.Get("Request-Id");
            // Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
            requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
            requestTelemetry.Context.Operation.ParentId = requestId;
        }

        // StartOperation is a helper method that allows correlation of 
        // current operations with nested operations/telemetry
        // and initializes start time and duration on telemetry items.
        var operation = telemetryClient.StartOperation(requestTelemetry);

        // Process the request.
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception e)
        {
            requestTelemetry.Success = false;
            requestTelemetry.ResponseCode;
            telemetryClient.TrackException(e);
            throw;
        }
        finally
        {
            // Update status code and success as appropriate.
            if (context.Response != null)
            {
                requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
                requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
            }
            else
            {
                requestTelemetry.Success = false;
            }

            // Now it's time to stop the operation (and track telemetry).
            telemetryClient.StopOperation(operation);
        }
    }
    
    public static string GetOperationId(string id)
    {
        // Returns the root ID from the '|' to the first '.' if any.
        int rootEnd = id.IndexOf('.');
        if (rootEnd < 0)
            rootEnd = id.Length;

        int rootStart = id[0] == '|' ? 1 : 0;
        return id.Substring(rootStart, rootEnd - rootStart);
    }
}

Протокол HTTP для корреляции также объявляет заголовок Correlation-Context. Это опущено здесь для простоты.

Инструментирование очереди

Контекст трассировки W3C и ПРОТОКОЛ HTTP для корреляции передают сведения о корреляции с HTTP-запросами, но каждый протокол очереди должен определить, как те же сведения передаются по сообщению очереди. Некоторые протоколы очередей, такие как AMQP, позволяют передавать дополнительные метаданные. Другие протоколы, например очередь служба хранилища Azure, требуют, чтобы контекст был закодирован в полезные данные сообщения.

Примечание.

Трассировка между компонентами пока не поддерживается для очередей.

Если ваш производитель и потребитель отправляют данные телеметрии в разные ресурсы Application Insights, транзакция диагностика опыт и карта приложений отображают транзакции и сопоставляют сквозные транзакции. В случае очередей эта возможность пока не поддерживается.

Очередь служебной шины

Сведения о трассировке см. в разделе "Распределенная трассировка и корреляция" с помощью Служебная шина Azure обмена сообщениями.

Очередь службы хранилища Azure

В следующем примере показано, как отслеживать операции очереди службы хранилища Azure и сопоставлять данные телеметрии производителя, потребителя и службы хранилища Azure.

Очередь службы хранилища использует API HTTP. Все вызовы, отправляемые к очереди, отслеживаются сборщиком зависимостей Application Insights на наличие HTTP-запросов. Он настроен по умолчанию для приложений ASP.NET и ASP.NET Core. Сведения о других типах приложений см. в документации по консольным приложениям.

Кроме того, может потребоваться сопоставить идентификатор операции Application Insights с идентификатором запроса службы хранилища. Сведения о том, как настроить и получить идентификатор клиента для запроса службы хранилища и идентификатор запроса сервера, см. в разделе Мониторинг, диагностика и устранение неполадок с хранилищем Azure.

Постановка в очередь

Так как очереди службы хранилища Azure поддерживают API HTTP, все операции с очередью автоматически отслеживаются Application Insights. Во многих случаях этого инструментария должно быть достаточно. Чтобы сопоставить трассировки на стороне потребителя с трассировками производителя, необходимо передать некоторый контекст корреляции аналогично тому, как мы делаем это в протоколе HTTP для корреляции.

В этом примере показано, как отслеживать операцию Enqueue. Вы можете:

  • Сопоставить повторные попытки (если таковые имеются): все они имеют одну общую родительскую операцию, Enqueue. В противном случае они отслеживаются как дочерние элементы входящего запроса. В случае нескольких логических запросов к очереди может оказаться затруднительным определить, какой вызов совершался повторно.
  • Сопоставить журналы службы хранилища Azure (при необходимости): они сопоставляются с данными телеметрии Application Insights.

Операция Enqueue является дочерним элементом родительской операции. Примером является входящий HTTP-запрос. Вызов зависимостей HTTP является дочерним Enqueue элементом операции и внуком входящего запроса.

public async Task Enqueue(CloudQueue queue, string message)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("enqueue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Enqueue " + queue.Name;

    // MessagePayload represents your custom message and also serializes correlation identifiers into payload.
    // For example, if you choose to pass payload serialized to JSON, it might look like
    // {'RootId' : 'some-id', 'ParentId' : '|some-id.1.2.3.', 'message' : 'your message to process'}
    var jsonPayload = JsonConvert.SerializeObject(new MessagePayload
    {
        RootId = operation.Telemetry.Context.Operation.Id,
        ParentId = operation.Telemetry.Id,
        Payload = message
    });
    
    CloudQueueMessage queueMessage = new CloudQueueMessage(jsonPayload);

    // Add operation.Telemetry.Id to the OperationContext to correlate Storage logs and Application Insights telemetry.
    OperationContext context = new OperationContext { ClientRequestID = operation.Telemetry.Id};

    try
    {
        await queue.AddMessageAsync(queueMessage, null, null, new QueueRequestOptions(), context);
    }
    catch (StorageException e)
    {
        operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.Telemetry.Success = false;
        operation.Telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}  

Чтобы сократить объем данных телеметрии, передаваемой приложением, или если не требуется отслеживать операцию Enqueue по иным причинам, можно напрямую использовать API Activity.

  • Создайте (и запустите) новый элемент Activity вместо запуска операции Application Insights. Не нужно назначать в нем какие-либо свойства, за исключением имени операции.
  • Сериализируйте yourActivity.Id в полезные данные сообщения вместо operation.Telemetry.Id. Также можно использовать Activity.Current.Id.

Вывести из очереди

Так же, как и Enqueue, фактические HTTP-запросы к очереди службы хранилища Azure автоматически отслеживаются Application Insights. Предположительно операция Enqueue выполняется в родительском контексте, например в контексте входящего запроса. Пакеты SDK Application Insights автоматически сопоставляют такие операции и ее часть HTTP с родительским запросом и другими данными телеметрии в той же области.

С операцией Dequeue все не так просто. Пакет SDK Application Insights автоматически отслеживает HTTP-запросы. Но он не знает контекст корреляции, пока сообщение не будет проанализировано. Невозможно сопоставить HTTP-запрос, чтобы получить сообщение с остальной частью телеметрии, особенно при получении нескольких сообщений.

public async Task<MessagePayload> Dequeue(CloudQueue queue)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("dequeue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Dequeue " + queue.Name;
    
    try
    {
        var message = await queue.GetMessageAsync();
    }
    catch (StorageException e)
    {
        operation.telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.telemetry.Success = false;
        operation.telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }

    return null;
}

Обработка

В следующем примере входящие сообщения отслеживаются таким образом, как входящий HTTP-запрос:

public async Task Process(MessagePayload message)
{
    // After the message is dequeued from the queue, create RequestTelemetry to track its processing.
    RequestTelemetry requestTelemetry = new RequestTelemetry { Name = "process " + queueName };
    
    // It might also make sense to get the name from the message.
    requestTelemetry.Context.Operation.Id = message.RootId;
    requestTelemetry.Context.Operation.ParentId = message.ParentId;

    var operation = telemetryClient.StartOperation(requestTelemetry);

    try
    {
        await ProcessMessage();
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        throw;
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}

Другие операции очереди также можно инструментировать. Операцию заглядывания следует инструментировать так же, как операцию выведения из очереди. Инструментировать операции управления очередью необязательно. Application Insights отслеживает такие операции, как HTTP, и в большинстве случаев этого достаточно.

При инструментировании удаления сообщения убедитесь в том, что заданы идентификаторы операции (корреляция). Кроме того, можно использовать API Activity. Вам не нужно задавать идентификаторы операций для элементов телеметрии, так как пакет SDK для Application Insights делает это автоматически.

  • Получив элемент из очереди, создайте Activity.
  • Используйте Activity.SetParentId(message.ParentId) для сопоставления журналов потребителя и производителя.
  • Запустите Activity.
  • Отслеживайте операции выведения из очереди, обработки и удаления с помощью вспомогательных методов Start/StopOperation. Это необходимо выполнить из одного и того же асинхронного потока управления (контекста выполнения). Таким образом они будут сопоставлены должным образом.
  • Остановите Activity.
  • Используйте Start/StopOperation или вызовите Track данные телеметрии вручную.

Типы зависимостей

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

  • Azure queueдля очередей служба хранилища Azure
  • Azure Event Hubs для Центров событий Azure
  • Azure Service Bus для Служебной шины Azure

Пакетная обработка

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

Обработка каждого сообщения должна выполняться в собственном асинхронном потоке управления. Дополнительные сведения см. в разделе Отслеживание исходящих зависимостей.

Длительные фоновые задачи

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

async Task BackgroundTask()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
    operation.Telemetry.Type = "Background";
    try
    {
        int progress = 0;
        while (progress < 100)
        {
            // Process the task.
            telemetryClient.TrackTrace($"done {progress++}%");
        }
        // Update status code and success as appropriate.
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        // Update status code and success as appropriate.
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

В этом примере telemetryClient.StartOperation создает DependencyTelemetry и заполняет контекст корреляции. Предположим, имеется родительская операция, которая была создана входящими запросами, запланировавшими эту операцию. Так как BackgroundTask запускается в том же асинхронном потоке управления, что и входящий запрос, он сопоставляется с этой родительской операцией. BackgroundTask и все вложенные элементы телеметрии автоматически сопоставляются с запросом, вызвавшим ее, даже после завершения запроса.

Если задача запущена из фонового потока, с которым не связана ни одна операция (Activity), у задачи BackgroundTask отсутствуют какие-либо родительские элементы. Тем не менее у нее могут быть вложенные операции. Все элементы телеметрии, полученные от этой задачи, сопоставляются с элементом DependencyTelemetry, созданным в BackgroundTask.

Отслеживание исходящих зависимостей

Можно отслеживать собственный тип зависимостей или операцию, не поддерживаемую Application Insights.

Примером такого настраиваемого отслеживания может служить метод Enqueue в очереди служебной шины или очереди службы хранилища Azure.

Общий подход к настраиваемому отслеживанию зависимостей заключается в следующем.

  • TelemetryClient.StartOperation Вызовите метод (extension), который заполняет DependencyTelemetry свойства, необходимые для корреляции, и некоторые другие свойства, такие как начальная, метка времени и длительность.
  • Задайте другие настраиваемые свойства DependencyTelemetry, например имя и любой контекст, который требуется.
  • Создайте вызов зависимости и дождитесь его выполнения.
  • Остановите операцию после завершения с помощью StopOperation.
  • Обработка исключений.
public async Task RunMyTaskAsync()
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("task 1"))
    {
        try 
        {
            var myTask = await StartMyTaskAsync();
            // Update status code and success as appropriate.
        }
        catch(...) 
        {
            // Update status code and success as appropriate.
        }
    }
}

Удаление операции приводит к остановке операции, поэтому ее можно сделать вместо вызова StopOperation.

Предупреждение

В некоторых случаях необработаемое исключение может препятствовать finally вызову, поэтому операции могут не отслеживаться.

Обработка и отслеживание параллельных операций

Вызов StopOperation останавливает только запущенную операцию. Если текущая выполняемая операция не совпадает с операцией, которую нужно остановить, то StopOperation не выполняет никаких действий. Эта ситуация может произойти при параллельном запуске нескольких операций в одном контексте выполнения.

var firstOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 1");
var firstTask = RunMyTaskAsync();

var secondOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 2");
var secondTask = RunMyTaskAsync();

await firstTask;

// FAILURE!!! This will do nothing and will not report telemetry for the first operation
// as currently secondOperation is active.
telemetryClient.StopOperation(firstOperation); 

await secondTask;

Убедитесь, что вы всегда вызываете StartOperation и обрабатываете операцию в одном асинхронном методе, чтобы изолировать операции, выполняемые параллельно. Если операция синхронна (или не асинхронна), обтекайте процесс и отслеживайте его.Task.Run

public void RunMyTask(string name)
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>(name))
    {
        Process();
        // Update status code and success as appropriate.
    }
}

public async Task RunAllTasks()
{
    var task1 = Task.Run(() => RunMyTask("task 1"));
    var task2 = Task.Run(() => RunMyTask("task 2"));
    
    await Task.WhenAll(task1, task2);
}

Операции ApplicationInsights и System.Diagnostics.Activity

System.Diagnostics.Activity представляет распределенный контекст трассировки и используется платформами и библиотеками для создания и распространения контекста внутри и вне процесса и корреляции элементов телеметрии. Activity работает вместе с System.Diagnostics.DiagnosticSource механизмом уведомлений между платформой или библиотекой, чтобы уведомить о интересных событиях, таких как входящие или исходящие запросы и исключения.

Действия — это граждане первого класса в Application Insights. Автоматическая зависимость и сбор запросов сильно зависят от них вместе с событиями DiagnosticSource . Если вы создали в приложении, это не приведет к созданию Activity телеметрии Application Insights. Application Insights должен получать DiagnosticSource события и знать имена событий и полезные данные для преобразования Activity в данные телеметрии.

Каждая операция Application Insights (запрос или зависимость) включает в себя Activity. При StartOperation вызове он создает Activity под ним. Операцию StartOperation рекомендуется использовать для отслеживания телеметрий запроса или зависимости вручную, чтобы обеспечить корреляцию всех элементов.

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