Compartilhar via


Tutorial: Monitorar o Azure Functions com o rastreamento distribuído OpenTelemetry

Este artigo demonstra o suporte ao OpenTelemetry no Azure Function, que permite o rastreamento distribuído em várias chamadas de função usando o suporte integrado ao Application Insights e ao OpenTelemetry. Para ajudá-lo a começar, um modelo do Azure Developer CLI (azd) é usado para criar seu projeto de código, bem como a implantação do Azure na qual você executará seu aplicativo.

Neste tutorial, você usará a azd ferramenta para:

  • Inicialize um projeto com suporte a OpenTelemetry a partir de um modelo.
  • Examine o código que habilita a integração do OpenTelemetry.
  • Execute e verifique seu aplicativo habilitado para OpenTelemetry localmente.
  • Crie um aplicativo de funções e recursos relacionados no Azure.
  • Implante seu projeto de código no aplicativo de funções no Azure.
  • Verifique o rastreamento distribuído no Application Insights.

Os recursos necessários do Azure criados por este modelo seguem as práticas recomendadas atuais para implantações de aplicativos de funções seguras e escalonáveis no Azure. O mesmo azd comando também implanta seu projeto de código em seu novo aplicativo de funções no Azure.

Por padrão, o plano de Consumo Flexível segue um modelo de cobrança pague pelo que usar, o que significa que a conclusão deste início rápido gera um pequeno custo de alguns centavos de dólar ou menos na sua conta do Azure.

Importante

Atualmente, este artigo dá suporte apenas a C#, Python e TypeScript. Para concluir o início rápido, selecione um desses idiomas com suporte na parte superior do artigo.

Pré-requisitos

Inicializar o projeto

Use o azd init comando para criar um projeto de código do Azure Functions local a partir de um modelo que inclui o rastreamento distribuído OpenTelemetry.

  1. No seu terminal ou prompt de comando local, execute esse azd init comando em uma pasta vazia:

    azd init --template functions-quickstart-python-azd-otel -e flexquickstart-otel
    

    Esse comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O sinalizador -e define um nome para o ambiente atual. Em azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que você cria no Azure.

  1. No seu terminal ou prompt de comando local, execute esse azd init comando em uma pasta vazia:

    azd init --template functions-quickstart-typescript-azd-otel -e flexquickstart-otel
    

    Esse comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O sinalizador -e define um nome para o ambiente atual. Em azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que você cria no Azure.

  1. No seu terminal ou prompt de comando local, execute esse azd init comando em uma pasta vazia:

    azd init --template functions-quickstart-dotnet-azd-otel -e flexquickstart-otel
    

    Esse comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O sinalizador -e define um nome para o ambiente atual. Em azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que você cria no Azure.

Examine o código

O modelo cria um cenário de rastreamento distribuído completo com três funções que funcionam juntas. Vamos examinar os principais aspectos relacionados ao OpenTelemetry:

Configuração do OpenTelemetry

