1use std::collections::HashMap;
4use std::sync::Arc;
5
6use crate::error::TelemetryError;
7use crate::event::Event;
8use crate::machine::MachineId;
9use crate::sink::{NoopSink, TelemetrySink};
10
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
19pub enum CollectionPolicy {
20 #[default]
22 Disabled,
23 Enabled,
25}
26
27#[derive(Clone)]
31pub struct TelemetryContext {
32 tool: Arc<String>,
33 tool_version: Arc<String>,
34 machine_id: Arc<String>,
35 sink: Arc<dyn TelemetrySink>,
36 policy: CollectionPolicy,
37}
38
39impl std::fmt::Debug for TelemetryContext {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("TelemetryContext")
42 .field("tool", &self.tool)
43 .field("tool_version", &self.tool_version)
44 .field("machine_id_len", &self.machine_id.len())
45 .field("policy", &self.policy)
46 .field("sink", &"<dyn TelemetrySink>")
47 .finish()
48 }
49}
50
51impl TelemetryContext {
52 pub fn builder() -> TelemetryContextBuilder {
55 TelemetryContextBuilder::default()
56 }
57
58 #[must_use]
60 pub const fn policy(&self) -> CollectionPolicy {
61 self.policy
62 }
63
64 pub async fn record(&self, event_name: &str) -> Result<(), TelemetryError> {
70 if self.policy == CollectionPolicy::Disabled {
71 return Ok(());
72 }
73 let event = Event::now(event_name, &*self.tool, &*self.tool_version, &*self.machine_id);
74 self.sink.emit(&event).await
75 }
76
77 pub async fn record_with_attrs(
80 &self,
81 event_name: &str,
82 attrs: HashMap<String, String>,
83 ) -> Result<(), TelemetryError> {
84 if self.policy == CollectionPolicy::Disabled {
85 return Ok(());
86 }
87 let mut event = Event::now(event_name, &*self.tool, &*self.tool_version, &*self.machine_id);
88 event.attrs = attrs;
89 self.sink.emit(&event).await
90 }
91
92 pub async fn flush(&self) -> Result<(), TelemetryError> {
94 if self.policy == CollectionPolicy::Disabled {
95 return Ok(());
96 }
97 self.sink.flush().await
98 }
99}
100
101#[must_use]
103#[derive(Default)]
104pub struct TelemetryContextBuilder {
105 tool: Option<String>,
106 tool_version: Option<String>,
107 salt: Option<String>,
108 sink: Option<Arc<dyn TelemetrySink>>,
109 policy: CollectionPolicy,
110}
111
112impl TelemetryContextBuilder {
113 pub fn tool(mut self, tool: impl Into<String>) -> Self {
115 self.tool = Some(tool.into());
116 self
117 }
118
119 pub fn tool_version(mut self, version: impl Into<String>) -> Self {
121 self.tool_version = Some(version.into());
122 self
123 }
124
125 pub fn salt(mut self, salt: impl Into<String>) -> Self {
147 self.salt = Some(salt.into());
148 self
149 }
150
151 pub fn sink(mut self, sink: Arc<dyn TelemetrySink>) -> Self {
153 self.sink = Some(sink);
154 self
155 }
156
157 pub const fn policy(mut self, policy: CollectionPolicy) -> Self {
160 self.policy = policy;
161 self
162 }
163
164 #[must_use]
172 pub fn build(self) -> TelemetryContext {
173 let tool = self.tool.expect("TelemetryContextBuilder: tool is required");
174 let tool_version =
175 self.tool_version.expect("TelemetryContextBuilder: tool_version is required");
176 let sink = self.sink.unwrap_or_else(|| Arc::new(NoopSink));
177 let policy = self.policy;
178
179 let machine_id = match policy {
182 CollectionPolicy::Disabled => String::new(),
183 CollectionPolicy::Enabled => {
184 let salt = self
185 .salt
186 .expect("TelemetryContextBuilder: salt is required when policy is Enabled");
187 MachineId::derive(&salt)
188 }
189 };
190
191 TelemetryContext {
192 tool: Arc::new(tool),
193 tool_version: Arc::new(tool_version),
194 machine_id: Arc::new(machine_id),
195 sink,
196 policy,
197 }
198 }
199}