sentrystr_tracing/
lib.rs

1//! # SentryStr Tracing
2//!
3//! Tracing integration for SentryStr that seamlessly captures structured logs and sends them to Nostr relays.
4//!
5//! ## Quick Start
6//!
7//! ```rust
8//! use sentrystr_tracing::SentryStrTracingBuilder;
9//! use tracing::{info, warn, error};
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
13//!     let relays = vec!["wss://relay.damus.io".to_string()];
14//!
15//!     // Initialize tracing
16//!     SentryStrTracingBuilder::new()
17//!         .with_generated_keys_and_relays(relays)
18//!         .with_min_level(tracing::Level::INFO)
19//!         .init()
20//!         .await?;
21//!
22//!     // Now all tracing events are sent to Nostr
23//!     info!("Application started");
24//!     warn!(cpu_usage = 85.5, "High CPU usage");
25//!     error!(error_code = 500, "Database connection failed");
26//!
27//!     Ok(())
28//! }
29//! ```
30//!
31//! ## With Direct Message Alerts
32//!
33//! ```rust
34//! use sentrystr_tracing::{SentryStrTracingBuilder, builder::DirectMessageConfig};
35//! use sentrystr::Level;
36//! use nostr::Keys;
37//! use tracing::{info, error};
38//!
39//! #[tokio::main]
40//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
41//!     let relays = vec!["wss://relay.damus.io".to_string()];
42//!     let recipient = Keys::generate().public_key();
43//!
44//!     // Setup DM alerts for errors
45//!     let dm_config = DirectMessageConfig::new(recipient, relays.clone())
46//!         .with_min_level(Level::Error)
47//!         .with_nip17(true);
48//!
49//!     SentryStrTracingBuilder::new()
50//!         .with_generated_keys_and_relays(relays)
51//!         .with_direct_messaging(dm_config)
52//!         .init()
53//!         .await?;
54//!
55//!     info!("This won't send a DM");
56//!     error!("This will send a DM!"); // This triggers a direct message
57//!
58//!     Ok(())
59//! }
60//! ```
61//!
62//! ## Web Application Integration
63//!
64//! ```rust
65//! use sentrystr_tracing::SentryStrTracingBuilder;
66//! use tracing::{info, error, instrument};
67//!
68//! #[instrument]
69//! async fn handle_request(user_id: u64) -> Result<String, String> {
70//!     info!(user_id = user_id, "Processing request");
71//!
72//!     if user_id == 0 {
73//!         error!(user_id = user_id, "Invalid user ID");
74//!         return Err("Invalid user".to_string());
75//!     }
76//!
77//!     Ok("Success".to_string())
78//! }
79//!
80//! #[tokio::main]
81//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
82//!     SentryStrTracingBuilder::new()
83//!         .with_generated_keys_and_relays(vec!["wss://relay.damus.io".to_string()])
84//!         .init_with_env_filter("info,my_app=debug")
85//!         .await?;
86//!
87//!     // All function calls are now traced
88//!     let _ = handle_request(123).await;
89//!     let _ = handle_request(0).await; // This will log an error
90//!
91//!     Ok(())
92//! }
93//! ```
94
95pub mod builder;
96pub mod error;
97pub mod layer;
98pub mod visitor;
99
100pub use builder::SentryStrTracingBuilder;
101pub use error::TracingError;
102pub use layer::SentryStrLayer;
103pub use visitor::FieldVisitor;
104
105use sentrystr::{Event, Level};
106use std::collections::BTreeMap;
107use tracing::Metadata;
108
109pub type Result<T> = std::result::Result<T, TracingError>;
110
111pub fn convert_tracing_level(level: &tracing::Level) -> Level {
112    match *level {
113        tracing::Level::TRACE => Level::Debug,
114        tracing::Level::DEBUG => Level::Debug,
115        tracing::Level::INFO => Level::Info,
116        tracing::Level::WARN => Level::Warning,
117        tracing::Level::ERROR => Level::Error,
118    }
119}
120
121pub fn extract_event_metadata(metadata: &Metadata<'_>) -> BTreeMap<String, serde_json::Value> {
122    let mut fields = BTreeMap::new();
123
124    fields.insert(
125        "target".to_string(),
126        serde_json::Value::String(metadata.target().to_string()),
127    );
128    fields.insert(
129        "module_path".to_string(),
130        metadata
131            .module_path()
132            .map(|s| serde_json::Value::String(s.to_string()))
133            .unwrap_or(serde_json::Value::Null),
134    );
135    fields.insert(
136        "file".to_string(),
137        metadata
138            .file()
139            .map(|s| serde_json::Value::String(s.to_string()))
140            .unwrap_or(serde_json::Value::Null),
141    );
142    fields.insert(
143        "line".to_string(),
144        metadata
145            .line()
146            .map(|n| serde_json::Value::Number(n.into()))
147            .unwrap_or(serde_json::Value::Null),
148    );
149    fields.insert(
150        "level".to_string(),
151        serde_json::Value::String(metadata.level().to_string()),
152    );
153
154    fields
155}
156
157pub fn create_sentrystr_event(
158    message: String,
159    level: Level,
160    fields: BTreeMap<String, serde_json::Value>,
161    metadata_fields: BTreeMap<String, serde_json::Value>,
162) -> Event {
163    let mut event = Event::new().with_message(message).with_level(level);
164
165    for (key, value) in fields {
166        event = event.with_extra(&key, value);
167    }
168
169    for (key, value) in metadata_fields {
170        event = event.with_extra(format!("meta_{}", key), value);
171    }
172
173    event
174}