> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Implementando o OpenTelemetry em Azure Functions
- URL: https://www.azurebrasil.cloud/blog/implementando-o-opentelemetry-em-azure-functions/
- Published: 2025-07-16T19:33:00.000Z
- Updated: 2025-07-17T12:59:12.000Z
- Author: Talles Valiatti

O [OpenTelemetry](https://learn.microsoft.com/pt-br/dotnet/core/diagnostics/observability-with-otel?wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud) é um conjunto de especificações, APIs e SDKs de código aberto que permitem a coleta padronizada de métricas, traces e logs em aplicações distribuídas. Neste post, vamos implementar o [OpenTelemetry](https://opentelemetry.io/?ref=azurebrasil.cloud) em um projeto de [Azure Functions escrito em C# no modelo isolated](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=hostbuilder%2Cwindows&wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud), garantindo observabilidade completa das nossas functions. Para mais detalhes sobre como as Azure Functions funcionam, confira a [documentação oficial](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud).

Vamos começar criando um diretório raiz para o nosso projeto:

```bash
mkdir AzureFunctionsOpenTelemetry
cd AzureFunctionsOpenTelemetry

```

Dentro dele, inicialize uma [solution .NET](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln?wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud) e adicione um arquivo .gitignore.>

```bash
dotnet new sln
```

```bash
dotnet new gitignore
```

Agora inicialize o projeto de [Azure Functions usando o runtime isolado do .NET](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=macos%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&pivots=programming-language-csharp&wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud):

```bash
func init Functions --worker-runtime dotnet-isolated 

```

Adicione o projeto recém-criado à *solution*:

```bash
dotnet sln add Functions/Functions.csproj

```

Gere uma [*function* do tipo Timer Trigger](https://learn.microsoft.com/en-au/azure/azure-functions/functions-bindings-timer?tabs=python-v2%2Cisolated-process%2Cnodejs-v4&pivots=programming-language-csharp&wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud) chamada `MyTimerTrigger`:

```bash
func new --template "Timer Trigger" --name MyTimerTrigger

```

Edite o arquivo `Functions/MyTimerTrigger.cs` para ficar assim:

```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Functions;

public class MyTimerTrigger(ILoggerFactory loggerFactory)
{
    private readonly ILogger _logger = loggerFactory.CreateLogger<MyTimerTrigger>();

    [Function("MyTimerTrigger")]
    public void Run([TimerTrigger("*/10 * * * * *")] TimerInfo myTimer)
    {
        _logger.LogInformation("C# Timer trigger function executed at: {executionTime}", DateTime.Now);
    }
}
```

Execute `func start` para verificar que a trigger dispara a cada 10 segundos.

```bash
func start
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-22.53.50.png)

O nosso projeto de functions está funcionando. Vamos agora implementar o OpenTelemetry!

Para visualizar métricas e traces, vamos criar um [dashboard Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone?tabs=bash&wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud) via Docker Compose. Crie um arquivo `docker-compose.yaml` com o seguinte conteúdo: 

```yaml
version: '3.8'
name: mySuperApp.Functions
services:
  aspire-dashboard:
    image: mcr.microsoft.com/dotnet/aspire-dashboard:9.0
    container_name: aspire-dashboard
    ports:
      - "18888:18888"
      - "4317:18889"
    environment:
      - ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true
    restart: unless-stopped
```

Suba o container:

```bash
docker compose up -d
```

Nos logs do *container* você verá uma URL como `http://localhost:18888/login?t=<token>`. Acesse-a para abrir o dashboard.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.00.33.png)

```bash
http://localhost:18888/login?t=aaf06887b9142d8b7897b84ff4fa009e
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.00.54.png)

Nosso dashboard está fucionando!

De volta ao projeto de *Functions*, adicione o [exporter OTLP](https://www.nuget.org/packages/OpenTelemetry.Exporter.OpenTelemetryProtocol?ref=azurebrasil.cloud):

```bash
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
```

Crie a classe `Functions/ConfigurationExtensions.cs` com os seguintes [métodos de extensão](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-implement-and-call-a-custom-extension-method?wt.mc%5Fid=MVP%5F407589&ref=azurebrasil.cloud):

```csharp
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace Functions;

public static class ConfigurationExtensions
{
    public static void AddLogging(this FunctionsApplicationBuilder builder, string appName)
    {
        builder.Logging.AddOpenTelemetry(options =>
        {
            options.AddOtlpExporter();
            options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(appName)); 
            options.IncludeFormattedMessage = true;
            options.ParseStateValues = true;
            options.IncludeScopes = true; 
        });
    }
    
    public static void AddConfigurations(
        this IServiceCollection services,
        IConfiguration configuration,
        string appName)
    {
        
    
        var tp = Sdk.CreateTracerProviderBuilder()
            .AddSource("Microsoft.Azure.Functions.Worker")
            .SetSampler(new AlwaysOnSampler())
            .ConfigureResource(configure => configure
                .AddService(serviceName: appName))
            .AddOtlpExporter()
            .Build();
        
        services.AddSingleton(tp);
    }
}
```

Na classe acima, temos:

- Define métodos de extensão para configuração em projetos Azure Functions.
- Permite configurar logging distribuído com OpenTelemetry para a aplicação.
- No método AddLogging:
  - Adiciona um exportador OTLP para logs.
  - Configura o recurso com o nome da aplicação (appName) para identificação.
- No método AddConfigurations:
  - Cria e configura um TracerProvider.
  - Usa AlwaysOnSampler para capturar todas os traces.
  - Adiciona exportação OTLP para traces.
  - Configura o recurso de traces com o nome da aplicação (appName).
  - Adiciona o TracerProvider como singleton no container de injeção de dependência.

Com isso, podemos modificar o Program.cs para utilizar as configurações acima:

```csharp
using Functions;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

string appName = "MySuperApp.Functions";

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication()
    .AddLogging(appName);;

builder.Services
    .AddApplicationInsightsTelemetryWorkerService()
    .ConfigureFunctionsApplicationInsights()
    .AddConfigurations(builder.Configuration, appName);;

builder.Build().Run();

```

Voltando a function, vamos modifica-lá:

```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Functions;

public class MyTimerTrigger(ILoggerFactory loggerFactory) : BaseFunctionActivityManager
{
    private readonly ILogger _logger = loggerFactory.CreateLogger<MyTimerTrigger>();

    [Function("MyTimerTrigger")]
    public void Run([TimerTrigger("*/10 * * * * *")] TimerInfo myTimer, FunctionContext fc)
    {
        using var activity = StartFunctionActivity(fc, nameof(MyTimerTrigger));

        try
        {
            // Do some work here
            using (StartActivity("Activity 1"))
            {
                _logger.LogInformation("Task 1 ...: {Time}", DateTime.UtcNow);
            }
            
            using (StartActivity("Activity 2"))
            {
                _logger.LogInformation("Task 2 ...: {Time}", DateTime.UtcNow);
               
                // Activity 2 can have nested activities
                using (StartActivity("Activity 3"))
                {
                    _logger.LogInformation("Task 3 ...: {Time}", DateTime.UtcNow);
                }

            }
            
            FinishFunctionActivity(activity);
        }
        catch (Exception e)
        {
            FinishFunctionActivity(activity, failure: true);
            _logger.LogError(e, "error occurred while executing the timer function");
        }
    }
}
```

Alguns detalhes da implementação:

- Azure Function do tipo Timer Trigger que executa a cada 10 segundos.
- Utiliza um logger do ILoggerFactory para gerar logs com contexto.
- Herda de BaseFunctionActivityManager (presumidamente para gerenciar traces e atividades de monitoramento).
- O método Run:
  - Inicia uma atividade/trace principal para a função com StartFunctionActivity.
  - Executa tarefas em sequência, cada uma envolvida em uma atividade/trace distinta (“Activity 1”, “Activity 2”, “Activity 3”).
  - Faz log informativo em cada etapa, incluindo o horário de execução.
  - Permite criação de atividades aninhadas para melhor rastreamento (“Activity 3” está dentro de “Activity 2”).
  - Finaliza a atividade principal tanto ao final da execução normal (FinishFunctionActivity) quanto em caso de exceção (indicando falha).
  - Registra erro em log se algo der errado, detalhando a exceção.

Além disso, temos a classe BaseFunctionActivityManager.cs

```csharp
using System.Diagnostics;
using Microsoft.Azure.Functions.Worker;

namespace Functions;

public abstract class BaseFunctionActivityManager
{
    protected static Activity? StartFunctionActivity(FunctionContext fc, string functionName)
    {
        var manualInstrumentationSource = Activity.Current?.Source;
        
        if (manualInstrumentationSource == null)
        {
            return null;
        }
        
        var activity = manualInstrumentationSource.StartActivity(functionName);
        activity?.AddTag("invocation_id", fc.InvocationId);
        activity?.AddTag("created_at", DateTime.UtcNow.ToString("o"));
        return activity;
    }
    
    protected static Activity? StartActivity(string name)
    {
        var manualInstrumentationSource = Activity.Current?.Source;
        
        if (manualInstrumentationSource == null)
        {
            return null;
        }
        
        var activity = manualInstrumentationSource.StartActivity(name);
        return activity;
    }
    
    protected static void FinishFunctionActivity(Activity? activity, bool failure = false)
    {
        activity?.AddTag("completed_at", DateTime.UtcNow.ToString("o"));
        activity?.AddTag("status", failure ? "failure" : "success");
        activity?.SetStatus(failure ? ActivityStatusCode.Error : ActivityStatusCode.Ok);
    }
}
```

- Classe abstrata que facilita gerenciamento de atividades (traces) nas Azure Functions.
- Expõe métodos utilitários estáticos para criação, marcação e finalização de “*Activities*”:
  - **StartFunctionActivity**:
    - Inicia uma nova Activity (span) para monitoramento de uma função.
    - Usa o nome da função como identificador.
    - Adiciona tags ao trace, como o `invocation_id` e o timestamp (`created_at`).
    - Retorna a *Activity* criada (ou null, se não houver fonte de instrumentação ativa).
  - **StartActivity**:
    - Inicia uma Activity genérica com o nome especificado.
    - Retorna a Activity criada (ou null).
  - **FinishFunctionActivity**:
    - Marca a Activity como finalizada, adicionando tags de conclusão (`completed_at`) e status (`success` ou `failure`).
    - Define o status apropriado (Ok ou Error) para a Activity, de acordo com o parâmetro de falha.

Vamos executar novamente as *function*. Podemos ver os logs gerados no console:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.32.51.png)

Já no dashboard, podemos ver os *tracings* enviados pelas *functions*:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.33.16.png)

No segundo span, gerado via *StartFunctionActivity,* podemos ver as tags definidas anteriormente:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.33.46.png)

Além disso, podemos ver os logs relativos a cada span:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.36.39.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.35.42.png)

Vamos fazer uma pequena modificação no código:

```csharp
 using (StartActivity("Activity 3"))
                {
                    throw new Exception("Error: This is a test exception in Activity 3");
                    _logger.LogInformation("Task 3 ...: {Time}", DateTime.UtcNow);
                }
```

- Foi adicionada uma linha que lança uma exceção propositalmente:
  - `throw new Exception("Error: This is a test exception in Activity 3");`
- A registro de log (`_logger.LogInformation(...)`) que vinha na sequência agora nunca será executada, pois está abaixo do `throw` — a exceção interrompe imediatamente a execução do bloco.
- Com essa alteração, sempre que “*Activity* 3” for executada, ocorrerá uma exceção, simulando um erro para fins de teste.

Executando novamente o projeto, podemos ver o error fica bem evidente:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.38.39.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.39.08.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.40.39.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2025/07/Screenshot-2025-07-15-at-23.40.56.png)

Você já pode baixar o projeto por esse [link](https://github.com/TallesValiatti/AzureFunctionsOpenTelemetry?ref=azurebrasil.cloud), e não esquece de me seguir no [LinkedIn](https://www.linkedin.com/in/tallesvaliatti/?ref=azurebrasil.cloud)!

Até a próxima, abraços!