O src/otel-sample/host.json arquivo habilita o OpenTelemetry para o host do Functions:

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "extensions": {
    "serviceBus": {
        "maxConcurrentCalls": 10
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

A configuração de chave "telemetryMode": "OpenTelemetry" habilita o rastreamento distribuído entre chamadas de função.

O src/OTelSample/host.json arquivo habilita o OpenTelemetry para o host do Functions:

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "logging": {
    "OpenTelemetry": {
      "logLevel": {
        "Host.General": "Warning"
      }
    }
  }
}

A configuração chave "telemetryMode": "OpenTelemetry" permite o rastreamento distribuído entre chamadas de função.

Dependências do OpenTelemetry

O src/otel-sample/requirements.txt arquivo inclui os pacotes necessários para a integração do OpenTelemetry:

azure-functions
azure-monitor-opentelemetry
requests

O azure-monitor-opentelemetry pacote fornece a integração do OpenTelemetry com o Application Insights.

O src/otel-sample/package.json arquivo inclui os pacotes necessários para a integração do OpenTelemetry:

{
  "dependencies": {
    "@azure/functions": "^4.0.0",
    "@azure/functions-opentelemetry-instrumentation": "^0.1.0",
    "@azure/monitor-opentelemetry-exporter": "^1.0.0",
    "axios": "^1.6.0"
  }
}

Os pacotes @azure/functions-opentelemetry-instrumentation e @azure/monitor-opentelemetry-exporter fornecem a integração do OpenTelemetry com o Application Insights.

O .csproj arquivo inclui os pacotes necessários para a integração do OpenTelemetry:

<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.OpenTelemetry" Version="1.4.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />

Esses pacotes fornecem a integração do OpenTelemetry com o Application Insights e a instrumentação HTTP para rastreamento distribuído.

Implementação de função

As funções em src/otel-sample/function_app.py demonstram um fluxo de rastreamento distribuído:

Primeira função HTTP

@app.function_name("first_http_function")
@app.route(route="first_http_function", auth_level=func.AuthLevel.ANONYMOUS)
def first_http_function(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (first) processed a request.')

    # Call the second function
    base_url = f"{req.url.split('/api/')[0]}/api"
    second_function_url = f"{base_url}/second_http_function"

    response = requests.get(second_function_url)
    second_function_result = response.text

    result = {
        "message": "Hello from the first function!",
        "second_function_response": second_function_result
    }

    return func.HttpResponse(
        json.dumps(result),
        status_code=200,
        mimetype="application/json"
    )

Segunda função HTTP

@app.function_name("second_http_function")
@app.route(route="second_http_function", auth_level=func.AuthLevel.ANONYMOUS)
@app.service_bus_queue_output(arg_name="outputsbmsg", queue_name="%ServiceBusQueueName%",
                              connection="ServiceBusConnection")
def second_http_function(req: func.HttpRequest, outputsbmsg: func.Out[str]) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (second) processed a request.')

    message = "This is the second function responding."

    # Send a message to the Service Bus queue
    queue_message = "Message from second HTTP function to trigger ServiceBus queue processing"
    outputsbmsg.set(queue_message)
    logging.info('Sent message to ServiceBus queue: %s', queue_message)

    return func.HttpResponse(
        message,
        status_code=200
    )

Gatilho da Fila do Barramento de Serviço

@app.service_bus_queue_trigger(arg_name="azservicebus", queue_name="%ServiceBusQueueName%",
                               connection="ServiceBusConnection") 
def servicebus_queue_trigger(azservicebus: func.ServiceBusMessage):
    logging.info('Python ServiceBus Queue trigger start processing a message: %s',
                azservicebus.get_body().decode('utf-8'))
    time.sleep(5)  # Simulate processing work
    logging.info('Python ServiceBus Queue trigger end processing a message')

A configuração do OpenTelemetry está configurada em src/otel-sample/index.ts:

import { AzureFunctionsInstrumentation } from '@azure/functions-opentelemetry-instrumentation';
import { AzureMonitorTraceExporter, AzureMonitorLogExporter } from '@azure/monitor-opentelemetry-exporter';
import { getNodeAutoInstrumentations, getResourceDetectors } from '@opentelemetry/auto-instrumentations-node';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { detectResources } from '@opentelemetry/resources';
import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';

const resource = detectResources({ detectors: getResourceDetectors() });

const tracerProvider = new NodeTracerProvider({ 
  resource, 
  spanProcessors: [new SimpleSpanProcessor(new AzureMonitorTraceExporter())] 
});
tracerProvider.register();

const loggerProvider = new LoggerProvider({
  resource,
  processors: [new SimpleLogRecordProcessor(new AzureMonitorLogExporter())],
});

registerInstrumentations({
    tracerProvider,
    loggerProvider,
    instrumentations: [getNodeAutoInstrumentations(), new AzureFunctionsInstrumentation()],
});

As funções são definidas na src/otel-sample/src/functions pasta:

Primeira função HTTP

export async function firstHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (first) processed a request.");

  try {
    // Call the second function
    const baseUrl = request.url.split("/api/")[0];
    const secondFunctionUrl = `${baseUrl}/api/second_http_function`;

    const response = await axios.get(secondFunctionUrl);
    const secondFunctionResult = response.data;

    const result = {
      message: "Hello from the first function!",
      second_function_response: secondFunctionResult,
    };

    return {
      status: 200,
      body: JSON.stringify(result),
      headers: { "Content-Type": "application/json" },
    };
  } catch (error) {
    return {
      status: 500,
      body: JSON.stringify({ error: "Failed to process request" }),
    };
  }
}

Segunda função HTTP

export async function secondHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (second) processed a request.");

  const message = "This is the second function responding.";

  // Send a message to the Service Bus queue
  const queueMessage =
    "Message from second HTTP function to trigger ServiceBus queue processing";

  context.extraOutputs.set(serviceBusOutput, queueMessage);
  context.log("Sent message to ServiceBus queue:", queueMessage);

  return {
    status: 200,
    body: message,
  };
}

