diff --git a/Cargo.lock b/Cargo.lock index e7ed50f3..32b8f65e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2632,6 +2632,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "remote_config_test" +version = "0.1.0" +dependencies = [ + "datadog-opentelemetry", + "dd-trace", + "opentelemetry", + "tokio", +] + [[package]] name = "reqwest" version = "0.12.23" diff --git a/Cargo.toml b/Cargo.toml index eee67579..93e3b577 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "datadog-opentelemetry", "datadog-opentelemetry/examples/propagator", "datadog-opentelemetry/examples/simple_tracing", + "datadog-opentelemetry/examples/remote_config_test", "datadog-opentelemetry-mappings", "dd-trace", "dd-trace-propagation", diff --git a/datadog-opentelemetry/examples/README.md b/datadog-opentelemetry/examples/README.md index 0a08e111..a5fd5aeb 100644 --- a/datadog-opentelemetry/examples/README.md +++ b/datadog-opentelemetry/examples/README.md @@ -1,22 +1,55 @@ # Examples -## simple_tracing +This directory contains example applications demonstrating various features of the `datadog-opentelemetry` crate. -Demonstrates basic usage of Datadog OpenTelemetry tracing. Creates nested spans and demonstrates tracer initialization and shutdown. +## Available Examples +### simple_tracing +A basic example showing how to initialize the Datadog tracer and create spans. + +**Run:** ```bash cargo run -p simple_tracing ``` -## propagator - -HTTP server that demonstrates trace context propagation between services. Shows how to extract trace context from incoming requests and inject it into outgoing requests. +### propagator +An example demonstrating trace propagation between services. +**Run:** ```bash cargo run -p propagator ``` -The server runs on `http://localhost:3000` with endpoints: -- `/health` - Health check endpoint -- `/echo` - Echo request body -- `/jump` - Makes outbound request to port 3001 +### remote_config_test +A test application for manually testing the remote configuration feature for sampling rules. This application continuously emits spans under a specific service name (`dd-trace-rs-rc-test-service`) to test remote configuration updates from the Datadog backend. + +**Features:** +- Initializes tracer with remote configuration enabled +- Emits ~2 traces per second with realistic span structures +- Creates different operation types: `user_login`, `data_fetch`, `file_upload`, `analytics_event` +- Each trace has a parent span with 2 child spans (`database_query` and `external_api_call`) + +**Run:** +```bash +# From project root +cargo run -p remote_config_test + +# Or use the convenience script +./run_remote_config_test.sh + +# With debug logging to see remote config activity +DD_LOG_LEVEL=DEBUG cargo run -p remote_config_test +``` + +**Prerequisites:** +- Datadog Agent running on `localhost:8126` +- Agent must have remote configuration enabled +- Agent should be configured to receive remote config from Datadog backend + +**Testing Remote Configuration:** +1. Start the application +2. Verify spans are being sent to your APM dashboard +3. Create a sampling rule in your Datadog backend for service `dd-trace-rs-rc-test-service` +4. Monitor the application and APM dashboard to see sampling rate changes + +See the [remote_config_test README](remote_config_test/README.md) for detailed instructions. diff --git a/datadog-opentelemetry/examples/remote_config_test/Cargo.toml b/datadog-opentelemetry/examples/remote_config_test/Cargo.toml new file mode 100644 index 00000000..3ed8bee4 --- /dev/null +++ b/datadog-opentelemetry/examples/remote_config_test/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "remote_config_test" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "remote_config_test" +path = "src/main.rs" + +[dependencies] +datadog-opentelemetry = { path = "../.." } +dd-trace = { path = "../../../dd-trace" } +opentelemetry = { workspace = true } +tokio = { workspace = true, features = ["full", "signal"] } \ No newline at end of file diff --git a/datadog-opentelemetry/examples/remote_config_test/README.md b/datadog-opentelemetry/examples/remote_config_test/README.md new file mode 100644 index 00000000..f8adc01f --- /dev/null +++ b/datadog-opentelemetry/examples/remote_config_test/README.md @@ -0,0 +1,97 @@ +# Remote Configuration Test Application + +This is a test application for manually testing the remote configuration feature for sampling rules in `dd-trace-rs`. It continuously emits spans under a specific service name so you can test remote configuration updates from the Datadog backend. + +## What it does + +- Initializes the Datadog tracer with remote configuration enabled +- Continuously emits spans under the service name `dd-trace-rs-rc-test-service` +- Creates realistic traces with multiple spans (parent + 2 child spans) +- Simulates different operation types: `user_login`, `data_fetch`, `file_upload`, `analytics_event` +- Emits approximately 2 traces per second +- Logs progress every 10 traces + +## Prerequisites + +1. **Datadog Agent**: You need a Datadog Agent running on `localhost:8126` that is configured to receive remote configuration updates from the Datadog backend. + +2. **Agent Configuration**: Your agent should have remote configuration enabled. Check your agent's configuration for: + ```yaml + remote_configuration: + enabled: true + ``` + +## How to run + +### Option 1: From the project root +```bash +# Build and run the test application +cargo run --bin remote_config_test -p remote_config_test +``` + +### Option 2: From the example directory +```bash +cd datadog-opentelemetry/examples/remote_config_test +cargo run +``` + +## Environment Variables + +You can customize the behavior using these environment variables: + +- `DD_TRACE_AGENT_URL`: Agent URL (default: `http://localhost:8126`) +- `DD_LOG_LEVEL`: Log level for Datadog tracing (default: `INFO`) +- `DD_REMOTE_CONFIGURATION_ENABLED`: Enable/disable remote config (default: `true`) + +Example with custom agent URL: +```bash +DD_TRACE_AGENT_URL=http://localhost:8126 cargo run +``` + +## Testing Remote Configuration + +1. **Start the application**: Run the test application using one of the methods above. + +2. **Verify spans are being sent**: Check your Datadog APM dashboard to confirm spans from `dd-trace-rs-rc-test-service` are being received. + +3. **Create a sampling rule**: In your Datadog backend, create a sampling rule for the service: + - Service: `dd-trace-rs-rc-test-service` + - Sample rate: Choose a rate (e.g., 0.1 for 10% sampling) + +4. **Monitor for changes**: Watch the application logs and your APM dashboard to see if the sampling rate changes when the remote configuration is applied. + +## Expected Behavior + +- **Initial**: The application should emit spans at whatever the default sampling rate is +- **After remote config update**: The sampling rate should change according to your remote configuration rule +- **Remote config polling**: The client polls for configuration updates every 5 seconds (as per the spec) + +## Debugging + +### Increase logging verbosity +```bash +DD_LOG_LEVEL=DEBUG cargo run +``` + +### Check agent connectivity +Make sure your agent is accessible: +```bash +curl http://localhost:8126/v0.7/config +``` + +### Check spans are reaching the agent +```bash +curl http://localhost:8126/info +``` + +## What to look for + +When remote configuration is working correctly, you should see: + +1. **In application logs**: Periodic remote config client activity +2. **In agent logs**: Remote configuration requests and responses +3. **In APM dashboard**: Changes in sampling rate for the `dd-trace-rs-rc-test-service` + +## Stopping the application + +Press `Ctrl+C` to gracefully stop the application. It will flush any remaining spans before shutting down. \ No newline at end of file diff --git a/datadog-opentelemetry/examples/remote_config_test/src/main.rs b/datadog-opentelemetry/examples/remote_config_test/src/main.rs new file mode 100644 index 00000000..bfb73ebf --- /dev/null +++ b/datadog-opentelemetry/examples/remote_config_test/src/main.rs @@ -0,0 +1,111 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use opentelemetry::trace::{Tracer, TraceContextExt}; +use opentelemetry::Context; +use std::time::Duration; +use tokio::time::sleep; + +const SERVICE_NAME: &str = "dd-trace-rs-rc-test-service"; + +async fn process_request(request_id: u64, request_type: &str) { + // Create the main span for this request + let tracer = opentelemetry::global::tracer("request-processor"); + let main_span = tracer.start(format!("process_{}", request_type)); + let cx = Context::current_with_span(main_span); + + // Database query - create span, do work, end span + { + let db_span = tracer.start_with_context("database_query", &cx); + let _db_cx = cx.with_span(db_span); + sleep(Duration::from_millis(20 + (request_id % 30))).await; + } // span ends here + + // External API call - create span, do work, end span + { + let api_span = tracer.start_with_context("external_api_call", &cx); + let _api_cx = cx.with_span(api_span); + sleep(Duration::from_millis(30 + (request_id % 40))).await; + } // span ends here +} // main span ends here + +async fn background_worker() { + let mut counter = 0u64; + loop { + counter += 1; + + // Simulate different types of requests + let request_type = match counter % 4 { + 0 => "user_login", + 1 => "data_fetch", + 2 => "file_upload", + _ => "analytics_event", + }; + + process_request(counter, request_type).await; + + // Sleep between requests - emit roughly 2 spans per second + sleep(Duration::from_millis(500)).await; + + // Log every 10 requests to show we're still running + if counter % 10 == 0 { + println!("Emitted {} traces so far", counter); + } + } +} + +#[tokio::main] +async fn main() { + println!("Starting remote config test application"); + println!("Service name: {}", SERVICE_NAME); + println!("Agent URL: {}", std::env::var("DD_TRACE_AGENT_URL").unwrap_or_else(|_| "http://localhost:8126".to_string())); + + // Initialize the Datadog tracer with remote config enabled + let config = dd_trace::Config::builder() + .set_service(SERVICE_NAME.to_string()) + .set_env("dd-trace-rs-test-env".to_string()) + .set_version("1.0.0".to_string()) + // Remote config is enabled by default, but let's be explicit + .build(); + + // Enable debug logging to see remote config activity + if std::env::var("DD_LOG_LEVEL").unwrap_or_default().to_lowercase() == "debug" { + // Note: set_max_level is not public, but the config will handle log level internally + eprintln!("Debug logging enabled"); + } + + // Verify configuration values + println!("Config - Service: {}", config.service()); + println!("Config - Environment: {:?}", config.env()); + println!("Config - Version: {:?}", config.version()); + + let tracer_provider = datadog_opentelemetry::tracing() + .with_config(config) + .init(); + + println!("Tracer initialized with remote config enabled"); + println!("Starting to emit spans continuously..."); + println!("You can now create sampling rules in the Datadog backend for service: {}", SERVICE_NAME); + println!("Press Ctrl+C to stop"); + + // Run the background worker that emits spans + let worker_handle = tokio::spawn(background_worker()); + + // Wait for Ctrl+C + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!("Received Ctrl+C, shutting down..."); + } + _ = worker_handle => { + println!("Worker finished unexpectedly"); + } + } + + // Shutdown the tracer to flush remaining spans + println!("Shutting down tracer..."); + if let Err(e) = tracer_provider.shutdown_with_timeout(Duration::from_secs(5)) { + eprintln!("Error shutting down tracer: {}", e); + } + + println!("Application stopped"); +} \ No newline at end of file diff --git a/debug_remote_config.rs b/debug_remote_config.rs new file mode 100644 index 00000000..0b57273b --- /dev/null +++ b/debug_remote_config.rs @@ -0,0 +1,119 @@ +// Quick debug script to see what JSON our remote config client generates +use dd_trace::Config; +use std::sync::{Arc, Mutex}; + +// Copy the relevant structs and logic from remote_config.rs to test serialization +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +struct ClientState { + root_version: u64, + targets_version: u64, + config_states: Vec, + has_error: bool, + error: Option, + backend_client_state: Option, +} + +#[derive(Debug, Clone, Serialize)] +struct ConfigState { + id: String, + version: u64, + product: String, + apply_state: u64, + apply_error: Option, +} + +#[derive(Debug, Serialize)] +struct ConfigRequest { + client: ClientInfo, + cached_target_files: Vec, +} + +#[derive(Debug, Serialize)] +struct ClientInfo { + #[serde(skip_serializing_if = "Option::is_none")] + state: Option, + id: String, + products: Vec, + is_tracer: bool, + #[serde(skip_serializing_if = "Option::is_none")] + client_tracer: Option, + capabilities: String, +} + +#[derive(Debug, Serialize)] +struct ClientTracer { + runtime_id: String, + language: String, + tracer_version: String, + service: String, + #[serde(default)] + extra_services: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + env: Option, + #[serde(skip_serializing_if = "Option::is_none")] + app_version: Option, + tags: Vec, +} + +#[derive(Debug, Clone, Serialize)] +struct CachedTargetFile { + path: String, + length: u64, + hashes: Vec, +} + +#[derive(Debug, Clone, Serialize)] +struct Hash { + algorithm: String, + hash: String, +} + +fn main() { + let config = Arc::new(Mutex::new( + Config::builder() + .set_service("dd-trace-rs-rc-test-service".to_string()) + .set_env("dd-trace-rs-test-env".to_string()) + .set_version("1.0.0".to_string()) + .build() + )); + + let cfg = config.lock().unwrap(); + + let state = ClientState { + root_version: 1, + targets_version: 0, + config_states: Vec::new(), + has_error: false, + error: None, + backend_client_state: None, + }; + + let client_info = ClientInfo { + state: Some(state), + id: "test-client-id".to_string(), + products: vec!["APM_TRACING".to_string()], + is_tracer: true, + client_tracer: Some(ClientTracer { + runtime_id: cfg.runtime_id().to_string(), + language: "rust".to_string(), + tracer_version: cfg.tracer_version().to_string(), + service: cfg.service().to_string(), + extra_services: cfg.get_extra_services(), + env: cfg.env().map(|s| s.to_string()), + app_version: cfg.version().map(|s| s.to_string()), + tags: cfg.global_tags().map(|s| s.to_string()).collect(), + }), + capabilities: "test-capabilities".to_string(), // Simplified for testing + }; + + let request = ConfigRequest { + client: client_info, + cached_target_files: Vec::new(), + }; + + let json = serde_json::to_string_pretty(&request).unwrap(); + println!("Request JSON:"); + println!("{}", json); +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..e7aa0105 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.8' + +services: + datadog-agent: + env_file: + - ~/sandbox.docker.env + image: datadog/agent:latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /proc/:/host/proc/:ro + - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro + ports: + - "8126:8126/tcp" # APM traces + - "8125:8125/udp" # DogStatsD metrics + environment: + + - DD_APM_ENABLED=true + - DD_APM_NON_LOCAL_TRAFFIC=true + - DD_LOG_LEVEL=TRACE + - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true + - DD_AC_EXCLUDE=name:datadog-agent + - DD_LOGS_ENABLED=true + # Remote Configuration settings + - DD_REMOTE_CONFIGURATION_ENABLED=true + - DD_APM_RECEIVER_TIMEOUT=30s + # Additional APM settings for better debugging + - DD_APM_ANALYZED_SPANS=*:1.0 + # Enable additional telemetry for debugging + - DD_APM_TELEMETRY_ENABLED=true + - DD_TRACE_DEBUG=true + - DD_SITE=datad0g.com + restart: unless-stopped + container_name: dd-agent \ No newline at end of file diff --git a/remote_config_test_3sec_output.log b/remote_config_test_3sec_output.log new file mode 100644 index 00000000..82ba828b --- /dev/null +++ b/remote_config_test_3sec_output.log @@ -0,0 +1,27 @@ +Starting remote config test application +Service name: dd-trace-rs-rc-test-service +Agent URL: http://localhost:8126 +Debug logging enabled +Config - Service: dd-trace-rs-rc-test-service +Config - Environment: Some("dd-trace-rs-test-env") +Config - Version: Some("1.0.0") +DEBUG datadog-opentelemetry/src/lib.rs:310 - RemoteConfigClient: Started remote configuration client +Tracer initialized with remote config enabled +Starting to emit spans continuously... +You can now create sampling rules in the Datadog backend for service: dd-trace-rs-rc-test-service +Press Ctrl+C to stop +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 10 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 20 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 30 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 40 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 50 traces so far diff --git a/remote_config_test_output.log b/remote_config_test_output.log new file mode 100644 index 00000000..71e74b2b --- /dev/null +++ b/remote_config_test_output.log @@ -0,0 +1,27 @@ +Starting remote config test application +Service name: dd-trace-rs-rc-test-service +Agent URL: http://localhost:8126 +Debug logging enabled +Config - Service: dd-trace-rs-rc-test-service +Config - Environment: Some("dd-trace-rs-test-env") +Config - Version: Some("1.0.0") +DEBUG datadog-opentelemetry/src/lib.rs:310 - RemoteConfigClient: Started remote configuration client +Tracer initialized with remote config enabled +Starting to emit spans continuously... +You can now create sampling rules in the Datadog backend for service: dd-trace-rs-rc-test-service +Press Ctrl+C to stop +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 10 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 20 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:320 - RemoteConfigClient: Failed to fetch config: Failed to send request: error sending request for url (http://localhost:8126/v0.7/config): connection closed before message completed +Emitted 30 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 40 traces so far +DEBUG dd-trace/src/configuration/remote_config.rs:333 - RemoteConfigClient: Sending request to http://localhost:8126/v0.7/config with 1 products +DEBUG dd-trace/src/configuration/remote_config.rs:342 - RemoteConfigClient: Received response with status: 200 OK +Emitted 50 traces so far diff --git a/run_remote_config_test.sh b/run_remote_config_test.sh new file mode 100755 index 00000000..a7379dec --- /dev/null +++ b/run_remote_config_test.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Remote Configuration Test Script +# This script runs the remote configuration test application + +echo "🔧 Remote Configuration Test for dd-trace-rs" +echo "=============================================" +echo + +# Check if agent is reachable +echo "🌐 Checking agent connectivity..." +if curl -s "http://localhost:8126/info" > /dev/null 2>&1; then + echo "✅ Agent at localhost:8126 is reachable" +else + echo "❌ Agent at localhost:8126 is not reachable" + echo " Make sure your Datadog Agent is running with remote config enabled" + echo +fi + +# Set up defaults if not set +export DD_TRACE_AGENT_URL="${DD_TRACE_AGENT_URL:-http://localhost:8126}" +export DD_LOG_LEVEL="${DD_LOG_LEVEL:-INFO}" +export DD_REMOTE_CONFIGURATION_ENABLED="${DD_REMOTE_CONFIGURATION_ENABLED:-true}" + +# Show current environment +echo "📋 Environment variables:" +echo " DD_TRACE_AGENT_URL: ${DD_TRACE_AGENT_URL}" +echo " DD_LOG_LEVEL: ${DD_LOG_LEVEL}" +echo " DD_REMOTE_CONFIGURATION_ENABLED: ${DD_REMOTE_CONFIGURATION_ENABLED}" +echo + +echo "🚀 Starting remote config test application..." +echo " Service name: dd-trace-rs-rc-test-service" +echo " The app will emit ~2 traces per second" +echo " Press Ctrl+C to stop" +echo + +# Run the application +exec cargo run -p remote_config_test diff --git a/test_request.json b/test_request.json new file mode 100644 index 00000000..379ac936 --- /dev/null +++ b/test_request.json @@ -0,0 +1,25 @@ +{ + "client": { + "state": { + "root_version": 1, + "targets_version": 0, + "config_states": [], + "has_error": false + }, + "id": "test-client-id", + "products": ["APM_TRACING"], + "is_tracer": true, + "client_tracer": { + "runtime_id": "test-runtime-id", + "language": "rust", + "tracer_version": "0.0.1", + "service": "dd-trace-rs-rc-test-service", + "extra_services": [], + "env": "dd-trace-rs-test-env", + "app_version": "1.0.0", + "tags": [] + }, + "capabilities": "AAAAAAAQAAA=" + }, + "cached_target_files": [] +}