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}