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    use greentic_types::telemetry::attr_keys;
53
54    let mut telemetry = TelemetryCtx::new(ctx.tenant_id.as_ref()).with_env(ctx.env.as_str());
55
56    if let Some(session) = ctx.session_id() {
57        telemetry = telemetry.with_session(session);
58    }
59    if let Some(flow) = ctx.flow_id() {
60        telemetry = telemetry.with_flow(flow);
61    }
62    if let Some(node) = ctx.node_id() {
63        telemetry = telemetry.with_node(node);
64    }
65    if let Some(provider) = ctx.provider_id() {
66        telemetry = telemetry.with_provider(provider);
67    }
68    // B11 rollout identifiers ride the free-form attributes map under the same
69    // canonical keys the greentic-types bridge uses; mirror that projection so
70    // packc telemetry carries env + revision/bundle/customer attribution too.
71    if let Some(v) = ctx.attributes.get(attr_keys::CUSTOMER_ID) {
72        telemetry = telemetry.with_customer_id(v);
73    }
74    if let Some(v) = ctx.attributes.get(attr_keys::DEPLOYMENT_ID) {
75        telemetry = telemetry.with_deployment_id(v);
76    }
77    if let Some(v) = ctx.attributes.get(attr_keys::BUNDLE_ID) {
78        telemetry = telemetry.with_bundle_id(v);
79    }
80    if let Some(v) = ctx.attributes.get(attr_keys::REVISION_ID) {
81        telemetry = telemetry.with_revision_id(v);
82    }
83
84    set_current_telemetry_ctx(telemetry);
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use greentic_config_types::TelemetryConfig;
91    use greentic_types::{EnvId, TenantCtx, TenantId};
92    use std::str::FromStr;
93
94    #[test]
95    fn install_with_config_is_noop_when_disabled() {
96        let cfg = TelemetryConfig {
97            enabled: false,
98            exporter: TelemetryExporterKind::Otlp,
99            endpoint: Some("http://localhost:4317".to_string()),
100            sampling: 1.0,
101        };
102
103        install_with_config("packc-test", &cfg).expect("disabled config should be a no-op");
104    }
105
106    #[test]
107    fn install_with_config_is_noop_for_none_exporter() {
108        let cfg = TelemetryConfig {
109            enabled: true,
110            exporter: TelemetryExporterKind::None,
111            endpoint: None,
112            sampling: 0.25,
113        };
114
115        install_with_config("packc-test", &cfg).expect("none exporter should be a no-op");
116    }
117
118    #[test]
119    fn set_current_tenant_ctx_accepts_full_context() {
120        let tenant = TenantId::from_str("tenant-a").expect("tenant");
121        let env = EnvId::from_str("dev").expect("env");
122        let mut ctx = TenantCtx::new(env, tenant)
123            .with_session("sess-123")
124            .with_flow("flow.main")
125            .with_node("node-1")
126            .with_provider("provider-1");
127        // Exercise the B11 rollout-ID projection branches.
128        ctx.attributes.insert(
129            greentic_types::telemetry::attr_keys::BUNDLE_ID.to_string(),
130            "customer.support".to_string(),
131        );
132
133        set_current_tenant_ctx(&ctx);
134    }
135}