better_posthog/lib.rs
1//! An ergonomic Rust SDK for [PostHog](https://posthog.com/).
2//!
3//! This crate offloads network I/O to a background thread, ensuring the main application remains non-blocking and error-free.
4//!
5//! # Quick Start
6//!
7//! ```no_run
8//! use better_posthog::{events, Event};
9//!
10//! // Initialize the client.
11//! let _guard = better_posthog::init(better_posthog::ClientOptions {
12//! api_key: Some("phc_your_api_key".into()),
13//! ..Default::default()
14//! });
15//!
16//! // Capture events.
17//! events::capture(Event::new("page_view", "user_123"));
18//!
19//! // Or use the builder pattern for events.
20//! events::capture(
21//! Event::builder()
22//! .event("button_click")
23//! .distinct_id("user_123")
24//! .property("button_id", "submit")
25//! .build()
26//! );
27//!
28//! // Batch multiple events.
29//! events::batch(vec![
30//! Event::new("event_1", "user_123"),
31//! Event::new("event_2", "user_123"),
32//! ]);
33//!
34//! // Guard is dropped here, triggering graceful shutdown.
35//! ```
36
37mod client;
38mod context;
39mod worker;
40
41pub use client::{ApiKey, BeforeSendFn, ClientOptions, Host};
42use client::{CLIENT, Client};
43
44pub mod events;
45pub use events::{Event, EventBuilder};
46
47/// Guard that manages the PostHog client lifecycle.
48///
49/// When dropped, this guard triggers graceful shutdown of the background worker,
50/// attempting to flush pending events within the configured timeout.
51///
52/// # Examples
53///
54/// ```no_run
55/// use better_posthog::{init, ClientOptions};
56///
57/// let _guard = init(ClientOptions::new("phc_your_api_key"));
58///
59/// // ... application code ...
60///
61/// // Guard is dropped here, triggering graceful shutdown
62/// ```
63#[must_use = "ClientGuard must be held for the duration of the application"]
64pub struct ClientGuard {
65 shutdown_timeout: std::time::Duration,
66}
67
68impl Drop for ClientGuard {
69 fn drop(&mut self) {
70 if let Some(client) = CLIENT.get()
71 && !client.worker.flush(self.shutdown_timeout)
72 {
73 log::warn!(
74 "PostHog shutdown timed out after {:?}, some events may be lost",
75 self.shutdown_timeout
76 );
77 }
78 }
79}
80
81/// Initializes the PostHog client with the given configuration.
82///
83/// Returns a [`ClientGuard`] that must be held for the duration of the application.
84/// When the guard is dropped, it triggers graceful shutdown of the background worker.
85///
86/// If no API key is provided in the configuration, the client will not be initialized.
87/// Event capture calls will be no-ops in this case.
88///
89/// # Panics
90///
91/// Panics if called more than once.
92///
93/// # Examples
94///
95/// ```no_run
96/// let _guard = better_posthog::init(better_posthog::ClientOptions {
97/// api_key: Some("phc_your_api_key".into()),
98/// host: better_posthog::Host::EU,
99/// shutdown_timeout: std::time::Duration::from_secs(5),
100/// });
101/// ```
102pub fn init(options: impl Into<ClientOptions>) -> ClientGuard {
103 let options = options.into();
104 let shutdown_timeout = options.shutdown_timeout;
105
106 if options.api_key.is_none() {
107 log::warn!("PostHog client not initialized: no API key provided");
108 return ClientGuard { shutdown_timeout };
109 }
110
111 assert!(
112 CLIENT.set(Client::new(options)).is_ok(),
113 "PostHog client already initialized"
114 );
115
116 ClientGuard { shutdown_timeout }
117}
118
119/// Flushes pending events, waiting up to the specified timeout.
120///
121/// Returns `true` if the flush completed within the timeout.
122///
123/// # Examples
124///
125/// ```no_run
126/// if !better_posthog::flush(std::time::Duration::from_secs(5)) {
127/// eprintln!("Flush timed out");
128/// }
129/// ```
130pub fn flush(timeout: std::time::Duration) -> bool {
131 #[allow(clippy::option_if_let_else)]
132 if let Some(client) = CLIENT.get() {
133 client.worker.flush(timeout)
134 } else {
135 log::warn!("PostHog client not initialized");
136 false
137 }
138}