mod layer;
mod service;
pub use layer::TelemetryLayer;
pub use service::{TelemetryService, TelemetryServiceFuture};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct TelemetryLayerConfig {
pub service_name: String,
pub service_version: String,
pub record_sizes: bool,
pub record_timing: bool,
pub excluded_methods: Vec<String>,
pub propagate_context: bool,
}
impl Default for TelemetryLayerConfig {
fn default() -> Self {
Self {
service_name: "turbomcp-service".to_string(),
service_version: env!("CARGO_PKG_VERSION").to_string(),
record_sizes: true,
record_timing: true,
excluded_methods: Vec::new(),
propagate_context: true,
}
}
}
impl TelemetryLayerConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn service_name(mut self, name: impl Into<String>) -> Self {
self.service_name = name.into();
self
}
#[must_use]
pub fn service_version(mut self, version: impl Into<String>) -> Self {
self.service_version = version.into();
self
}
#[must_use]
pub fn record_sizes(mut self, enabled: bool) -> Self {
self.record_sizes = enabled;
self
}
#[must_use]
pub fn record_timing(mut self, enabled: bool) -> Self {
self.record_timing = enabled;
self
}
#[must_use]
pub fn exclude_method(mut self, method: impl Into<String>) -> Self {
self.excluded_methods.push(method.into());
self
}
#[must_use]
pub fn propagate_context(mut self, enabled: bool) -> Self {
self.propagate_context = enabled;
self
}
#[must_use]
pub fn should_instrument(&self, method: &str) -> bool {
!self.excluded_methods.iter().any(|m| m == method)
}
}
#[derive(Debug, Clone)]
pub struct SpanData {
pub method: String,
pub request_id: Option<String>,
pub duration: Duration,
pub success: bool,
pub error: Option<String>,
pub request_size: Option<usize>,
pub response_size: Option<usize>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_defaults() {
let config = TelemetryLayerConfig::default();
assert_eq!(config.service_name, "turbomcp-service");
assert!(config.record_sizes);
assert!(config.record_timing);
assert!(config.excluded_methods.is_empty());
assert!(config.propagate_context);
}
#[test]
fn test_config_builder() {
let config = TelemetryLayerConfig::new()
.service_name("my-service")
.service_version("2.0.0")
.record_sizes(false)
.exclude_method("ping")
.exclude_method("initialize");
assert_eq!(config.service_name, "my-service");
assert_eq!(config.service_version, "2.0.0");
assert!(!config.record_sizes);
assert_eq!(config.excluded_methods.len(), 2);
}
#[test]
fn test_should_instrument() {
let config = TelemetryLayerConfig::new()
.exclude_method("ping")
.exclude_method("notifications/initialized");
assert!(config.should_instrument("tools/call"));
assert!(config.should_instrument("resources/read"));
assert!(!config.should_instrument("ping"));
assert!(!config.should_instrument("notifications/initialized"));
}
}