How to setup HTTP Requests Tracing using Opentelemetry SDK

Last updated: June 12, 2026

Introduction

This guide explains how to instrument your application's HTTP API requests using the OpenTelemetry SDK and send that trace data to Astra for API discovery and security testing.

When an HTTP request is processed in your application, the OpenTelemetry SDK captures key details about it — the method, URL, headers, body, status code, and network information — and exports them as spans to Astra Traffic Collector. This gives Astra the visibility it needs to build and maintain an accurate API inventory.

The SDK handles tracing asynchronously, so instrumentation does not block your request flow or add latency to your API handlers. If the collector is temporarily unavailable, spans are dropped rather than queued indefinitely — ensuring your application is never impacted.

Who Should Read This

This guide is written for application developers responsible for adding observability to their services. You will be working directly inside REST controllers or HTTP request handlers, where both the incoming request and outgoing response are accessible.

This guide is relevant to you if you are:

  • Integrating the OpenTelemetry SDK into a new or existing service

  • Setting up HTTP tracing for API inventory discovery in Astra

  • Working in Go, Java, C#, Node.js, or Python

Prerequisites

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.

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OtelTracingExample
{
    public static class OtelConfig
    {
        public const string OtelEndpoint = "astra-traffic-collector:4317";
        public const string SensorID = "17062dc8-6cd6-4951-9b21-5f40c85b0e71";
        public const string SensorVersion = "1.0.0";
        public const string ServiceName = "user-api-service";
    }

    public static class Instrumentation
    {
        public static readonly ActivitySource ActivitySource = new ActivitySource("user-api-service");
    }

    public static class Program
    {
        private static TracerProvider tracerProvider;

        public static void InitTracer()
        {
            // Create tracer provider with resource attributes
            tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault()
                    .AddService(OtelConfig.ServiceName)
                    .AddAttributes(new KeyValuePair<string, object>[]
                    {
                        new("sensor.version", OtelConfig.SensorVersion)
                    }))
                .AddSource("user-api-service")
                .AddOtlpExporter(options =>
                {
                    options.Endpoint = new Uri(OtelConfig.OtelEndpoint);
                    options.Protocol = OtlpExportProtocol.Grpc;
                })
                .Build();
        }

        public static void CreateSampleSpan()
        {
            // Sample request/response data
            string requestBody = @"{
    ""name"": ""John Doe"",
    ""email"": ""john.doe@example.com"",
    ""age"": 30,
    ""preferences"": {
        ""theme"": ""dark"",
        ""notifications"": true
    }
}";

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

            // Request headers
            var requestHeaders = new Dictionary<string, string>
            {
                { "host", "api.example.com" },
                { "content-type", "application/json" },
                { "accept", "application/json" },
                { "authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." },
                { "user-agent", "MyApp/1.0" }
            };

            // Response headers
            var responseHeaders = new Dictionary<string, string>
            {
                { "content-type", "application/json" },
                { "content-length", "156" },
                { "x-request-id", "req-123456789" },
                { "cache-control", "no-cache" }
            };

            // Convert headers to JSON
            string requestHeadersJson = JsonSerializer.Serialize(requestHeaders);
            string responseHeadersJson = JsonSerializer.Serialize(responseHeaders);

            // Create span
            using var activity = Instrumentation.ActivitySource.StartActivity("http-request");
            if (activity != null)
            {
                // Set all required span attributes
                activity.SetTag("sensor.id", SensorID);
                activity.SetTag("http.method", "POST");
                activity.SetTag("http.scheme", "https");
                activity.SetTag("http.flavor", "1.1");
                activity.SetTag("http.host", "api.example.com");
                activity.SetTag("http.target", "/users?source=mobile");
                activity.SetTag("http.status_code", 200);
                activity.SetTag("net.sock.peer.addr", "192.168.1.100");
                activity.SetTag("http.request.headers", requestHeadersJson);
                activity.SetTag("http.response.headers", responseHeadersJson);
                activity.SetTag("http.request.body", requestBody);
                activity.SetTag("http.response.body", responseBody);

                Console.WriteLine($"Created span with ID: {activity.Id}");

                // Simulate some processing time
                Thread.Sleep(100);
            }
        }

        public static async Task Main(string[] args)
        {
            try
            {
                // Initialize tracer
                InitTracer();

                // Create and send sample span
                CreateSampleSpan();

                // Wait a bit for export
                await Task.Delay(2000);
                Console.WriteLine("Sample span sent successfully");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            finally
            {
                // Shutdown
                tracerProvider?.Dispose();
                Instrumentation.ActivitySource?.Dispose();
            }
        }
    }
}

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

Instrument at the handler layer, not the client layer
Place tracing logic inside your REST controllers or HTTP request handlers — where both the full request and response are available. Instrumenting at the HTTP client layer risks missing response details needed for accurate span attributes.

Trace only successful requests
Only include spans for 2xx and 3xx responses. Tracing 4xx and 5xx responses adds noise to your API inventory without reflecting valid service interactions. Astra will not process spans outside this range.

Redact sensitive header values before setting span attributes
Strip or mask sensitive headers — such as Authorization, Cookie, X-API-Key, and any session tokens — before passing them to http.request.headers or http.response.headers. Astra does not perform automatic redaction, so this is the developer's responsibility.

Cap body size for large payloads
Capturing full request and response bodies for large payloads can increase memory usage and export time. Set a size limit in your instrumentation logic — truncating or omitting bodies beyond a defined threshold (e.g. 10KB) is a reasonable default.

Use environment variables for configuration
Avoid hardcoding OtelEndpoint and SensorID in your application code. Load them from environment variables so they can be updated across environments without code changes.

Keep resource attributes consistent across services
The sensor.version and service.name resource attributes should follow a consistent naming convention across all instrumented services. This ensures spans are correctly grouped and attributed in the Astra dashboard.

Align attribute names with OpenTelemetry Semantic Conventions
Where possible, use the standard OTel attribute names (http.method, http.status_code, etc.) as specified in this guide. Deviating from these will cause spans to be malformed or dropped by the collector.