How to setup HTTP Requests Tracing using Opentelemetry SDK

Last updated: August 20, 2025

Overview

This document defines how to use the OpenTelemetry SDK in software applications for HTTP API request instrumentation.

The primary objective is to ensure comprehensive API observability by consistently capturing key attributes in OpenTelemetry spans whenever an HTTP request is processed.


Intended Audience

This guide is written for application developers who are responsible for implementing observability in their services. The tracing should be applied inside REST controllers or HTTP request handlers, where the incoming request and outgoing response are available.

By following this specification, developers ensure that every API request is enriched with standardized span attributes for request/response details, headers, network information, and service metadata.


Runtime Characteristics

The OpenTelemetry SDK is asynchronous in nature:

  • When attributes are added to spans, they are buffered in memory.

  • Exporting to the configured backend (Astra Traffic Collector) happens asynchronously.

  • This means instrumentation does not block the main request flow and does not add significant latency to API handling.

Developers should note:

  • The actual cost of creating spans is lightweight and non-blocking.

  • Exporting traces depends on the chosen exporter, but the SDK ensures that spans are sent out of band.

  • If the exporter backend is unavailable, spans are dropped rather than blocking the API handler.


Prerequisites


Tracing Selection Criteria

Only successful HTTP requests should be selected for tracing. This ensures that trace data reflects valid service interactions, rather than clutter from failed requests.

  • Include:

    • Status codes in the 2xx range (e.g., 200, 201)

    • Status codes in the 3xx range (e.g., 301, 302)

  • Exclude:

    • Status codes in the 4xx range (client errors)

    • Status codes in the 5xx range (server errors)


Resource Attributes

Resource attributes describe the service or application emitting telemetry data. These attributes are set once at the resource level (not per span).

Attribute Name

Type

Description

Example

sensor.version

string

Version of the deployed sensor agent

"1.0.0"


Required Span Attributes

Each span must include a consistent set of attributes. These attributes provide details about the request, network, headers, and bodies.

1. HTTP Request Metadata

Attribute Name

Type

Description

Example

sensor.id

string

integration ID aka sensorID displayed in Astra Dashboard at the end of creation of Opentelemetry SDK integration

"17062dc8-6cd6-4951-9b21-5f40c85b0e71"

http.method

string

HTTP method used

"GET", "POST", "PUT", "PATCH"

http.scheme

string

Request scheme

"https", "http"

http.flavor

string

HTTP protocol version

"1.0", "1.1", "2.0"

http.host

string

Hostname or domain

"api.mycompany.com"

http.target

string

URL path including query string

"/stage-test-client1?param1=10"

http.status_code

int

Response HTTP status code

200, 201, 301

2. Network Information

Attribute Name

Type

Description

Example

net.sock.peer.addr

string

Client IP address or unique identifier

"127.0.0.1"

3. Headers and Body

Attribute Name

Type

Description

Example Value

http.request.headers

string

JSON string of request headers

{"content-type":"application/xml"}

http.response.headers

string

JSON string of response headers

{"content-type":"application/json"}

http.request.body

string

Raw request body

<root>...</root>

http.response.body

string

Raw response body

{"email":"val@yopmail.com"}


Example: Tracing For A Sample HTTP Request

Let's consider an example cURL request and how that request is mapped to otel span attributes for easy understanding

HTTP Request cURL:

curl -X POST 'https://api.example.com/users?source=mobile' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
  -H 'User-Agent: MyApp/1.0' \
  --data-raw '{
    "name": "John Doe",
    "email": "john.doe@example.com",
    "age": 30,
    "preferences": {
        "theme": "dark",
        "notifications": true
    }
}'

#Following cURL response is assumed with 200OK status
{
    "id": 12345,
    "name": "John Doe",
    "email": "john.doe@example.com",
    "created_at": "2023-12-01T10:30:00Z",
    "status": "active"
}

Corresponding Span Attributes

import (
    "encoding/json"
    "log"
)

reqBody := `<root>
    <name>Example</name>
    <mail>val@yopmail.com</mail>
    <age>30</age>
    <addresses>
        <address city="Anytown1">
            <street>123 Main St</street>
        </address>
        <address city="Anytown2">
            <street>124 Main St</street>
        </address>
    </addresses>
</root>`

