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
-
Enable container app logging
- Navigate to your Azure Container Apps Environment
- Enable logging to Azure Monitor
- Note the region where your container app is deployed
-
Create Event Hubs namespace
- Create Event Hubs namespace with name
last9in the same region as your container app - Create an Event Hub in the
last9namespace with namelogs-hub
- Create Event Hubs namespace with name
-
Configure diagnostic settings
- Navigate to your Container App
- Go to Diagnostic Settings
- Click Add diagnostic setting
- Enable necessary logs (console logs, system logs, or both)
- Select Stream to an event hub
- Choose the Event Hub created in Step 2 (
logs-hub)
-
Create shared access policy
- Navigate to your Event Hub (
logs-hub) - Go to Shared access policies
- Click Add and create a new policy:
- Name:
last9-logs - Permissions: Listen only
- Name:
- 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 - Navigate to your Event Hub (
Setup OpenTelemetry Collector
Step 1: Create collector directory
mkdir last9-otel-collectorcd last9-otel-collectormkdir -p storagechmod 777 storageStep 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-stoppedStart the container:
docker compose up -ddocker run -d \ --name otel-collector \ --restart unless-stopped \ -v "$(pwd)/otel.yaml:/etc/otel-collector/config.yaml" \ -v "$(pwd)/storage:/storage" \ otel/opentelemetry-collector-contrib:latest \ --config /etc/otel-collector/config.yaml \ --feature-gates transform.flatten.logsInstall the collector:
sudo apt-get updatesudo apt-get -y install wget systemctlwget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.110.0/otelcol-contrib_0.110.0_linux_amd64.debsudo dpkg -i otelcol-contrib_0.110.0_linux_amd64.debStart the collector:
otelcol-contrib --config $(pwd)/otel.yaml --feature-gates transform.flatten.logsVerification
Check collector status
Monitor the collector logs for successful startup:
docker logs otel-collectorYou 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
- Log in to your Last9 dashboard
- Navigate to the Logs Explorer
- Filter by your container app name
- Logs should appear within 1-2 minutes
Troubleshooting
If logs are not appearing:
-
Verify Event Hub connection:
- Check the connection string format
- Ensure the shared access policy has Listen permissions
-
Check diagnostic settings:
- Verify logs are being sent to the correct Event Hub
- Confirm the right log types are enabled
-
Monitor collector logs:
- Look for connection errors or authentication issues
- Verify the collector is processing messages
-
Test network connectivity:
- Ensure the collector can reach both Azure Event Hubs and Last9 endpoints
-
Validate configuration:
- Check YAML syntax in
otel.yaml - Verify Last9 endpoint and authorization header
- Check YAML syntax in
For additional support, reach out to Last9 support at support@last9.io.