Skip to main content

Send Telemetry to Datadog from WebAssembly in Rust

Observability in Datadog from WebAssembly

Overview

Enables the extraction of traces/spans from Wasm modules executing in a Rust program/application and emits them to a Datadog agent.

Requirements

The Rust Observe SDK requires a tokio runtime. If your application already uses tokio, you don't need to make any modifications. If not, you can run the async SDK functions inside a tokio::runtime::Runtime that you initialize yourself.

Installation

Install the dylibso-observe-sdk crate from GitHub:

Cargo.toml
# ...
[dependencies]
# consider pinning to a commit instead of the `main` branch, using: commit = "..."
dylibso-observe-sdk = { git = "https://github.com/dylibso/observe-sdk", branch = "main" }

Example

A basic Rust program that loads a Wasm module and executes it using the Wasmtime WebAssembly runtime.

use dylibso_observe_sdk::adapter::datadog::{
AdapterMetadata, DatadogAdapter, DatadogConfig, DatadogMetadata, Options, SpanFilter,
};

/// You need the datadog agent running on localhost for this example to work
#[tokio::main]
pub async fn main() -> anyhow::Result<()> {
let args: Vec<_> = std::env::args().skip(1).collect();
let data = std::fs::read(&args[0])?;
let function_name = "_start";
let config = wasmtime::Config::new();

// Create instance
let engine = wasmtime::Engine::new(&config)?;
let module = wasmtime::Module::new(&engine, &data)?;

// Create a new instance of the Datadog adapter with default configuration
let ddconfig = DatadogConfig::default();
let adapter = DatadogAdapter::create(ddconfig);

// Setup WASI
let wasi_ctx = wasmtime_wasi::WasiCtxBuilder::new()
.inherit_env()?
.inherit_stdio()
.args(&args.clone())?
.build();

let mut store = wasmtime::Store::new(&engine, wasi_ctx);
let mut linker = wasmtime::Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |wasi| wasi)?;

// create an optional filter that instructs the adapter to throw away any
// spans below a configured threshold
let options = Options {
span_filter: SpanFilter {
min_duration_microseconds: std::time::Duration::from_micros(100),
},
};

// Provide the observability functions to the `Linker` to be made available
// to the instrumented guest code. These are safe to add and are a no-op
// if guest code is uninstrumented.
let trace_ctx = adapter.start(&mut linker, &data, options)?;

let instance = linker.instantiate(&mut store, &module)?;

let f = instance
.get_func(&mut store, function_name)
.expect("function exists");

f.call(&mut store, &[], &mut []).unwrap();

// associate additional metadata with the trace
let meta = DatadogMetadata {
http_url: Some("https://example.com/things/123".into()),
http_method: Some("GET".into()),
http_status_code: Some(200u16),
http_client_ip: Some("23.123.15.145".into()),
http_request_content_length: Some(128974u64),
http_response_content_length: Some(239823874u64),
..Default::default()
};
trace_ctx.set_metadata(AdapterMetadata::Datadog(meta)).await;

// stop the trace
trace_ctx.shutdown().await;

Ok(())
}

Adapter Configuration

You may modify the behavior of your adapter by passing in a configuration when initializing the adapter. A configuration has the following fields:

let config = DatadogConfig{
// the URL of the Datadog agent
agent_host: "http://localhost:8126".into(),
// the service name to group your observability data under
service_name: "default-wasm-service".into(),
// default metadata that apply to every trace
default_tags: HashMap::new(),
// the type of trace being captured
trace_type: DatadogTraceType::Web,
};
let adapter = DatadogAdapter::create(config);