diff --git a/Cargo.lock b/Cargo.lock index 3cab9596..e57e6e9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,61 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -805,6 +860,27 @@ dependencies = [ "uuid", ] +[[package]] +name = "dd-trace-examples" +version = "0.0.1" +dependencies = [ + "axum", + "datadog-opentelemetry", + "dd-trace", + "opentelemetry", + "opentelemetry-http", + "opentelemetry_sdk", + "rand 0.8.5", + "serde", + "serde_json", + "tokio", + "tower 0.4.13", + "tower-http 0.5.2", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", +] + [[package]] name = "dd-trace-propagation" version = "0.0.1" @@ -1971,6 +2047,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.5" @@ -2668,8 +2750,8 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tower", - "tower-http", + "tower 0.5.2", + "tower-http 0.6.6", "tower-service", "url", "wasm-bindgen", @@ -2995,6 +3077,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_regex" version = "1.1.0" @@ -3514,6 +3606,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -3527,6 +3630,24 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -3542,7 +3663,7 @@ dependencies = [ "http-body 1.0.1", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -3565,10 +3686,23 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -3590,6 +3724,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcf5959f39507d0d04d6413119c04f33b623f4f951ebcbdddddfad2d0623a9c" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" diff --git a/Cargo.toml b/Cargo.toml index 57f2e475..2aa6ffb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "dd-trace", "dd-trace-propagation", "dd-trace-sampling", + "dd-trace-examples", ] # https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2 @@ -38,7 +39,9 @@ opentelemetry_sdk = { version = "0.30.0", features = [ opentelemetry = { version = "0.30.0", features = [ "trace", ], default-features = false } -opentelemetry-semantic-conventions = { version = "0.30.0", features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.30.0", features = [ + "semconv_experimental", +] } tokio = { version = "1.44.1" } rand = { version = "0.8", features = ["small_rng"] } diff --git a/datadog-opentelemetry/src/span_exporter.rs b/datadog-opentelemetry/src/span_exporter.rs index def5dd68..b06a6a8b 100644 --- a/datadog-opentelemetry/src/span_exporter.rs +++ b/datadog-opentelemetry/src/span_exporter.rs @@ -596,6 +596,7 @@ impl TraceExporterWorker { ) }) .collect(); + dbg!(&trace_chunks); match self.trace_exporter.send_trace_chunks(trace_chunks) { Ok(agent_response) => { self.handle_agent_response(agent_response); diff --git a/datadog-opentelemetry/src/span_processor.rs b/datadog-opentelemetry/src/span_processor.rs index 020e19e4..70c12397 100644 --- a/datadog-opentelemetry/src/span_processor.rs +++ b/datadog-opentelemetry/src/span_processor.rs @@ -40,6 +40,7 @@ struct Trace { tags: Option>, } +#[derive(Debug)] pub(crate) struct TracePropagationData { pub sampling_decision: Option, pub origin: Option, @@ -403,6 +404,7 @@ impl DatadogSpanProcessor { /// If [`Trace`] contains origin, tags or sampling_decision add them as attributes of the root /// span fn add_trace_propagation_data(&self, mut trace: Trace) -> Vec { + dbg!(&trace); let origin = trace.origin.unwrap_or_default(); for span in trace.finished_spans.iter_mut() { @@ -427,6 +429,7 @@ impl DatadogSpanProcessor { "_sampling_priority_v1", sampling_decision.priority.into_i8() as i64, )); + dbg!(&sampling_decision, u64::from_be_bytes(span.span_context.span_id().to_bytes())); span.attributes.push(KeyValue::new( SAMPLING_DECISION_MAKER_TAG_KEY, @@ -454,6 +457,7 @@ impl opentelemetry_sdk::trace::SpanProcessor for DatadogSpanProcessor { if parent_ctx.span().span_context().is_remote() { let propagation_data = self.get_remote_propagation_data(span, parent_ctx); + dbg!(&propagation_data); self.registry .register_span(trace_id, span_id, propagation_data); } else if !parent_ctx.has_active_span() { diff --git a/dd-trace-examples/Cargo.toml b/dd-trace-examples/Cargo.toml new file mode 100644 index 00000000..d845094f --- /dev/null +++ b/dd-trace-examples/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "dd-trace-examples" +rust-version.workspace = true +edition.workspace = true +version.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +description.workspace = true + +[dependencies] +# Datadog OpenTelemetry +datadog-opentelemetry = { path = "../datadog-opentelemetry" } +dd-trace = { path = "../dd-trace" } + +# Tracing ecosystem +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-opentelemetry = "0.31" + +# HTTP server +axum = "0.7" +tokio = { version = "1.44.1", features = ["full"] } + +# OpenTelemetry +opentelemetry = { version = "0.30.0", features = ["trace"] } +opentelemetry_sdk = { version = "0.30.0", features = ["trace"] } +opentelemetry-http = { version = "0.30.0" } + +# Utilities +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "trace"] } +rand = "0.8" diff --git a/dd-trace-examples/README.md b/dd-trace-examples/README.md new file mode 100644 index 00000000..d7c74907 --- /dev/null +++ b/dd-trace-examples/README.md @@ -0,0 +1,145 @@ +# Datadog OpenTelemetry Example + +This example demonstrates how to use `datadog-opentelemetry` with the `tracing` crate and `tracing-opentelemetry` bridge in an Axum HTTP server. + +## Features + +- **Datadog OpenTelemetry Integration**: Uses the `datadog-opentelemetry` crate for sending traces to Datadog +- **Tracing Bridge**: Demonstrates the `tracing-opentelemetry` bridge for seamless integration +- **HTTP Server**: Built with Axum framework +- **Structured Logging**: Uses the `tracing` crate for structured logging +- **Custom Spans**: Shows how to create custom spans with attributes +- **REST API**: Includes endpoints for user management and health checks + +## Prerequisites + +1. **Datadog Agent**: Make sure you have a Datadog agent running locally or accessible +2. **Rust**: Ensure you have Rust 1.84.1+ installed + +## Running the Example + +### 1. Start the Datadog Agent (if running locally) + +```bash +# Using Docker +docker run -d --name datadog-agent \ + -e DD_API_KEY=your_api_key \ + -e DD_APM_ENABLED=true \ + -e DD_APM_NON_LOCAL_TRAFFIC=true \ + -p 8126:8126 \ + datadog/agent:latest + +# Or using the official install script +DD_API_KEY=your_api_key DD_APM_ENABLED=true bash -c "$(curl -L https://s1.datadoghq.com/install_script_agent7.sh)" +``` + +### 2. Run the Example + +```bash +# From the examples directory +cargo run + +# Or from the workspace root +cargo run -p dd-trace-examples +``` + +The server will start on `http://localhost:3000` + +### 3. Test the API + +```bash +# Health check +curl http://localhost:3000/health + +# Get user by ID +curl http://localhost:3000/users/123 + +# Create a new user +curl -X POST http://localhost:3000/users \ + -H "Content-Type: application/json" \ + -d '{"name": "John Doe", "email": "john@example.com"}' +``` + +## Configuration + +The example is configured to send traces to `http://localhost:8126/v0.5/traces` (default Datadog agent endpoint). You can modify the configuration in the `main()` function: + +```rust +let pipeline = DatadogPipeline::new() + .with_service_name("dd-trace-example") + .with_service_version("1.0.0") + .with_env("development") + .with_trace_endpoint("http://localhost:8126/v0.5/traces") + .build()?; +``` + +## Key Components + +### 1. Datadog Pipeline Setup + +```rust +use datadog_opentelemetry::DatadogPipeline; + +let pipeline = DatadogPipeline::new() + .with_service_name("dd-trace-example") + .with_service_version("1.0.0") + .with_env("development") + .build()?; +``` + +### 2. Tracing Integration + +```rust +use tracing::{info, instrument}; +use tracing_opentelemetry::OpenTelemetrySpanExt; + +#[instrument(skip(tracer))] +async fn get_user(Path(id): Path, tracer: Extension) -> Json> { + let span = tracer.start("get_user"); + span.set_attribute(opentelemetry::KeyValue::new("user.id", id.to_string())); + // ... function logic + span.end(); +} +``` + +### 3. Global Tracer Setup + +```rust +use opentelemetry::global; + +let tracer = pipeline.tracer(); +global::set_tracer_provider(pipeline.trace_provider()); +``` + +## What You'll See + +1. **Console Logs**: Structured logging output showing the application flow +2. **Datadog Traces**: Spans and traces sent to your Datadog agent +3. **Custom Attributes**: User ID, name, and email attributes on spans +4. **Performance Metrics**: Timing information for each operation + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: Make sure the Datadog agent is running and accessible +2. **Permission Denied**: Check that the agent endpoint is correct and accessible +3. **No Traces in Datadog**: Verify your API key and agent configuration + +### Debug Mode + +The example includes debug logging for the datadog-opentelemetry crate: + +```rust +tracing_subscriber::fmt() + .with_env_filter("info,datadog_opentelemetry=debug") + .init(); +``` + +## Next Steps + +- Modify the service name, version, and environment +- Add more custom attributes to spans +- Implement error handling and error spans +- Add metrics collection +- Configure sampling and filtering rules diff --git a/dd-trace-examples/src/main.rs b/dd-trace-examples/src/main.rs new file mode 100644 index 00000000..8e7df6a0 --- /dev/null +++ b/dd-trace-examples/src/main.rs @@ -0,0 +1,140 @@ +use axum::{extract::Request, http::Method, response::Json, routing::get, Router}; +use datadog_opentelemetry; +use dd_trace::Config; +use opentelemetry::trace::TracerProvider; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::{DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, TraceLayer}, +}; +use tracing::{field::Empty, info, instrument, level_filters::LevelFilter, Level}; +use tracing_opentelemetry::OpenTelemetrySpanExt; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; + +#[derive(Debug, Serialize, Deserialize)] +struct User { + id: u32, + name: String, + email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct CreateUserRequest { + name: String, + email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ApiResponse { + success: bool, + data: Option, + message: String, +} + +#[instrument] +async fn health_check() -> Json> { + // Simulate health check + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + + info!("Health check performed"); + + Json(ApiResponse { + success: true, + data: None, + message: "Service is healthy".to_string(), + }) +} + +#[instrument] +async fn root() -> &'static str { + info!("Root endpoint accessed"); + "Datadog OpenTelemetry Example API\n\nAvailable endpoints:\n- GET /health - Health check\n- GET /users/{id} - Get user by ID\n- POST /users - Create new user" +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + info!("Starting Datadog OpenTelemetry example application..."); + + // Initialize Datadog OpenTelemetry pipeline + let tracer_provider = datadog_opentelemetry::tracing() + .with_config( + Config::builder() + .set_trace_agent_url("http://0.0.0.0:8126".into()) + .set_service("dd-trace-example".to_string()) + .set_version("1.0.0".to_string()) + .set_env("development".to_string()) + .set_log_level_filter(dd_trace::log::LevelFilter::Info) + .build(), + ) + .init(); + + info!("Datadog pipeline initialized successfully"); + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::DEBUG)) + .with( + tracing_opentelemetry::layer().with_tracer(tracer_provider.tracer("dd-trace-example")), + ) + .try_init()?; + + // Create CORS layer + let cors = CorsLayer::new() + .allow_methods([Method::GET, Method::POST]) + .allow_origin(Any); + + // Build our application with a route + let app = Router::new() + .route("/", get(root)) + .route("/health", get(health_check)) + .layer(cors) + .layer( + TraceLayer::new_for_http() + .make_span_with(make_span) + .on_request(DefaultOnRequest::new().level(Level::DEBUG)) + .on_response(DefaultOnResponse::new().level(Level::DEBUG)) + .on_failure(DefaultOnFailure::new().level(Level::ERROR)), + ); + + // Run it + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + info!("Starting server on {}", addr); + + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app).await?; + + // Shutdown the tracer provider + tracer_provider.shutdown()?; + + Ok(()) +} + +fn make_span(request: &Request) -> tracing::Span { + let route = request.uri().path(); + let span = tracing::info_span!( + target: "otel::tracing", + "incoming request", + // Tracing span name must be a static string, but we can use this field to use a + // dynamic string for the opentelemetry span name. + // See https://github.com/tokio-rs/tracing/pull/732. + otel.name = %route, + http.grpc_status = Empty, + http.grpc_status_str = Empty, + error.message = Empty, + rpc.system = "grpc", + uri = %request.uri(), + route = route, + org_id = Empty, + upstream_req_id = Empty, + query_source = Empty, + ); + + // Extract tracing information from incoming request and propagate it + // to this span + let remote_parent_ctx = opentelemetry::global::get_text_map_propagator(|propagator| { + let extractor = opentelemetry_http::HeaderExtractor(request.headers()); + propagator.extract(&extractor) + }); + + span.set_parent(remote_parent_ctx.clone()); + span +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..c5242467 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "dd-trace-examples" +rust-version.workspace = true +edition.workspace = true +version.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +description.workspace = true + +[dependencies] +# Datadog OpenTelemetry +datadog-opentelemetry = { path = "../datadog-opentelemetry" } + +# Tracing ecosystem +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-opentelemetry = "0.24" + +# HTTP server +axum = "0.7" +tokio = { version = "1.44.1", features = ["full"] } + +# OpenTelemetry +opentelemetry = { version = "0.30.0", features = ["trace"] } +opentelemetry_sdk = { version = "0.30.0", features = ["trace"] } + +# Utilities +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "trace"] } +rand = "0.8" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..d7c74907 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,145 @@ +# Datadog OpenTelemetry Example + +This example demonstrates how to use `datadog-opentelemetry` with the `tracing` crate and `tracing-opentelemetry` bridge in an Axum HTTP server. + +## Features + +- **Datadog OpenTelemetry Integration**: Uses the `datadog-opentelemetry` crate for sending traces to Datadog +- **Tracing Bridge**: Demonstrates the `tracing-opentelemetry` bridge for seamless integration +- **HTTP Server**: Built with Axum framework +- **Structured Logging**: Uses the `tracing` crate for structured logging +- **Custom Spans**: Shows how to create custom spans with attributes +- **REST API**: Includes endpoints for user management and health checks + +## Prerequisites + +1. **Datadog Agent**: Make sure you have a Datadog agent running locally or accessible +2. **Rust**: Ensure you have Rust 1.84.1+ installed + +## Running the Example + +### 1. Start the Datadog Agent (if running locally) + +```bash +# Using Docker +docker run -d --name datadog-agent \ + -e DD_API_KEY=your_api_key \ + -e DD_APM_ENABLED=true \ + -e DD_APM_NON_LOCAL_TRAFFIC=true \ + -p 8126:8126 \ + datadog/agent:latest + +# Or using the official install script +DD_API_KEY=your_api_key DD_APM_ENABLED=true bash -c "$(curl -L https://s1.datadoghq.com/install_script_agent7.sh)" +``` + +### 2. Run the Example + +```bash +# From the examples directory +cargo run + +# Or from the workspace root +cargo run -p dd-trace-examples +``` + +The server will start on `http://localhost:3000` + +### 3. Test the API + +```bash +# Health check +curl http://localhost:3000/health + +# Get user by ID +curl http://localhost:3000/users/123 + +# Create a new user +curl -X POST http://localhost:3000/users \ + -H "Content-Type: application/json" \ + -d '{"name": "John Doe", "email": "john@example.com"}' +``` + +## Configuration + +The example is configured to send traces to `http://localhost:8126/v0.5/traces` (default Datadog agent endpoint). You can modify the configuration in the `main()` function: + +```rust +let pipeline = DatadogPipeline::new() + .with_service_name("dd-trace-example") + .with_service_version("1.0.0") + .with_env("development") + .with_trace_endpoint("http://localhost:8126/v0.5/traces") + .build()?; +``` + +## Key Components + +### 1. Datadog Pipeline Setup + +```rust +use datadog_opentelemetry::DatadogPipeline; + +let pipeline = DatadogPipeline::new() + .with_service_name("dd-trace-example") + .with_service_version("1.0.0") + .with_env("development") + .build()?; +``` + +### 2. Tracing Integration + +```rust +use tracing::{info, instrument}; +use tracing_opentelemetry::OpenTelemetrySpanExt; + +#[instrument(skip(tracer))] +async fn get_user(Path(id): Path, tracer: Extension) -> Json> { + let span = tracer.start("get_user"); + span.set_attribute(opentelemetry::KeyValue::new("user.id", id.to_string())); + // ... function logic + span.end(); +} +``` + +### 3. Global Tracer Setup + +```rust +use opentelemetry::global; + +let tracer = pipeline.tracer(); +global::set_tracer_provider(pipeline.trace_provider()); +``` + +## What You'll See + +1. **Console Logs**: Structured logging output showing the application flow +2. **Datadog Traces**: Spans and traces sent to your Datadog agent +3. **Custom Attributes**: User ID, name, and email attributes on spans +4. **Performance Metrics**: Timing information for each operation + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: Make sure the Datadog agent is running and accessible +2. **Permission Denied**: Check that the agent endpoint is correct and accessible +3. **No Traces in Datadog**: Verify your API key and agent configuration + +### Debug Mode + +The example includes debug logging for the datadog-opentelemetry crate: + +```rust +tracing_subscriber::fmt() + .with_env_filter("info,datadog_opentelemetry=debug") + .init(); +``` + +## Next Steps + +- Modify the service name, version, and environment +- Add more custom attributes to spans +- Implement error handling and error spans +- Add metrics collection +- Configure sampling and filtering rules diff --git a/examples/src/main.rs b/examples/src/main.rs new file mode 100644 index 00000000..afbae61c --- /dev/null +++ b/examples/src/main.rs @@ -0,0 +1,162 @@ +use axum::{ + extract::Path, + http::{Method, StatusCode}, + response::Json, + routing::{get, post}, + Router, +}; +use datadog_opentelemetry::DatadogPipeline; +use opentelemetry::{ + global, + trace::{Span, Tracer}, +}; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use tracing::{info, instrument, warn}; +use tracing_opentelemetry::OpenTelemetrySpanExt; + +#[derive(Debug, Serialize, Deserialize)] +struct User { + id: u32, + name: String, + email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct CreateUserRequest { + name: String, + email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ApiResponse { + success: bool, + data: Option, + message: String, +} + +#[instrument(skip(tracer))] +async fn get_user(Path(id): Path, tracer: axum::extract::Extension) -> Json> { + let span = tracer.start("get_user"); + span.set_attribute(opentelemetry::KeyValue::new("user.id", id.to_string())); + + // Simulate some work + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + let user = User { + id, + name: format!("User {}", id), + email: format!("user{}@example.com", id), + }; + + info!("Retrieved user: {:?}", user); + span.end(); + + Json(ApiResponse { + success: true, + data: Some(user), + message: "User retrieved successfully".to_string(), + }) +} + +#[instrument(skip(tracer))] +async fn create_user( + Json(payload): Json, + tracer: axum::extract::Extension, +) -> Json> { + let span = tracer.start("create_user"); + span.set_attribute(opentelemetry::KeyValue::new("user.name", payload.name.clone())); + span.set_attribute(opentelemetry::KeyValue::new("user.email", payload.email.clone())); + + // Simulate some work + tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; + + let user = User { + id: rand::random::() % 10000, + name: payload.name, + email: payload.email, + }; + + info!("Created user: {:?}", user); + span.end(); + + Json(ApiResponse { + success: true, + data: Some(user), + message: "User created successfully".to_string(), + }) +} + +#[instrument(skip(tracer))] +async fn health_check(tracer: axum::extract::Extension) -> Json> { + let span = tracer.start("health_check"); + + // Simulate health check + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + + info!("Health check performed"); + span.end(); + + Json(ApiResponse { + success: true, + data: None, + message: "Service is healthy".to_string(), + }) +} + +#[instrument] +async fn root() -> &'static str { + info!("Root endpoint accessed"); + "Datadog OpenTelemetry Example API\n\nAvailable endpoints:\n- GET /health - Health check\n- GET /users/{id} - Get user by ID\n- POST /users - Create new user" +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize tracing subscriber + tracing_subscriber::fmt() + .with_env_filter("info,datadog_opentelemetry=debug") + .init(); + + info!("Starting Datadog OpenTelemetry example application..."); + + // Initialize Datadog OpenTelemetry pipeline + let pipeline = DatadogPipeline::new() + .with_service_name("dd-trace-example") + .with_service_version("1.0.0") + .with_env("development") + .with_trace_endpoint("http://localhost:8126/v0.5/traces") // Default Datadog agent endpoint + .build()?; + + info!("Datadog pipeline initialized successfully"); + + // Get the tracer from the pipeline + let tracer = pipeline.tracer(); + + // Set the global tracer + global::set_tracer_provider(pipeline.trace_provider()); + + // Create CORS layer + let cors = CorsLayer::new() + .allow_methods([Method::GET, Method::POST]) + .allow_origin(Any); + + // Build our application with a route + let app = Router::new() + .route("/", get(root)) + .route("/health", get(health_check)) + .route("/users/:id", get(get_user)) + .route("/users", post(create_user)) + .layer(cors) + .layer(axum::extract::Extension(tracer)); + + // Run it + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + info!("Starting server on {}", addr); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await?; + + Ok(()) +}