respBody := "{\"email\":\"val@yopmail.com\",\"arr\":[1,2,3,4]}"

// Example request headers map
requestHeaders := map[string]string{
    "host":            "localhost:8080",
    "content-type":    "application/xml",
    "accept":          "application/json",
    "user-agent":      "curl/7.68.0",
    "accept-encoding": "gzip, deflate, br",
    "authorization":   "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
}

// Example response headers map
responseHeaders := map[string]string{
    "content-type":      "application/json",
    "content-length":    "152",
    "x-request-id":      "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "strict-transport-security": "max-age=31536000; includeSubDomains",
    "cache-control":    "no-cache, no-store, must-revalidate",
}

// Convert request headers to JSON string
requestHeadersJSON, err := json.Marshal(requestHeaders)
if err != nil {
    log.Fatalf("Failed to marshal request headers: %v", err)
}

// Convert response headers to JSON string
responseHeadersJSON, err := json.Marshal(responseHeaders)
if err != nil {
    log.Fatalf("Failed to marshal response headers: %v", err)
}

// Set span attributes
span.SetAttributes(
    attribute.String("sensor.id", "17062dc8-6cd6-4951-9b21-5f40c85b0e71"), // Unique identifier for the sensor
    attribute.String("http.method", "GET"), //http methods like: GET/POST/PUT/PATCH
    attribute.String("http.scheme", "https"), //url scheme like:  http/https
    attribute.String("http.flavor", "2.0"), //scheme flavor: 1.0/1.1/2.0
    attribute.String("http.host", "localhost"), //host or main domain name: api.mycompany.com
    attribute.String("http.target", "/stage-test-client1?param1=10"), //url target. Everything after domain name in this fashion: https://api.mydomain.com/<this_is_url_target_including_prefix_/>
    attribute.String("net.sock.peer.addr", "127.0.0.1"), //IP address of incoming request. Or else, any unique identifier of client
    attribute.Int("http.status_code", 200), //http status code like: 200/404/500/etc
    attribute.String("http.request.headers", string(requestHeadersJSON)), //jsonified string of request header/value map
    attribute.String("http.response.headers", string(responseHeadersJSON)), //jsonified string of response header/value map
    attribute.String("http.request.body", reqBody), //stringified request body
    attribute.String("http.response.body", respBody),//stringified response body
)

Complete code example (including initializing otel sdk and sending the span)

Replace the following variable in the code piece

Variable

Description

Example Value

OtelEndpoint
OR

OTEL_ENDPOINT

grpc endpoint of Astra Traffic Collector. Provide the endpoint address without scheme http://

astra-traffic-collector:4317

SensorID
OR

SENSOR_ID

integration ID aka sensorID displayed in Astra Dashboard at the end of creation of Opentelemetry SDK integration

17062dc8-6cd6-4951-9b21-5f40c85b0e71

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const { trace } = require('@opentelemetry/api');

// Global configuration
const OTEL_ENDPOINT = 'astra-traffic-collector:4317';
const SENSOR_ID = '17062dc8-6cd6-4951-9b21-5f40c85b0e71'
const SENSOR_VERSION = '1.0.0';
const SERVICE_NAME = 'user-api-service';

let sdk;

