Skip to main content

packc/
telemetry.rs

1use anyhow::Result;
2use greentic_config_types::{TelemetryConfig, TelemetryExporterKind};
3pub use greentic_telemetry::with_task_local;
4use greentic_telemetry::{
5    TelemetryConfig as ServiceTelemetryConfig, TelemetryCtx,
6    export::{ExportConfig, ExportMode, Sampling},
7    init_telemetry_auto, init_telemetry_from_config, set_current_telemetry_ctx,
8};
9use greentic_types::TenantCtx;
10
11/// Install the default Greentic telemetry stack for the given service.
12pub fn install(service_name: &str) -> Result<()> {
13    init_telemetry_auto(ServiceTelemetryConfig {
14        service_name: service_name.to_string(),
15    })
16}
17
18/// Install telemetry honoring greentic-config telemetry settings.
19pub fn install_with_config(service_name: &str, cfg: &TelemetryConfig) -> Result<()> {
20    if !cfg.enabled || matches!(cfg.exporter, TelemetryExporterKind::None) {
21        return Ok(());
22    }
23
24    let export = match cfg.exporter {
25        TelemetryExporterKind::Otlp => export_config(ExportMode::OtlpGrpc, cfg),
26        TelemetryExporterKind::Stdout => export_config(ExportMode::JsonStdout, cfg),
27        TelemetryExporterKind::Gcp => export_config(ExportMode::GcpCloudTrace, cfg),
28        TelemetryExporterKind::Azure => export_config(ExportMode::AzureAppInsights, cfg),
29        TelemetryExporterKind::Aws => export_config(ExportMode::AwsXRay, cfg),
30        TelemetryExporterKind::None => unreachable!("handled above"),
31    };
32
33    init_telemetry_from_config(
34        ServiceTelemetryConfig {
35            service_name: service_name.to_string(),
36        },
37        export,
38    )
39}
40
41fn export_config(mode: ExportMode, cfg: &TelemetryConfig) -> ExportConfig {
42    let mut export = ExportConfig::default();
43    export.mode = mode;
44    export.endpoint = cfg.endpoint.clone();
45    export.sampling = Sampling::TraceIdRatio(cfg.sampling as f64);
46    export.compression = None;
47    export
48}
49
50/// Map the provided tenant context into the task-local telemetry slot.
51pub fn set_current_tenant_ctx(ctx: &TenantCtx) {
52    let mut telemetry = TelemetryCtx::new(ctx.tenant_id.as_ref()).with_env(ctx.env.as_str());
53
54    if let Some(session) = ctx.session_id() {
55        telemetry = telemetry.with_session(session);
56    }
57    if let Some(flow) = ctx.flow_id() {
58        telemetry = telemetry.with_flow(flow);
59    }
60    if let Some(node) = ctx.node_id() {
61        telemetry = telemetry.with_node(node);
62    }
63    if let Some(provider) = ctx.provider_id() {
64        telemetry = telemetry.with_provider(provider);
65    }
66    // B11 rollout identifiers ride the free-form attributes map under the same
67    // canonical keys the greentic-types bridge uses; mirror that projection so
68    // packc telemetry carries env + revision/bundle/customer attribution too.
69    if let Some(v) = ctx.attributes.get("gt.customer_id") {
70        telemetry = telemetry.with_customer_id(v);
71    }
72    if let Some(v) = ctx.attributes.get("gt.deployment_id") {
73        telemetry = telemetry.with_deployment_id(v);
74    }
75    if let Some(v) = ctx.attributes.get("gt.bundle_id") {
76        telemetry = telemetry.with_bundle_id(v);
77    }
78    if let Some(v) = ctx.attributes.get("gt.revision_id") {
79        telemetry = telemetry.with_revision_id(v);
80    }
81
82    set_current_telemetry_ctx(telemetry);
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use greentic_config_types::TelemetryConfig;
89    use greentic_types::{EnvId, TenantCtx, TenantId};
90    use std::str::FromStr;
91
92    #[test]
93    fn install_with_config_is_noop_when_disabled() {
94        let cfg = TelemetryConfig {
95            enabled: false,
96            exporter: TelemetryExporterKind::Otlp,
97            endpoint: Some("http://localhost:4317".to_string()),
98            sampling: 1.0,
99        };
100
101        install_with_config("packc-test", &cfg).expect("disabled config should be a no-op");
102    }
103
104    #[test]
105    fn install_with_config_is_noop_for_none_exporter() {
106        let cfg = TelemetryConfig {
107            enabled: true,
108            exporter: TelemetryExporterKind::None,
109            endpoint: None,
110            sampling: 0.25,
111        };
112
113        install_with_config("packc-test", &cfg).expect("none exporter should be a no-op");
114    }
115
116    #[test]
117    fn set_current_tenant_ctx_accepts_full_context() {
118        let tenant = TenantId::from_str("tenant-a").expect("tenant");
119        let env = EnvId::from_str("dev").expect("env");
120        let mut ctx = TenantCtx::new(env, tenant)
121            .with_session("sess-123")
122            .with_flow("flow.main")
123            .with_node("node-1")
124            .with_provider("provider-1");
125        // Exercise the B11 rollout-ID projection branches.
126        ctx.attributes
127            .insert("gt.bundle_id".to_string(), "customer.support".to_string());
128
129        set_current_tenant_ctx(&ctx);
130    }
131}