Skip to main content

kaizen/provider/
mod.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Query-back from telemetry providers (PostHog, Datadog). OTLP has no pull in v1.
3
4use anyhow::Result;
5use serde_json::Value;
6use std::time::Duration;
7
8#[cfg(feature = "telemetry-datadog")]
9mod datadog;
10#[cfg(feature = "telemetry-posthog")]
11mod posthog;
12mod pull_import;
13
14/// Trailing time window for a pull (coarse; provider maps to its API).
15#[derive(Debug, Clone, Copy)]
16pub struct PullWindow {
17    pub days: u32,
18}
19
20impl Default for PullWindow {
21    fn default() -> Self {
22        Self { days: 7 }
23    }
24}
25
26/// One page of remote rows; cursor is opaque to Kaizen.
27#[derive(Debug, Clone, Default)]
28pub struct PullPage {
29    pub next_cursor: Option<String>,
30    pub items: Vec<Value>,
31}
32
33pub use pull_import::import_pull_page_to_remote;
34
35/// Abstraction for PostHog / Datadog query APIs. OTLP is export-only, not a query authority.
36pub trait TelemetryQueryProvider: Send + Sync {
37    fn health(&self) -> Result<()>;
38    /// Provider-reported label for debugging (e.g. `posthog-2024-01`).
39    fn schema_version(&self) -> &str;
40    fn pull(&self, window: PullWindow, cursor: Option<&str>) -> Result<PullPage>;
41}
42
43// Submodules `posthog` / `datadog` are feature-gated; keep timeout here for a single value.
44#[allow(dead_code)]
45const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
46
47/// Build a `TelemetryQueryProvider` for the configured query authority, or `None` when pull is off.
48pub fn from_config(
49    q: &crate::core::config::TelemetryQueryConfig,
50) -> Option<std::sync::Arc<dyn TelemetryQueryProvider>> {
51    use crate::core::config::QueryAuthority;
52    match q.provider {
53        QueryAuthority::None => None,
54        #[cfg(feature = "telemetry-posthog")]
55        QueryAuthority::Posthog => {
56            // Real impl needs host/key from env like exporters; minimal stub for compile.
57            posthog::posthog_from_env()
58        }
59        #[cfg(not(feature = "telemetry-posthog"))]
60        QueryAuthority::Posthog => {
61            tracing::warn!(
62                "telemetry query provider is posthog but `telemetry-posthog` feature is off"
63            );
64            None
65        }
66        #[cfg(feature = "telemetry-datadog")]
67        QueryAuthority::Datadog => datadog::datadog_from_env(),
68        #[cfg(not(feature = "telemetry-datadog"))]
69        QueryAuthority::Datadog => {
70            tracing::warn!(
71                "telemetry query provider is datadog but `telemetry-datadog` feature is off"
72            );
73            None
74        }
75    }
76}
77
78#[allow(dead_code)]
79pub(crate) fn http_timeout() -> Duration {
80    HTTP_TIMEOUT
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    struct Nop;
88    impl TelemetryQueryProvider for Nop {
89        fn health(&self) -> Result<()> {
90            Ok(())
91        }
92        fn schema_version(&self) -> &'static str {
93            "test"
94        }
95        fn pull(&self, _window: PullWindow, _cursor: Option<&str>) -> Result<PullPage> {
96            Ok(PullPage::default())
97        }
98    }
99
100    #[test]
101    fn nop_pull_empty() {
102        let p = Nop;
103        assert_eq!(p.schema_version(), "test");
104        let page = p.pull(PullWindow { days: 1 }, None).unwrap();
105        assert!(page.items.is_empty());
106    }
107}