function initTracer() {
    // Initialize OpenTelemetry SDK
    sdk = new NodeSDK({
        resource: new Resource({
            [SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME,
            'sensor.version': SENSOR_VERSION
        }),
        traceExporter: new OTLPTraceExporter({
            url: OTEL_ENDPOINT
        })
    });

    // Start the SDK
    sdk.start();
    
    console.log('OpenTelemetry initialized successfully');
}

function createSampleSpan() {
    const tracer = trace.getTracer('user-api-service');

    // Sample request/response data
    const requestBody = `{
    "name": "John Doe",
    "email": "john.doe@example.com",
    "age": 30,
    "preferences": {
        "theme": "dark",
        "notifications": true
    }
}`;

    const responseBody = `{
    "id": 12345,
    "name": "John Doe",
    "email": "john.doe@example.com",
    "created_at": "2023-12-01T10:30:00Z",
    "status": "active"
}`;

    // Request headers
    const requestHeaders = {
        "host": "api.example.com",
        "content-type": "application/json",
        "accept": "application/json",
        "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "user-agent": "MyApp/1.0"
    };

    // Response headers
    const responseHeaders = {
        "content-type": "application/json",
        "content-length": "156",
        "x-request-id": "req-123456789",
        "cache-control": "no-cache"
    };

    // Convert headers to JSON
    const requestHeadersJson = JSON.stringify(requestHeaders);
    const responseHeadersJson = JSON.stringify(responseHeaders);

    // Create span
    const span = tracer.startSpan('http-request');
    try {
        // Set all required span attributes
        span.setAttributes({
            'sensor.id': '17062dc8-6cd6-4951-9b21-5f40c85b0e71',
            'http.method': 'POST',
            'http.scheme': 'https',
            'http.flavor': '1.1',
            'http.host': 'api.example.com',
            'http.target': '/users?source=mobile',
            'http.status_code': 200,
            'net.sock.peer.addr': '192.168.1.100',
            'http.request.headers': requestHeadersJson,
            'http.response.headers': responseHeadersJson,
            'http.request.body': requestBody,
            'http.response.body': responseBody
        });

        console.log(`Created span with trace ID: ${span.spanContext().traceId}`);

        // Simulate some processing time
        setTimeout(() => {}, 100);

    } finally {
        span.end();
    }
}

async function main() {
    try {
        // Initialize tracer
        initTracer();

        // Create and send sample span
        createSampleSpan();

        // Wait a bit for export
        await new Promise(resolve => setTimeout(resolve, 2000));
        console.log('Sample span sent successfully');

    } catch (error) {
        console.error('Error:', error.message);
    } finally {
        // Shutdown
        if (sdk) {
            await sdk.shutdown();
        }
    }
}

// Required dependencies in package.json:
/*
{
  "dependencies": {
    "@opentelemetry/api": "^1.4.0",
    "@opentelemetry/sdk-node": "^0.39.0",
    "@opentelemetry/exporter-otlp-grpc": "^0.39.0",
    "@opentelemetry/semantic-conventions": "^1.12.0"
  }
}
*/

main().catch(console.error);

Implementation Notes

  1. Define global configuration variables:

    # otel_config.py
    import os
    
    class OtelConfig:
        """Global configuration for OpenTelemetry settings"""
        OTEL_ENDPOINT = os.getenv("OTEL_ENDPOINT", "astra-traffic-collector:4317")
        SENSOR_ID = os.getenv("SENSOR_ID","17062dc8-6cd6-4951-9b21-5f40c85b0e71")
        SENSOR_VERSION = os.getenv("SENSOR_VERSION", "1.0.0")
        SERVICE_NAME = os.getenv("SERVICE_NAME", "your-service-name")
  2. Set resource attributes at tracer initialization:

    from opentelemetry import trace
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from otel_config import OtelConfig
    
    # Initialize OpenTelemetry
    resource = Resource.create({
        "service.name": OtelConfig.SERVICE_NAME,
        "sensor.version": OtelConfig.SENSOR_VERSION
    })
    
    trace.set_tracer_provider(TracerProvider(resource=resource))
    
    # Configure OTLP exporter
    otlp_exporter = OTLPSpanExporter(endpoint=OtelConfig.OTEL_ENDPOINT)
    span_processor = BatchSpanProcessor(otlp_exporter)
    trace.get_tracer_provider().add_span_processor(span_processor)
    
    # Get tracer
    tracer = trace.get_tracer("com.example.YourApplicationName")
  3. Ensure span attributes include all required metadata.

  4. Redact sensitive values (tokens, passwords, API keys) from headers and bodies.


Best Practices

  1. Maintain consistent resource attributes across services.

  2. Apply redaction or masking for sensitive values.

  3. Limit body capture for large payloads to avoid performance overhead.

  4. Use span attributes to align with OpenTelemetry Semantic Conventions where applicable.