Skip to content
Last9 named a Gartner Cool Vendor in AI for SRE Observability for 2025! Read more →
Last9

Azure Container Apps

Send logs to Last9 from Azure Container Apps using OpenTelemetry

Use OpenTelemetry to instrument your Azure Container Apps and send logs to Last9. This integration uses Azure Event Hubs and OpenTelemetry Collector to stream logs from your container applications.

Prerequisites

  • Last9 cluster created
  • Obtain your OTLP endpoint and auth header: https://app.last9.io/integrations/
  • Azure Container Apps deployment
  • Azure Event Hubs namespace
  • VM or local environment to run OpenTelemetry Collector

Setup Azure Resources

  1. Enable container app logging

    1. Navigate to your Azure Container Apps Environment
    2. Enable logging to Azure Monitor
    3. Note the region where your container app is deployed
  2. Create Event Hubs namespace

    1. Create Event Hubs namespace with name last9 in the same region as your container app
    2. Create an Event Hub in the last9 namespace with name logs-hub
  3. Configure diagnostic settings

    1. Navigate to your Container App
    2. Go to Diagnostic Settings
    3. Click Add diagnostic setting
    4. Enable necessary logs (console logs, system logs, or both)
    5. Select Stream to an event hub
    6. Choose the Event Hub created in Step 2 (logs-hub)
  4. Create shared access policy

    1. Navigate to your Event Hub (logs-hub)
    2. Go to Shared access policies
    3. Click Add and create a new policy:
      • Name: last9-logs
      • Permissions: Listen only
    4. Save and copy the Primary connection string

    It will look like:

    Endpoint=sb://last9.servicebus.windows.net/;SharedAccessKeyName=last9-logs;SharedAccessKey=<access_key>;EntityPath=logs-hub

Setup OpenTelemetry Collector

Step 1: Create collector directory

mkdir last9-otel-collector
cd last9-otel-collector
mkdir -p storage
chmod 777 storage

Step 2: Create configuration file

Create otel.yaml with the following configuration:

receivers:
azureeventhub:
connection: "<your_event_hub_primary_connection_string>"
storage: file_storage/otlp
format: "azure"
apply_semantic_conventions: true
processors:
batch:
timeout: 5s
send_batch_size: 20000
send_batch_max_size: 20000
transform/azure_logs:
error_mode: ignore
flatten_data: true
log_statements:
- context: log
statements:
- merge_maps(attributes, body, "insert")
- flatten(attributes)
# Set body to actual log message
- set(body, attributes["properties.Log"])
- delete_key(attributes, "properties.Log")
# Set service name to container name
- set(resource.attributes["service.name"], attributes["properties.ContainerAppName"])
# Handle Severity mapping
- set(severity_text, "TRACE") where Int(severity_text) >= 1 and Int(severity_text) <= 4
- set(severity_text, "DEBUG") where Int(severity_text) >= 5 and Int(severity_text) <= 8
- set(severity_text, "INFO") where Int(severity_text) >= 9 and Int(severity_text) <= 12
- set(severity_text, "WARN") where Int(severity_text) >= 13 and Int(severity_text) <= 16
- set(severity_text, "ERROR") where Int(severity_text) >= 17 and Int(severity_text) <= 20
- set(severity_text, "FATAL") where Int(severity_text) >= 21 and Int(severity_text) <= 24
transform/add_timestamp:
error_mode: ignore
flatten_data: true
log_statements:
- context: log
conditions:
- time_unix_nano == 0
statements:
- set(observed_time, Now())
- set(time_unix_nano, observed_time_unix_nano)
exporters:
otlp/last9:
endpoint: "$last9_otlp_endpoint"
headers:
"Authorization": "$last9_basic_auth_header"
extensions:
file_storage/otlp:
directory: /storage
create_directory: true
timeout: 10s
compaction:
directory: /storage
on_rebound: true
check_interval: 1s
service:
extensions: [file_storage/otlp]
pipelines:
logs:
receivers: [azureeventhub]
processors: [transform/azure_logs, transform/add_timestamp, batch]
exporters: [otlp/last9]

Replace the following placeholders:

  • <your_event_hub_primary_connection_string>: Connection string from Step 4
  • $last9_otlp_endpoint: Your Last9 OTLP endpoint
  • $last9_basic_auth_header: Your Last9 authorization header

Run OpenTelemetry Collector

Create docker-compose.yaml:

services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
volumes:
- ./otel.yaml:/etc/otel-collector/config.yaml
- ./storage:/storage
command: ["--config", "/etc/otel-collector/config.yaml", "--feature-gates", "transform.flatten.logs"]
restart: unless-stopped

Start the container:

docker compose up -d

Verification

Check collector status

Monitor the collector logs for successful startup:

docker logs otel-collector

You should see messages indicating successful connection to the Event Hub.

Generate logs

Trigger some activity in your Container Apps to generate logs.

View logs in Last9

  1. Log in to your Last9 dashboard
  2. Navigate to the Logs Explorer
  3. Filter by your container app name
  4. Logs should appear within 1-2 minutes

Troubleshooting

If logs are not appearing:

  1. Verify Event Hub connection:

    • Check the connection string format
    • Ensure the shared access policy has Listen permissions
  2. Check diagnostic settings:

    • Verify logs are being sent to the correct Event Hub
    • Confirm the right log types are enabled
  3. Monitor collector logs:

    • Look for connection errors or authentication issues
    • Verify the collector is processing messages
  4. Test network connectivity:

    • Ensure the collector can reach both Azure Event Hubs and Last9 endpoints
  5. Validate configuration:

    • Check YAML syntax in otel.yaml
    • Verify Last9 endpoint and authorization header

For additional support, reach out to Last9 support at support@last9.io.

Next Steps