posthog_cli/
invocation_context.rs

1use anyhow::Result;
2use posthog_rs::Event;
3use reqwest::blocking::Client;
4use std::{
5    sync::{Mutex, OnceLock},
6    thread::JoinHandle,
7};
8use tracing::{debug, info, warn};
9
10use crate::utils::auth::{get_token, Token};
11
12// I've decided in my infinite wisdom that global state is fine, actually.
13pub static INVOCATION_CONTEXT: OnceLock<InvocationContext> = OnceLock::new();
14
15pub struct InvocationContext {
16    pub token: Token,
17    pub client: Client,
18
19    handles: Mutex<Vec<JoinHandle<()>>>,
20}
21
22pub fn context() -> &'static InvocationContext {
23    INVOCATION_CONTEXT.get().expect("Context has been set up")
24}
25
26pub fn init_context(host: Option<String>, skip_ssl: bool) -> Result<()> {
27    let mut token = get_token()?;
28    if let Some(host) = host {
29        // If the user passed a host, respect it
30        token.host = Some(host);
31    }
32
33    let client = reqwest::blocking::Client::builder()
34        .danger_accept_invalid_certs(skip_ssl)
35        .build()?;
36
37    INVOCATION_CONTEXT.get_or_init(|| InvocationContext::new(token, client));
38
39    // This is pulled at compile time, not runtime - we set it at build.
40    if let Some(token) = option_env!("POSTHOG_API_TOKEN") {
41        let ph_config = posthog_rs::ClientOptionsBuilder::default()
42            .api_key(token.to_string())
43            .request_timeout_seconds(5) // It's a CLI, 5 seconds is an eternity
44            .build()
45            .expect("Building PH config succeeds");
46        posthog_rs::init_global(ph_config).expect("Initializing PostHog client");
47    } else {
48        warn!("Posthog api token not set at build time - is this a debug build?");
49    };
50
51    Ok(())
52}
53
54impl InvocationContext {
55    pub fn new(token: Token, client: Client) -> Self {
56        Self {
57            token,
58            client,
59            handles: Default::default(),
60        }
61    }
62
63    pub fn capture_command_invoked(&self, command: &str) {
64        let env_id = &self.token.env_id;
65        let event_name = "posthog cli command run".to_string();
66        let mut event = Event::new_anon(event_name);
67
68        event
69            .insert_prop("command_name", command)
70            .expect("Inserting command prop succeeds");
71
72        event
73            .insert_prop("env_id", env_id)
74            .expect("Inserting env_id prop succeeds");
75
76        let handle = std::thread::spawn(move || {
77            debug!("Capturing event");
78            let res = posthog_rs::capture(event); // Purposefully ignore errors here
79            if let Err(err) = res {
80                debug!("Failed to capture event: {:?}", err);
81            } else {
82                debug!("Event captured successfully");
83            }
84        });
85
86        self.handles.lock().unwrap().push(handle);
87    }
88
89    pub fn finish(&self) {
90        info!("Finishing up....");
91
92        self.handles
93            .lock()
94            .unwrap()
95            .drain(..)
96            .for_each(|handle| handle.join().unwrap());
97
98        info!("Finished!")
99    }
100}