Gatilho da Fila do Barramento de Serviço

export async function serviceBusQueueTrigger(
  message: unknown,
  context: InvocationContext
): Promise<void> {
  context.log("TypeScript ServiceBus Queue trigger start processing a message:", message);

  // Simulate processing time
  await new Promise((resolve) => setTimeout(resolve, 5000));

  context.log("TypeScript ServiceBus Queue trigger end processing a message");
}

A configuração do OpenTelemetry está configurada em src/OTelSample/Program.cs:

using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
using OpenTelemetry.Trace;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()    
    .WithTracing(tracing =>
    {
        tracing.AddHttpClientInstrumentation();
    });

builder.Services.AddOpenTelemetry().UseAzureMonitorExporter();
builder.Services.AddOpenTelemetry().UseFunctionsWorkerDefaults();

builder.Services.AddHttpClient();

builder.Build().Run();

As funções são definidas em arquivos de classe separados:

Primeira função HTTP

public class FirstHttpTrigger
{
    private readonly ILogger<FirstHttpTrigger> _logger;
    private readonly IHttpClientFactory _httpClientFactory;

    public FirstHttpTrigger(ILogger<FirstHttpTrigger> logger, IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
    }

    [Function("first_http_function")]
    public async Task<IActionResult> Run(
         [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("first_http_function function processed a request.");

        var baseUrl = $"{req.Url.AbsoluteUri.Split("/api/")[0]}/api";
        var targetUri = $"{baseUrl}/second_http_function";

        var client = _httpClientFactory.CreateClient();
        var response = await client.GetAsync(targetUri);
        var content = await response.Content.ReadAsStringAsync();

        return new OkObjectResult($"Called second_http_function, status: {response.StatusCode}, content: {content}");
    }
}

Segunda função HTTP

public class SecondHttpTrigger
{
    private readonly ILogger<SecondHttpTrigger> _logger;

    public SecondHttpTrigger(ILogger<SecondHttpTrigger> logger)
    {
        _logger = logger;
    }

    [Function("second_http_function")]
    public MultiResponse Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("second_http_function function processed a request.");

        return new MultiResponse
        {
            Messages = new string[] { "Hello" },
            HttpResponse = req.CreateResponse(System.Net.HttpStatusCode.OK)
        };
    }
}

