How to setup Azure Functions Integration for Traffic Monitoring

Last updated: June 10, 2026

Introduction

This guide walks you through instrumenting an Azure Function (TypeScript) to send request and response traffic to Astra's Traffic Collector via OpenTelemetry. Once set up, API traffic from your Azure Function will be visible in the Astra dashboard for security monitoring and analysis.

Prerequisites

  • Access to Azure Functions in Azure cloud

  • Azure Functions extension installed in Visual Studio Code

  • Astra Traffic Collector installed and reachable

  • Azure Functions Integration created in Astra dashboard with sensorID copied

Instructions

Step 1:Configure the OTLP/HTTP Receiver in Astra Traffic Collector

Open config_custom.yaml in your Traffic Collector instance and add the receiver configuration.

For development or testing, use HTTP:

receivers:
  otlp:
    protocols:
      http:
        endpoint: "0.0.0.0:4318"  # HTTP receiver on port 4318

processors:
 #...

For production, use HTTPS by adding TLS settings with your certificate and key file paths. If your certificate is from a trusted CA like Let's Encrypt, ca_file is not required , only add it for self-signed or untrusted certificates.

Also update docker-compose.yaml to mount the certificate files as volumes into the collector container.

Save the file and restart the collector: systemctl restart astra-traffic-collector.service

Step 2: Install Dependencies

Navigate to your Azure Functions project directory and run the following commands:

npm install @azure/functions                     
npm install @opentelemetry/exporter-trace-otlp-http  
npm install @opentelemetry/sdk-trace-node        
npm install @opentelemetry/resources          
npm install @opentelemetry/api

Step 3: Add the Instrumentation Code

Navigate to /src/functions/function.ts and paste the boilerplate code provided below into your file.

Once pasted, update only the two values in the config block at the top:

  • sensorId — use the value shown when you created the Azure Functions Integration in the Astra dashboard

  • collectorUrl — set this to your Astra Traffic Collector's OTLP/HTTP endpoint

import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { SpanStatusCode } from '@opentelemetry/api';

// ====================== USER CONFIGURATION - EDIT THESE VALUES ======================
const config = {
    sensorId: '',
    
    collectorUrl: '', // example: https?://collector-domain:4318/v1/traces
    exporterSettings: {
        timeoutMillis: 3000,
    },
};
// ====================== END USER CONFIGURATION ====================================

// OpenTelemetry Setup
const setupTelemetry = () => {
    console.log('Starting OpenTelemetry setup...');
    const resource = resourceFromAttributes({
        'sensor.id': config.sensorId,
        'sensor.version': '1.0.0',  
        'service.name': 'astra-otel-plugin'  
    });

    const traceExporter = new OTLPTraceExporter({
        url: config.collectorUrl,
        timeoutMillis: config.exporterSettings.timeoutMillis
    });

    const provider = new NodeTracerProvider({
        resource: resource,
        spanProcessors: [new SimpleSpanProcessor(traceExporter as any)]
    });

    provider.register();
    return provider.getTracer('astra-otel-plugin');
};

const tracer = setupTelemetry();

// Telemetry wrapper function
const withTelemetry = async (
    request: HttpRequest, 
    statusCode: number,
    responseHeaders: Record,
    responseBody: any
) => {
    const span = tracer.startSpan('http_trigger');
    
    try {
        // Get request headers and body
        const headers = Object.fromEntries(request.headers.entries());
        const body = await request.text().catch(() => '');
        const url = new URL(request.url);
        const target = url.pathname + url.search;

        // Get client IP address from headers
        const clientIp = request.headers.get('x-forwarded-for') || 
                        request.headers.get('x-real-ip') || 
                        '';

        // Set request span attributes
        span.setAttribute('http.method', request.method);
        span.setAttribute('http.status_code', statusCode);
        span.setAttribute('http.target', target);
        span.setAttribute('http.host', url.host);
        span.setAttribute('http.flavor', '1.1');
        span.setAttribute('http.scheme', url.protocol.replace(':', ''));
        span.setAttribute('http.request.headers', JSON.stringify(headers));
        span.setAttribute('http.request.body', body);
        span.setAttribute('net.sock.peer.addr', clientIp);
        
        // Set response span attributes
        span.setAttribute('http.response.headers', JSON.stringify(responseHeaders));
        span.setAttribute('http.response.body', JSON.stringify(responseBody));
        span.setStatus({ code: SpanStatusCode.OK });
    } catch (error) {
        span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error instanceof Error ? error.message : 'Unknown error'
        });
        throw error;
    } finally {
        span.end();
    }
};

app.http('httpTrigger1', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: async (request: HttpRequest, context: InvocationContext): Promise => {

        // First, execute business logic and get response

        const responseBody = {
            // 'message': 'ok'
        }
        const statusCode = 200;
        const responseHeaders = {
            // 'content-type': 'application/json',
            // 'x-custom-header': 'custom-value'
        };

        // Start telemetry operations without awaiting them
        withTelemetry(
            request, 
            statusCode,
            responseHeaders,
            responseBody
        ).catch(error => console.error('Telemetry error:', error));

        // return response to user
        return {
            status: statusCode,
            headers: responseHeaders,
            body: JSON.stringify(responseBody)
        };
    }
});

Step 4. Add Your Business Logic

Inside the app.http() handler, write your business logic before the withTelemetry() call. Set the following three variables to form your HTTP response:

  • responseBody — the content to return to the client

  • statusCode — the appropriate HTTP status code

  • responseHeaders — any required response headers including Content-Type

Do not modify the telemetry setup code outside the config block. Changes there can break observability and result in missing data in the Astra dashboard.

Step 5. Deploy the Function

Deploy your updated function directory to Azure so the changes take effect on the remote environment.

Step 6. Test the Integration

Click the Get function URL button in the Azure portal top menu, copy the URL, and send a request via browser or cURL. The response will match what you defined in responseBody. Verify that traces are appearing in the Astra Traffic Collector.

Expected Outcome

After deployment, API traffic flowing through your Azure Function will be captured as OpenTelemetry traces and forwarded to the Astra Traffic Collector. Endpoints and request details will appear in your Astra dashboard.

Best Practices

  • Keep business logic separate from telemetry logic, following the pattern in the boilerplate

  • Use HTTPS for your Traffic Collector in production to ensure secure telemetry transmission

Troubleshooting

Traces not appearing in the Astra dashboard — Verify that sensorId and collectorUrl are correctly set in the config block and that the Traffic Collector is running and reachable.

Collector not receiving data — Confirm port 4318 is open and accessible from the Azure Function's network. Check that the OTLP/HTTP receiver is correctly configured in config_custom.yaml.

Deployment not reflecting changes — Ensure you deployed the function directory to the remote Azure environment and not just saved locally.

Certificate errors on HTTPS — Confirm certificate files are placed in /etc/otelcol-contrib/ with correct permissions. The private key must be readable only by the owner.