How to setup APIGEE Instrumentation

Last updated: August 18, 2025

Overview

This document provides detailed instructions for setting up request and response instrumentation in Apigee using a JavaScript policy. The goal is to capture API traffic and send it to the Astra Traffic Collector for further analysis and security monitoring.

Illustration: High-level integration flow between APIGEE and Astra Traffic Collector


Prerequisites

Before beginning, ensure you have:


Quick Installation

Step 1: Set Up OTLP/HTTP Receiver in Astra Traffic Collector

You can configure Astra Traffic Collector to receive OTLP data over HTTP or HTTPS.

📄 How To Set Up OTLP/HTTP Receiver in Astra Traffic Collector

Step 2: Create a JavaScript Policy in APIGEE

  1. Go to your Apigee API Proxy.

  2. Select the Develop tab.

  3. Click + Add Step in the ProxyEndpoint PostFlow section at Response time.

  4. Choose to create a new Policy with type as JavaScript under the Extension Policies.

  5. Provide a name and then upload the javascript policy available here.

Before attaching the policy, modify the configuration variables at the top of the JavaScript file

Variable

Description

Example Value

samplingRatio

Controls sampling frequency (0–1). 1 means 100% sampling.

1

domainName

Your proxy domain name.

"mydomain.nip.io"

sensorId

Sensor ID from Astra dashboard.

"12345678-1234-4abc-9def-987654321000"

astraTrafficCollectorEndpoint

OTLP/HTTP endpoint of the Astra Traffic Collector.

"https://collector.example.com:4318/v1/traces"

var samplingRatio = 1;                   // exampleValue: 0.5
var domainName = "your-domain.com";      // exampleValue: "mydomain.nip.io"
var sensorId = "your-sensor-id";         // exampleValue: "12345678-1234-4abc-9def-987654321000"
var astraTrafficCollectorEndpoint = "";  // exampleValue: "https?://collector-ip-or-domain-endpoint:4318/v1/traces"





//----------------------DO NOT CHANGE THIS. Below is javascript policy. DO NOT CHANGE THIS ----------------------

// Generate a random number between 0 and 1
var randomValue = Math.random();

// Execute only if the random value is = samplingRatio
if (samplingRatio == 1 || randomValue = samplingRatio) {
    // Capture request headers
    var allReqHeaders = {};
    for (var header in request.headers) {
        allReqHeaders[header] = request.headers[header];
    }
    allReqHeaders["host"] = domainName;
    context.setVariable('allReqHeaders', JSON.stringify(allReqHeaders));

    // Capture response headers
    var allRespHeaders = {};
    for (var header in response.headers) {
        allRespHeaders[header] = response.headers[header];
    }
    context.setVariable('allRespHeaders', JSON.stringify(allRespHeaders));

    // Extract path from proxy URL
    var proxyUrl = context.getVariable('proxy.url');
    
    function getPathFromUrl(proxyUrl) {
        var firstDoubleSlash = proxyUrl.indexOf("//");
        if (firstDoubleSlash !== -1) {
            var startIndex = proxyUrl.indexOf("/", firstDoubleSlash + 2);
            return startIndex !== -1 ? proxyUrl.substring(startIndex) : "/";
        }
        return "/";
    }

    var targetPath = getPathFromUrl(proxyUrl);

    // Construct OpenTelemetry payload
    var payload = {
        "resourceSpans": [
            {
                "resource": {
                    "attributes": [
                        { "key": "service.name", "value": { "stringValue": "astra-apigee-js-instrumentation" } },
                        { "key": "sensor.id", "value": { "stringValue": sensorId } },
                        { "key": "sensor.version", "value": { "stringValue": "1.0.0" } },
                    ]
                },
                "scopeSpans": [
                    {
                        "scope": { "name": "instrumentaton" },
                        "spans": [
                            {
                                "traceId": "1234567890abcdef1234567890abcdef",
                                "spanId": "abcdef1234567890",
                                "name": "proxy-endpoint-span",
                                "kind": 1,
                                "startTimeUnixNano": Date.now() * 1e6,
                                "endTimeUnixNano": Date.now() * 1e6,
                                "attributes": [
                                    { "key": "http.method", "value": { "stringValue": context.getVariable('request.verb') } },
                                    { "key": "http.status_code", "value": { "intValue": parseInt(context.getVariable('response.status.code'), 10) } },
                                    { "key": "http.target", "value": { "stringValue": targetPath } },
                                    { "key": "http.host", "value": { "stringValue": domainName } },
                                    { "key": "http.flavor", "value": { "stringValue": context.getVariable('request.version') } },
                                    { "key": "http.scheme", "value": { "stringValue": context.getVariable('client.scheme') } },
                                    { "key": "net.sock.peer.addr", "value": { "stringValue": context.getVariable('client.ip') } },
                                    { "key": "http.request.headers", "value": { "stringValue": context.getVariable('allReqHeaders') } },
                                    { "key": "http.response.headers", "value": { "stringValue": context.getVariable('allRespHeaders') } },
                                    { "key": "http.request.body", "value": { "stringValue": context.getVariable('request.content') } },
                                    { "key": "http.response.body", "value": { "stringValue": context.getVariable('response.content') } }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    };

    // Store payload as an Apigee variable
    context.setVariable('OTELPayload', JSON.stringify(payload));

    // Function to handle HTTP response
    function onComplete(response, error) {
        if (response) {
            if (response.status >= 200 && response.status  300) {
                print('Payload sent successfully. Response Status: ' + response.status);
            } else {
                print('Failed to send payload. Status: ' + response.status);
                print('Response Content: ' + response.content);
            }
        } else {
            print('HTTP Request Error: ' + error);
            context.setVariable('jscallout-http-error', 'Error: ' + error);
        }
    }

    // Send payload to OpenTelemetry collector
    var method = 'POST';
    var headers = { 'content-type': 'application/json' };
    var body = context.getVariable('OTELPayload');
    var req = new Request(astraTrafficCollectorEndpoint, method, headers, body);
    httpClient.send(req, onComplete);
}
  1. Click on the policy that just got created and you will be able to see a similar XML that apigee created to attach the JS File to the policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Javascript continueOnError="false" enabled="true" timeLimit="500" name="logReqRespPolicy">
  <DisplayName>logReqRespPolicy</DisplayName>
  <Properties/>
  <ResourceURL>jsc://instrument.js</ResourceURL>
</Javascript>

By default, the timeLimit attribute in policy's xml file is set to 200ms. If your Astra Traffic Collector used https secure communication, increase the timeLimit (500ms) to avoid failure of response from your backend service.

Step 3: How the Policy Works

  • Request & Response Headers — Captures all request and response headers.

  • Request & Response Body — Captures the full payload content.

  • Trace Attributes — Generates an OpenTelemetry-compatible payload.

  • Conditional Sampling — Sends only a subset of requests if samplingRatio is less than 1.

  • Outbound Transmission — Sends the payload to Astra Traffic Collector over HTTP(S).


Troubleshooting