Skip to content
Last9
Book demo

Azure Container Apps

Send traces, logs, and metrics from Azure Container Apps to Last9 using the managed OpenTelemetry agent — with step-by-step setup for Node.js, Java, Next.js, and React.

Use the built-in OpenTelemetry agent in Azure Container Apps (ACA) to route traces, logs, and metrics from your applications to Last9 — without running a separate collector.

ACA’s managed OTel agent acts as a gRPC collector inside your environment. Once configured, it automatically injects the collector endpoint into every container app, so your applications only need the OTel SDK and a one-line startup change.


Prerequisites


Step 1: Configure the ACA Environment

This is a one-time setup per ACA environment. It tells the managed OTel agent where to forward telemetry.

Get your gRPC endpoint and credentials from app.last9.io → Integrations → OpenTelemetry. Use the gRPC endpoint (format: host:port, no https:// prefix).

  1. Add Last9 as an OTLP destination

    az containerapp env telemetry otlp add \
    --name <your-env-name> \
    --resource-group <your-resource-group> \
    --otlp-name last9 \
    --endpoint "<grpc-endpoint-from-last9>" \
    --insecure false \
    --headers "Authorization={{ .Logs.AuthValue }}" \
    --enable-open-telemetry-traces true \
    --enable-open-telemetry-metrics true \
    --enable-open-telemetry-logs true

    Three fields that commonly cause failures:

    Field❌ Wrong✅ Correct
    --endpointhttps://otlp-aps1.last9.io:443otlp-aps1.last9.io:443
    --insecuretrue (the default)false
    --headersusername=X password=YAuthorization=Basic <base64>
  2. Verify the configuration

    After the command runs, ACA automatically injects the following into every container app in the environment:

    OTEL_EXPORTER_OTLP_ENDPOINT=http://k8se-otel.k8se-apps.svc.cluster.local:4317
    OTEL_EXPORTER_OTLP_PROTOCOL=grpc
    OTEL_RESOURCE_ATTRIBUTES=<ACA container and environment metadata>

Step 2: Instrument Your App

  1. Install packages

    npm install \
    @opentelemetry/api@1.9.0 \
    @opentelemetry/auto-instrumentations-node@0.59.0 \
    @opentelemetry/exporter-trace-otlp-grpc@0.201.1 \
    @opentelemetry/exporter-trace-otlp-http@0.201.1 \
    @opentelemetry/instrumentation@0.201.1 \
    @opentelemetry/resources@2.0.1 \
    @opentelemetry/sdk-node@0.201.1 \
    @opentelemetry/sdk-trace-base@2.0.1 \
    @opentelemetry/sdk-trace-node@2.0.1 \
    @opentelemetry/semantic-conventions@1.34.0
  2. Change the startup command in your Dockerfile

    CMD ["node", "--require", "@opentelemetry/auto-instrumentations-node/register", "server.js"]

    This single flag auto-instruments HTTP, database calls, and popular logging libraries (Winston, Pino, Bunyan) without code changes.

  3. Set environment variables on the container app

    OTEL_SERVICE_NAME=your-service-name
    OTEL_TRACES_EXPORTER=otlp
    OTEL_METRICS_EXPORTER=otlp
    OTEL_LOGS_EXPORTER=otlp
    OTEL_TRACES_SAMPLER=always_on

Logging library support:

LibrarySetupSeverity in Last9
Winston ≥ 3.xZero changes — auto-bridged by --require flag✅ Full (INFO/WARN/ERROR)
PinoZero changes — auto-bridged✅ Full
BunyanZero changes — auto-bridged✅ Full
console.logAdd console shim (see below)❌ Body only, no severity

console.log shim — add at the very top of your entry file if you are not using a logging library:

const { logs, SeverityNumber } = require("@opentelemetry/api-logs");
function _emit(body, severityNumber) {
logs.getLogger("console-bridge").emit({ body, severityNumber });
}
const _log = console.log.bind(console),
_info = console.info.bind(console),
_warn = console.warn.bind(console),
_error = console.error.bind(console);
console.log = (...a) => {
_log(...a);
_emit(a.map(String).join(" "), SeverityNumber.INFO);
};
console.info = (...a) => {
_info(...a);
_emit(a.map(String).join(" "), SeverityNumber.INFO);
};
console.warn = (...a) => {
_warn(...a);
_emit(a.map(String).join(" "), SeverityNumber.WARN);
};
console.error = (...a) => {
_error(...a);
_emit(a.map(String).join(" "), SeverityNumber.ERROR);
};

Verification

After deploying, wait 2–3 minutes and check Last9:

  1. Services — your OTEL_SERVICE_NAME should appear
  2. Traces — filter by service name
  3. Logs — filter by service.name attribute
  4. Metrics — search http.server.request.duration (HTTP), process.runtime.nodejs.* (Node.js), jvm.* (Java)
  5. Applications — for React SPA browser monitoring

Smoke test

Validate the ACA environment config before deploying app changes:

START_NS=$(date +%s)000000000
END_NS=$(date +%s)100000000
curl -X POST https://otlp-aps1.last9.io:443/v1/traces \
-H "Authorization: {{ .Logs.AuthValue }}" \
-H "Content-Type: application/json" \
-d "{\"resourceSpans\":[{\"resource\":{\"attributes\":[{\"key\":\"service.name\",\"value\":{\"stringValue\":\"smoke-test\"}}]},\"scopeSpans\":[{\"scope\":{\"name\":\"test\"},\"spans\":[{\"traceId\":\"abcdef1234567890abcdef1234567890\",\"spanId\":\"abcdef12345678\",\"name\":\"test\",\"kind\":1,\"startTimeUnixNano\":\"$START_NS\",\"endTimeUnixNano\":\"$END_NS\",\"status\":{\"code\":1}}]}]}]}"
# Expected: {"partialSuccess":{}}
# Span appears in Last9 Traces within ~2 min

Troubleshooting

  • No data at all

    ACA OTel config is wrong. Check three fields: endpoint (no https:// prefix), --insecure false, and the correct Authorization header.

  • 401 errors

    Wrong header format. Must be Authorization=Basic <base64>, not username=X password=Y.

  • Connection refused

    --insecure true (the default) was used. Must pass --insecure false.

  • Traces only, no logs (Java)

    Missing env var. Add OTEL_LOGS_EXPORTER=otlp.

  • Traces only, no logs (Node.js)

    Using console.log without the shim. Add the console shim to your entry file.

  • Nothing from React SPA

    Wrong SDK. Use the Last9 RUM SDK, not OTel browser packages.

  • Wrong service name in Last9

    OTEL_SERVICE_NAME is not set. Add the env var to your container app spec.

  • Java logs missing MDC fields

    Missing env var. Add OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES=*.

Please get in touch with us on Discord or Email if you have any questions.