How to setup APIGEE Instrumentation
Setting up APIGEE Instrumentation using JavaScript Policy
Overview
This guide explains how to set up a JavaScript policy in an Apigee proxy to instrument request and response flows. The JavaScript policy file is available at: GitHub Repository.

Prerequisites
Access to Apigee UI in Google Cloud
An existing Apigee proxy
Step 1: Setting up HTTP(S) communication between APIGEE and Astra Traffic Collector
Apigee can send data over both HTTP and HTTPS. However, using HTTPS ensures encryption and security, especially if data is sent over the public internet.
Enable HTTP in config_custom.yaml
Open config_custom.yaml in your traffic collector's instance and update the receivers section
receivers:
otlp:
protocols:
http:
endpoint: "0.0.0.0:4318" # HTTP receiver on port 4318
save the file once edited and restart the collector using systemctl restart astra-traffic-collector.service
Enable HTTPS in config_custom.yaml (optional)
Update the same config_custom.yaml to use HTTPS with your cert and key files from the trusted authority:
receivers:
otlp:
protocols:
http:
endpoint: "0.0.0.0:4318" # HTTP receiver on port 4318
tls:
cert_file: "/etc/otelcol-contrib/<cert-file>"
key_file: "/etc/otelcol-contrib/<privkey-file>"
The third field is ca_file, which is used when the certificate is self-signed or from an untrusted CA.
If your certificate is issued by a trusted CA (e.g., Let's Encrypt), you don’t need to specify ca_file.
Ensure the certificate files are correctly placed in /etc/otelcol-contrib/ and have proper permissions. The private key should be readable only by the owner (for security reasons) & The certificate can be readable by others but should not be writable:
Next, modify your volume section in the docker-compose.yaml by adding the following lines in your Astra Traffic Collector to include volume mounts for the certificates:
volumes:
- <path_to_certificates>/<cert-file>:/etc/otelcol-contrib/<cert-file>:ro
- <path_to_certificates>/<privkey-file>:/etc/otelcol-contrib/<privkey-file>:ro
save the file once edited and restart the collector using systemctl restart astra-traffic-collector.service
Step 2: Create a JavaScript Policy
Go to your Apigee API Proxy.
Select the Develop tab.
Click + Add Step in the ProxyEndpoint PostFlow section at Response time.
Choose to create a new Policy with type as JavaScript under the Extension Policies.
Provide a name and then upload the javascript policy available here.
Before attaching the JavaScript policy, update the following values in the script given below.
Sampling Ratio: Adjust the <samplingRatio> variable to control request tracing. (Default: 1 for 100% sampling). SamplingRatio value should be: 0 <= samplingRatio <= 1
Domain Name: Update <domainName> to your proxy domain. Keep the value within double quotes.
Sensor ID: Set <sensorId> using the value provided at the time of creating the Apigee Integration. Keep the value within double quotes.
Astra Traffic Collector Endpoint: Change <astraTrafficCollectorEndpoint> to the appropriate Astra's OpenTelemetry collector URL. Keep the value within double quotes.
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"
//----------------------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);
}
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.
Troubleshooting
Ensure the JavaScript file is correctly referenced in the policy.
Check for syntax errors in the JavaScript code.
Use context.setVariable() to debug variable values.
For further assistance, refer to the Apigee Documentation.
Updated on: 11/02/2025
Thank you!