sentrystr_tracing/
layer.rs1use crate::{convert_tracing_level, create_sentrystr_event, extract_event_metadata, FieldVisitor};
2use sentrystr::{DirectMessageSender, MessageEvent, NostrSentryClient};
3use std::sync::Arc;
4use tokio::sync::RwLock;
5use tracing::{Event, Subscriber};
6use tracing_subscriber::{layer::Context, Layer};
7
8pub struct SentryStrLayer {
9 client: Arc<RwLock<NostrSentryClient>>,
10 dm_sender: Option<Arc<RwLock<DirectMessageSender>>>,
11 min_level: Option<tracing::Level>,
12 include_fields: bool,
13 include_metadata: bool,
14}
15
16impl SentryStrLayer {
17 pub fn new(client: NostrSentryClient) -> Self {
18 Self {
19 client: Arc::new(RwLock::new(client)),
20 dm_sender: None,
21 min_level: None,
22 include_fields: true,
23 include_metadata: true,
24 }
25 }
26
27 pub fn with_direct_messaging(mut self, dm_sender: DirectMessageSender) -> Self {
28 self.dm_sender = Some(Arc::new(RwLock::new(dm_sender)));
29 self
30 }
31
32 pub fn with_min_level(mut self, level: tracing::Level) -> Self {
33 self.min_level = Some(level);
34 self
35 }
36
37 pub fn with_fields(mut self, include: bool) -> Self {
38 self.include_fields = include;
39 self
40 }
41
42 pub fn with_metadata(mut self, include: bool) -> Self {
43 self.include_metadata = include;
44 self
45 }
46
47 fn should_process_event(&self, event_level: &tracing::Level) -> bool {
48 if let Some(min_level) = &self.min_level {
49 event_level <= min_level
50 } else {
51 true
52 }
53 }
54}
55
56impl<S> Layer<S> for SentryStrLayer
57where
58 S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
59{
60 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
61 let mut visitor = FieldVisitor::new();
62 event.record(&mut visitor);
63
64 let message = visitor.extract_message();
65 let level = convert_tracing_level(event.metadata().level());
66
67 if !self.should_process_event(event.metadata().level()) {
68 return;
69 }
70
71 let fields = if self.include_fields {
72 visitor.fields
73 } else {
74 std::collections::BTreeMap::new()
75 };
76
77 let metadata_fields = if self.include_metadata {
78 extract_event_metadata(event.metadata())
79 } else {
80 std::collections::BTreeMap::new()
81 };
82
83 let sentrystr_event = create_sentrystr_event(message, level, fields, metadata_fields);
84
85 let client = Arc::clone(&self.client);
86 let dm_sender = self.dm_sender.as_ref().map(Arc::clone);
87
88 tokio::spawn(async move {
89 let client = client.read().await;
90 if let Err(e) = client.capture_event(sentrystr_event.clone()).await {
91 eprintln!("Failed to send event to SentryStr: {}", e);
92 return;
93 }
94
95 if let Some(dm_sender) = dm_sender {
96 let dm_sender = dm_sender.read().await;
97 let message_event = MessageEvent {
98 event: sentrystr_event,
99 author: nostr::Keys::generate().public_key(),
100 nostr_event_id: nostr::EventId::all_zeros(),
101 received_at: chrono::Utc::now(),
102 };
103
104 if let Err(e) = dm_sender.send_message_for_event(&message_event).await {
105 eprintln!("Failed to send direct message: {}", e);
106 }
107 }
108 });
109 }
110}
111
112impl Clone for SentryStrLayer {
113 fn clone(&self) -> Self {
114 Self {
115 client: Arc::clone(&self.client),
116 dm_sender: self.dm_sender.as_ref().map(Arc::clone),
117 min_level: self.min_level,
118 include_fields: self.include_fields,
119 include_metadata: self.include_metadata,
120 }
121 }
122}