use std::collections::HashMap;
use std::time::SystemTime;
use metrique::emf::Emf;
use metrique::writer::{
AttachGlobalEntrySinkExt, Entry, EntryIoStreamExt, FormatExt, GlobalEntrySink,
};
use metrique::{ServiceMetrics, flex::Flex, unit::Count, unit_of_work::metrics};
#[metrics(rename_all = "PascalCase")]
struct DynamicMetrics {
#[metrics(timestamp)]
timestamp: SystemTime,
operation: &'static str,
#[metrics(unit = Count)]
static_counter: usize,
#[metrics(flatten)]
dynamic_field: Flex<usize>,
#[metrics(flatten)]
user_tag: Flex<String>,
#[metrics(flatten)]
computed_metric: Flex<f64>,
}
impl DynamicMetrics {
fn init(operation: &'static str) -> DynamicMetricsGuard {
Self {
timestamp: SystemTime::now(),
operation,
static_counter: 0,
dynamic_field: Flex::new("records_processed"),
user_tag: Flex::new("environment"),
computed_metric: Flex::new(format!("{}Duration", operation.to_lowercase())),
}
.append_on_drop(ServiceMetrics::sink())
}
}
#[derive(Entry)]
#[entry(rename_all = "PascalCase")]
struct Globals {
service: String,
region: String,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let globals = Globals {
service: "DynamicService".into(),
region: "us-west-2".into(),
};
let _handle = ServiceMetrics::attach_to_stream(
Emf::all_validations("DynamicMetrics".to_string(), vec![vec![]])
.output_to_makewriter(std::io::stdout)
.merge_globals(globals),
);
basic_dynamic_example().await;
config_driven_example().await;
user_tags_example().await;
ab_test_example().await;
builder_api_example().await;
optional_fields_example().await;
let _error_metric = create_error_metric("timeout", 3);
unset_then_set_example().await;
}
async fn basic_dynamic_example() {
println!("=== Basic Dynamic Field Example ===");
let mut metrics = DynamicMetrics::init("ProcessData");
let records_processed = 42;
let environment = "production";
let processing_duration = 123.45;
metrics.dynamic_field.set_value(records_processed);
metrics.user_tag.set_value(environment.to_string());
metrics.computed_metric.set_value(processing_duration);
metrics.static_counter = 1;
}
async fn config_driven_example() {
println!("=== Configuration-Driven Example ===");
let config = HashMap::from([
("metric_prefix".to_string(), "api_".to_string()),
("counter_suffix".to_string(), "_total".to_string()),
]);
let field_name = format!(
"{}requests{}",
config.get("metric_prefix").unwrap(),
config.get("counter_suffix").unwrap()
);
let mut metrics = DynamicMetrics::init("HandleRequest");
metrics.dynamic_field = Flex::new(field_name).with_value(156); metrics.user_tag.set_value("v1.2.3".to_string());
metrics.computed_metric.set_value(89.2);
metrics.static_counter = 2;
}
async fn user_tags_example() {
println!("=== User-Provided Tags Example ===");
let user_tags = vec![
("customer_tier", "premium"),
("feature_flag", "new_ui_enabled"),
];
for (tag_key, tag_value) in user_tags {
let mut metrics = DynamicMetrics::init("UserAction");
metrics.user_tag = Flex::new(tag_key).with_value(tag_value.to_string());
metrics.dynamic_field.set_value(1); metrics.computed_metric.set_value(45.6);
metrics.static_counter = 3;
}
}
async fn ab_test_example() {
println!("=== A/B Test Metrics Example ===");
let variants = vec![
("variant_a_conversions", 45),
("variant_b_conversions", 52),
("control_conversions", 38),
];
for (variant_field, conversion_count) in variants {
let mut metrics = DynamicMetrics::init("ABTest");
metrics.dynamic_field = Flex::new(variant_field).with_value(conversion_count);
metrics.user_tag.set_value("checkout_flow_v2".to_string());
metrics.computed_metric.set_value(234.1);
metrics.static_counter = 4;
}
}
fn create_error_metric(error_type: &str, error_count: usize) -> DynamicMetricsGuard {
let mut metrics = DynamicMetrics::init("ErrorTracking");
let error_field = format!("{}_errors", error_type.to_lowercase());
metrics.dynamic_field = Flex::new(error_field).with_value(error_count);
metrics.user_tag.set_value("high".to_string()); metrics.computed_metric.set_value(123.45);
metrics
}
async fn builder_api_example() {
println!("=== Builder API Example ===");
let mut metrics = DynamicMetrics::init("ConvenienceTest");
metrics.dynamic_field = Flex::new("api_requests").with_value(150usize);
metrics.user_tag = Flex::new("deployment").with_value("v2.1.0".to_string());
metrics.computed_metric = Flex::new("response_time_ms").with_value(45.2f64);
metrics.static_counter = 5;
}
async fn optional_fields_example() {
println!("=== Optional Fields Example ===");
let user_id: Option<String> = Some("user456".to_string());
let session_id: Option<String> = None;
let mut metrics = DynamicMetrics::init("OptionalFields");
metrics.dynamic_field.set_value(1usize); metrics.user_tag = Flex::new("user_id").with_optional_value(user_id);
metrics.computed_metric =
Flex::new("session_id").with_optional_value(session_id.map(|s| s.len() as f64));
metrics.static_counter = 6;
}
async fn unset_then_set_example() {
println!("=== Unset Then Set Example ===");
let mut metrics = DynamicMetrics {
timestamp: SystemTime::now(),
operation: "ProcessRequest",
static_counter: 0,
dynamic_field: Flex::new("items_processed"), user_tag: Flex::new("request_source"), computed_metric: Flex::new("processing_time"), }
.append_on_drop(ServiceMetrics::sink());
let items_to_process = vec!["item1", "item2", "item3"];
let request_came_from_api = true;
let processed_count = items_to_process.len();
metrics.dynamic_field.set_value(processed_count);
if request_came_from_api {
metrics.user_tag.set_value("api".to_string());
} else {
metrics.user_tag.set_value("batch".to_string());
}
let start_time = std::time::Instant::now();
let processing_duration = start_time.elapsed();
metrics
.computed_metric
.set_value(processing_duration.as_millis() as f64);
metrics.static_counter = 7;
}
#[cfg(test)]
mod tests {
use super::*;
use metrique::writer::test_util;
#[test]
fn test_flex_dynamic_fields() {
let test_util::TestEntrySink { inspector, sink } = test_util::test_entry_sink();
let metrics = DynamicMetrics {
timestamp: SystemTime::now(),
operation: "TestOp",
static_counter: 100,
dynamic_field: Flex::new("custom_metric").with_value(42usize),
user_tag: Flex::new("user_id").with_value("user123".to_string()),
computed_metric: Flex::new("computed_value").with_value(3.14),
}
.append_on_drop(sink);
drop(metrics);
let entries = inspector.entries();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
assert_eq!(entry.values["Operation"], "TestOp");
assert_eq!(entry.metrics["StaticCounter"], 100);
assert_eq!(entry.metrics["custom_metric"], 42);
assert_eq!(entry.values["user_id"], "user123");
assert_eq!(entry.metrics["computed_value"], 3.14);
}
}