How to Export Logs, Metrics, and Traces from Azure Container Apps with OpenTelemetry

0
otel

Getting observability data out of Azure Container Apps can be frustrating. The documentation is scattered, and most examples only cover Application Insights. But what if you want to send telemetry to Datadog, Grafana, or your own collector?

Good news: Azure Container Apps has a managed OpenTelemetry agent that makes this surprisingly simple. Here’s a complete, working guide.

This is based on the following guide
Collect and read OpenTelemetry data in Azure Container Apps

What We’re Building

A minimal .NET 10 application that exports logs, metrics, and traces via OpenTelemetry. No complex configuration—Azure handles the heavy lifting.

Time required: About 15 minutes

Prerequisites

  • Azure CLI installed and logged in
  • Docker installed
  • An Azure Container Apps environment (or we’ll create one)
  • A Docker Hub account (or Azure Container Registry)

Step 1: Create the .NET Application (Skip to Step 4 to use the premade image)

Create a new empty web project:

dotnet new web -n OtelDemo
cd OtelDemo

Add the OpenTelemetry packages:

dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore

Replace Program.cs with this:

using System.Diagnostics;
using System.Diagnostics.Metrics;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Custom telemetry sources
var meter = new Meter("MyApp", "1.0.0");
var requestCounter = meter.CreateCounter<int>("myapp.requests");
var activitySource = new ActivitySource("MyApp");

// Configure OpenTelemetry
builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m
        .AddMeter("MyApp")
        .AddMeter("Microsoft.AspNetCore.Hosting")
        .AddMeter("Microsoft.AspNetCore.Server.Kestrel")
        .AddOtlpExporter())
    .WithTracing(t => t
        .AddSource("MyApp")
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());

// Add logging with OTLP
builder.Logging.AddOpenTelemetry(o =>
{
    o.IncludeFormattedMessage = true;
    o.AddOtlpExporter();
});

var app = builder.Build();

app.MapGet("/", (ILogger<Program> logger) =>
{
    using var activity = activitySource.StartActivity("HandleRequest");
    requestCounter.Add(1);
    logger.LogInformation("Request processed at {Time}", DateTime.UtcNow);
    return "Hello OpenTelemetry!";
});

app.Run();

The magic is UseOtlpExporter()—it enables OTLP export for traces, metrics, and logs with one line.

Step 2: Create the Dockerfile

Create a file named Dockerfile in your project folder:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "OtelDemo.dll"]

Step 3: Build and Push to Docker Hub

# Login to Docker Hub
docker login

# Build the image
docker build -t yourusername/otel-demo:v1 .

# Push to Docker Hub
docker push yourusername/otel-demo:v1

Step 4: Deploy to Azure Container Apps

Now the Azure side. First, set your variables (PowerShell):

$RESOURCE_GROUP="your-resource-group"
$ENVIRONMENT="your-environment-name"

Or Bash:

RESOURCE_GROUP="your-resource-group"
ENVIRONMENT="your-environment-name"

Option A: Send to Aspire Dashboard (Great for Testing)

The Aspire Dashboard is a free, easy way to visualize your telemetry.

Deploy the dashboard:

az containerapp create \
  --name aspire-dashboard \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image mcr.microsoft.com/dotnet/aspire-dashboard:latest \
  --target-port 18888 \
  --ingress external \
  --min-replicas 1 \
  --env-vars "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true"

Configure OTLP to send telemetry to it:

az containerapp env telemetry otlp add \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --otlp-name "aspire" \
  --endpoint "http://aspire-dashboard:4317" \
  --insecure true \
  --enable-open-telemetry-traces true \
  --enable-open-telemetry-logs true \
  --enable-open-telemetry-metrics true

Option B: Send to Datadog

Datadog has a dedicated configuration (not the generic OTLP command):

az containerapp env telemetry data-dog set \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --site "datadoghq.com" \
  --key "YOUR_DATADOG_API_KEY" \
  --enable-open-telemetry-traces true \
  --enable-open-telemetry-logs true \
  --enable-open-telemetry-metrics true

Datadog site values by region:

RegionSite Value
US1 (default)datadoghq.com
US3 (Azure)us3.datadoghq.com
US5us5.datadoghq.com
EUdatadoghq.eu
AP1ap1.datadoghq.com

Check your Datadog URL to find your region (e.g., app.us3.datadoghq.com → use us3.datadoghq.com).

Option C: Any OTLP Endpoint (Grafana, Honeycomb, etc.)

Use “http://aspire-dashboard:4317” for the default Aspire Endpoint.

az containerapp env telemetry otlp add \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --otlp-name "myCollector" \
  --endpoint "https://your-otlp-endpoint:4317" \
  --insecure false \
  --headers "Authorization=Bearer YOUR_TOKEN" \
  --enable-open-telemetry-traces true \
  --enable-open-telemetry-logs true \
  --enable-open-telemetry-metrics true

Step 5: Deploy Your Application

Use the image “icefire555/oteldemodotnet8:v1” if you want to use the one I’ve already built and published

az containerapp create \
  --name otel-demo \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image yourusername/otel-demo:v1 \
  --target-port 8080 \
  --ingress external

That’s it! No environment variables needed—Azure automatically injects OTEL_EXPORTER_OTLP_ENDPOINT into your container.

Step 6: Test It

Get your app URL and hit it a few times:

# Get the URL
az containerapp show \
  --name otel-demo \
  --resource-group $RESOURCE_GROUP \
  --query "properties.configuration.ingress.fqdn" -o tsv

# Generate some traffic
curl https://your-app-url.azurecontainerapps.io

Request

This is the page the application will reply with.
This is the page the application will reply with.

Check your dashboard (Aspire, Datadog, etc.)—you should see traces, logs, and metrics appearing within a minute or two.

Console Logs – Aspire

Traces

Metrics

How It Works

The managed OpenTelemetry agent runs at the environment level, not per-app. When you configure it:

  1. Azure deploys a managed collector in your environment
  2. It automatically injects OTEL_EXPORTER_OTLP_ENDPOINT into all your containers
  3. Your app’s OpenTelemetry SDK picks up this endpoint and starts exporting
  4. The agent routes telemetry to your configured destinations

You can even send to multiple destinations simultaneously—for example, Datadog for production monitoring and Aspire Dashboard for local debugging.

Useful Commands

List configured OTLP endpoints:

az containerapp env telemetry otlp list \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP

Remove an OTLP endpoint:

az containerapp env telemetry otlp remove \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --otlp-name "myOtlp" \
  --yes

Check Datadog configuration:

az containerapp env telemetry data-dog show \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP

Remove Datadog:

az containerapp env telemetry data-dog delete \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --yes

Limitations to Know

  • gRPC only: The managed agent only supports gRPC protocol (port 4317)
  • Environment-level config: All apps in the environment share the same OTLP configuration
  • Application Insights quirk: If using App Insights, it only accepts logs and traces—not metrics. Use Datadog or another OTLP endpoint for metrics.

Summary

The managed OpenTelemetry agent makes Azure Container Apps observability much simpler than it used to be. The key points:

  1. Add OpenTelemetry SDK to your .NET app with UseOtlpExporter()
  2. Configure the destination at the environment level (CLI or Bicep)
  3. Deploy your app—no manual environment variables needed

Happy monitoring!

About Author

Leave a Reply

Your email address will not be published. Required fields are marked *