sentrystr_tracing/
builder.rs

1use crate::{Result, SentryStrLayer, TracingError};
2use nostr::prelude::*;
3use nostr_sdk::prelude::*;
4use sentrystr::{Config, DirectMessageBuilder, NostrSentryClient};
5use tracing_subscriber::prelude::*;
6
7/// Builder for configuring SentryStr tracing integration.
8///
9/// # Examples
10///
11/// ```rust
12/// use sentrystr_tracing::SentryStrTracingBuilder;
13///
14/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
15/// // Basic setup
16/// SentryStrTracingBuilder::new()
17///     .with_generated_keys_and_relays(vec!["wss://relay.damus.io".to_string()])
18///     .with_min_level(tracing::Level::INFO)
19///     .init()
20///     .await?;
21/// # Ok(())
22/// # }
23/// ```
24pub struct SentryStrTracingBuilder {
25    config: Option<Config>,
26    dm_config: Option<DirectMessageConfig>,
27    min_level: Option<tracing::Level>,
28    include_fields: bool,
29    include_metadata: bool,
30}
31
32/// Configuration for direct message alerts in tracing.
33///
34/// # Examples
35///
36/// ```rust
37/// use sentrystr_tracing::builder::DirectMessageConfig;
38/// use sentrystr::Level;
39/// use nostr::PublicKey;
40///
41/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
42/// let recipient = PublicKey::from_bech32("npub1...")?;
43/// let relays = vec!["wss://relay.damus.io".to_string()];
44///
45/// let dm_config = DirectMessageConfig::new(recipient, relays)
46///     .with_min_level(Level::Error)
47///     .with_nip17(true);
48/// # Ok(())
49/// # }
50/// ```
51#[derive(Clone)]
52pub struct DirectMessageConfig {
53    pub recipient_pubkey: PublicKey,
54    pub min_level: Option<sentrystr::Level>,
55    pub use_nip17: bool,
56    pub relays: Vec<String>,
57}
58
59impl SentryStrTracingBuilder {
60    /// Creates a new SentryStrTracingBuilder with default settings.
61    pub fn new() -> Self {
62        Self {
63            config: None,
64            dm_config: None,
65            min_level: None,
66            include_fields: true,
67            include_metadata: true,
68        }
69    }
70
71    pub fn with_config(mut self, config: Config) -> Self {
72        self.config = Some(config);
73        self
74    }
75
76    pub fn with_secret_key_and_relays(mut self, secret_key: String, relays: Vec<String>) -> Self {
77        self.config = Some(Config::new(secret_key, relays));
78        self
79    }
80
81    pub fn with_generated_keys_and_relays(mut self, relays: Vec<String>) -> Self {
82        let keys = Keys::generate();
83        self.config = Some(Config::new(
84            keys.secret_key().display_secret().to_string(),
85            relays,
86        ));
87        self
88    }
89
90    pub fn with_direct_messaging(mut self, dm_config: DirectMessageConfig) -> Self {
91        self.dm_config = Some(dm_config);
92        self
93    }
94
95    pub fn with_dm_recipient(mut self, recipient_pubkey: PublicKey, relays: Vec<String>) -> Self {
96        self.dm_config = Some(DirectMessageConfig {
97            recipient_pubkey,
98            min_level: None,
99            use_nip17: true,
100            relays,
101        });
102        self
103    }
104
105    pub fn with_min_level(mut self, level: tracing::Level) -> Self {
106        self.min_level = Some(level);
107        self
108    }
109
110    pub fn with_fields(mut self, include: bool) -> Self {
111        self.include_fields = include;
112        self
113    }
114
115    pub fn with_metadata(mut self, include: bool) -> Self {
116        self.include_metadata = include;
117        self
118    }
119
120    pub async fn build(self) -> Result<SentryStrLayer> {
121        let config = self
122            .config
123            .ok_or_else(|| TracingError::Config("SentryStr config is required".to_string()))?;
124
125        let client = NostrSentryClient::new(config).await?;
126
127        let mut layer = SentryStrLayer::new(client)
128            .with_fields(self.include_fields)
129            .with_metadata(self.include_metadata);
130
131        if let Some(min_level) = self.min_level {
132            layer = layer.with_min_level(min_level);
133        }
134
135        if let Some(dm_config) = self.dm_config {
136            let dm_keys = Keys::generate();
137            let dm_client = Client::new(dm_keys.clone());
138
139            for relay in &dm_config.relays {
140                dm_client.add_relay(relay).await?;
141            }
142            dm_client.connect().await;
143
144            let dm_sender = DirectMessageBuilder::new()
145                .with_client(dm_client)
146                .with_keys(dm_keys)
147                .with_recipient(dm_config.recipient_pubkey)
148                .with_min_level(
149                    dm_config
150                        .min_level
151                        .unwrap_or(sentrystr::Level::Warning),
152                )
153                .with_nip17(dm_config.use_nip17)
154                .build()?;
155
156            layer = layer.with_direct_messaging(dm_sender);
157        }
158
159        Ok(layer)
160    }
161
162    pub async fn init(self) -> Result<()> {
163        let layer = self.build().await?;
164
165        tracing_subscriber::registry()
166            .with(layer)
167            .with(tracing_subscriber::fmt::layer())
168            .init();
169
170        Ok(())
171    }
172
173    pub async fn init_with_env_filter(self, env_filter: &str) -> Result<()> {
174        let layer = self.build().await?;
175
176        tracing_subscriber::registry()
177            .with(tracing_subscriber::EnvFilter::new(env_filter))
178            .with(layer)
179            .with(tracing_subscriber::fmt::layer())
180            .init();
181
182        Ok(())
183    }
184}
185
186impl Default for SentryStrTracingBuilder {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192impl DirectMessageConfig {
193    pub fn new(recipient_pubkey: PublicKey, relays: Vec<String>) -> Self {
194        Self {
195            recipient_pubkey,
196            min_level: None,
197            use_nip17: true,
198            relays,
199        }
200    }
201
202    pub fn with_min_level(mut self, level: sentrystr::Level) -> Self {
203        self.min_level = Some(level);
204        self
205    }
206
207    pub fn with_nip17(mut self, use_nip17: bool) -> Self {
208        self.use_nip17 = use_nip17;
209        self
210    }
211}