public class MultiResponse
{
    [ServiceBusOutput("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
    public string[]? Messages { get; set; }

    [HttpResult]
    public HttpResponseData? HttpResponse { get; set; }
}

Gatilho da Fila do Barramento de Serviço

public class ServiceBusQueueTrigger
{
    private readonly ILogger<ServiceBusQueueTrigger> _logger;

    public ServiceBusQueueTrigger(ILogger<ServiceBusQueueTrigger> logger)
    {
        _logger = logger;
    }

    [Function("servicebus_queue_trigger")]
    public async Task Run(
        [ServiceBusTrigger("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions)
    {
        _logger.LogInformation("Message ID: {id}", message.MessageId);
        _logger.LogInformation("Message Body: {body}", message.Body);

        // Complete the message
        await messageActions.CompleteMessageAsync(message);
    }
}

Fluxo de rastreamento distribuído

Essa arquitetura cria um cenário de rastreamento distribuído completo, com esse comportamento:

  1. A primeira função HTTP recebe uma solicitação HTTP e chama a segunda função HTTP
  2. A segunda função HTTP responde e envia uma mensagem para o Service Bus
  3. O gatilho do Service Bus processa a mensagem com um atraso para simular o trabalho de processamento

Principais aspectos da implementação do OpenTelemetry:

  • Integração do OpenTelemetry: o host.json arquivo habilita o OpenTelemetry com "telemetryMode": "OpenTelemetry"
  • Encadeamento de funções: a primeira função chama a segunda usando solicitações HTTP, criando rastreamentos correlacionados
  • Integração com o Barramento de Serviço: a segunda função gera saída para o Barramento de Serviço, que aciona a terceira função
  • Autenticação anônima: as funções HTTP usam auth_level=func.AuthLevel.ANONYMOUS, portanto, nenhuma chave de função é necessária

Você pode examinar o projeto de modelo completo aqui.

  • Integração do OpenTelemetry: o index.ts arquivo configura o OpenTelemetry com exportadores do Azure Monitor para rastreamentos e logs
  • Encadeamento de funções: a primeira função chama a segunda usando o axios com propagação de rastreamento automática
  • Integração do Barramento de Serviço: a segunda função envia dados para o Barramento de Serviço usando vínculos de saída, o que aciona a terceira função.
  • Identidade gerenciada: Todas as conexões do Barramento de Serviço usam identidade gerenciada em vez de cadeias de conexão
  • Simulação de processamento: o atraso de 5 segundos no gatilho do Barramento de Serviço simula o trabalho de processamento de mensagens

Você pode examinar o projeto de modelo completo aqui.

  • Integração do OpenTelemetry: o Program.cs arquivo configura o OpenTelemetry com o exportador do Azure Monitor
  • Encadeamento de funções: a primeira função chama a segunda usando HttpClient com instrumentação OpenTelemetry
  • Integração com o Barramento de Serviço: a segunda função gera saída para o Barramento de Serviço usando associações de saída, que acionam a terceira função
  • Identidade gerenciada: Todas as conexões do Service Bus usam identidade gerenciada em vez de cadeias de conexão
  • Trabalho Isolado do .NET 8: usa o modelo de trabalho isolado do .NET do Azure Functions mais recente para melhorar o desempenho e a flexibilidade

Você pode examinar o projeto de modelo completo aqui.

Depois de verificar suas funções localmente, é hora de publicá-las no Azure.

Publicar no Azure

Este projeto está configurado para usar o comando azd up para implantar esse projeto em um novo aplicativo de funções em um plano Flex Consumption no Azure com suporte ao OpenTelemetry.

Dica

Este projeto inclui um conjunto de arquivos Bicep que azd usa para criar uma implantação segura em um plano de consumo Flexível que segue as práticas recomendadas, incluindo conexões de identidade gerenciada.

  1. Execute este comando para que azd crie os recursos necessários do Azure no Azure e implante seu projeto de código no novo aplicativo de funções:

    azd up
    

    A pasta raiz contém o arquivo de definição azure.yaml necessário para azd.

    Se você ainda não estiver conectado, será solicitado que você se autentique com sua conta do Azure.

  2. Quando solicitado, forneça esses parâmetros de implantação obrigatórios:

    Parâmetro Description
    Assinatura do Azure Assinatura na qual seus recursos serão criados.
    Localização do Azure Região do Azure na qual criar o grupo de recursos que contém os novos recursos do Azure. Somente regiões que atualmente dão suporte para o plano de Consumo Flex são mostradas.

    O comando azd up usa sua resposta a esses prompts com os arquivos de configuração Bicep para concluir estas tarefas de implantação:

    • Criar e configurar esses recursos do Azure necessários (equivalente a azd provision):

      • Plano de Consumo Flexível do Azure Functions e aplicativo de funções com o OpenTelemetry habilitado
      • Armazenamento do Microsoft Azure (obrigatório) e Application Insights (recomendado)
      • Namespace e fila do Barramento de Serviço para demonstração de rastreamento distribuído
      • Políticas de acesso e funções para sua conta
      • Conexões de serviço a serviço usando identidades gerenciadas (em vez de cadeias de conexão armazenadas)
    • Empacotar e implantar seu código para o contêiner de implantação (equivalente a azd deploy). O aplicativo é então iniciado e executa no pacote implantado.

    Após o comando ser concluído com sucesso, você verá links para os recursos que criou.

Testar o rastreamento distribuído

Agora você pode testar a funcionalidade de rastreamento distribuído OpenTelemetry chamando suas funções implantadas e observando a telemetria no Application Insights.

Invocar a função no Azure

Você pode invocar seus endpoints de função no Azure realizando solicitações HTTP para suas URLs. Como as funções HTTP neste modelo são configuradas com acesso anônimo, nenhuma chave de função é necessária.

  1. No terminal local ou no prompt de comando, execute este comando para obter o nome do aplicativo de funções e construir a URL:

    APP_NAME=$(azd env get-value AZURE_FUNCTION_NAME)
    echo "Function URL: https://$APP_NAME.azurewebsites.net/api/first_http_function"
    

    O comando azd env get-value obtém o nome do seu aplicativo de funções do ambiente local.

  2. Teste a função no navegador navegando até a URL:

    https://your-function-app.azurewebsites.net/api/first_http_function
    

    Substitua your-function-app pelo nome real do aplicativo de funções da etapa anterior. Essa única solicitação cria um rastreamento distribuído que flui pelas três funções.

Exibir rastreamento distribuído no Application Insights

Depois de invocar a função, você pode observar o rastreamento distribuído completo no Application Insights:

Observação

Pode levar alguns minutos para que os dados de telemetria apareçam no Application Insights depois de invocar sua função. Se você não vir dados imediatamente, aguarde alguns minutos e atualize a visualização.

  1. Acesse seu recurso do Application Insights no portal do Azure (você pode encontrá-lo no mesmo grupo de recursos que seu aplicativo de funções).

  2. Abra o mapa do aplicativo para ver o rastreamento distribuído em todas as três funções. Você deverá ver o fluxo da solicitação HTTP por meio das funções e até o Barramento de Serviço.

  3. Verifique a busca de transações para encontrar sua solicitação e ver o cronograma completo de rastreamento. Pesquise transações do aplicativo de funções.

  4. Selecione uma transação específica para ver o rastreamento de ponta a ponta que mostra:

    • A solicitação HTTP para first_http_function
    • A chamada HTTP interna para second_http_function
    • A mensagem do Barramento de Serviço que está sendo enviada
    • O servicebus_queue_trigger que está processando a mensagem do Barramento de Serviço
  5. Nos detalhes do rastreamento, você pode ver:

    • Informações de tempo: quanto tempo cada etapa demorou
    • Dependências: as conexões entre funções
    • Logs: registros de aplicação correlacionados com o rastreamento
    • Métricas de desempenho: tempos de resposta e taxa de transferência

Este exemplo demonstra o rastreamento distribuído de ponta a ponta em várias funções do Azure com a integração do OpenTelemetry, fornecendo visibilidade completa sobre o comportamento e o desempenho do aplicativo.

Reimplantar seu código

Execute o azd up comando quantas vezes precisar para provisionar seus recursos do Azure e implantar atualizações de código em seu aplicativo de funções.

Observação

O pacote de implantação mais recente sempre substitui arquivos de código que já foram implantados.

Suas respostas iniciais aos prompts azd e quaisquer variáveis de ambiente geradas por azd são armazenadas localmente no seu ambiente nomeado. Use o azd env get-values comando para examinar todas as variáveis em seu ambiente que o comando usa ao criar recursos do Azure.

Limpar os recursos

Quando terminar de trabalhar com seu aplicativo de funções e recursos relacionados, use este comando para excluir o aplicativo de funções e seus recursos relacionados do Azure e evitar incorrer em custos adicionais:

azd down --no-prompt

Observação

A opção --no-prompt instrui azd a excluir seu grupo de recursos sem uma confirmação sua.

Este comando não afeta seu projeto de código local.