tracing_layer_slack/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub use tracing_layer_core::BackgroundWorker;
4pub use tracing_layer_core::layer::WebhookLayer;
5pub use tracing_layer_core::filters::EventFilters;
6use serde::Serialize;
7use tracing_layer_core::layer::WebhookLayerBuilder;
8use tracing_layer_core::{Config, WebhookMessage, WebhookMessageFactory, WebhookMessageInputs};
9
10/// Layer for forwarding tracing events to Slack.
11pub struct SlackLayer;
12
13impl SlackLayer {
14    pub fn builder(app_name: String, target_filters: EventFilters) -> WebhookLayerBuilder<SlackConfig, Self> {
15        WebhookLayer::builder(app_name, target_filters)
16    }
17}
18
19impl WebhookMessageFactory for SlackLayer {
20    fn create(inputs: WebhookMessageInputs) -> impl WebhookMessage {
21        let target = inputs.target;
22        let span = inputs.span;
23        let metadata = inputs.metadata;
24        let message = inputs.message;
25        let app_name = inputs.app_name;
26        let source_file = inputs.source_file;
27        let source_line = inputs.source_line;
28        let event_level = inputs.event_level;
29
30        #[cfg(feature = "blocks")]
31        {
32            let event_level_emoji = match event_level {
33                tracing::Level::TRACE => ":mag:",
34                tracing::Level::DEBUG => ":bug:",
35                tracing::Level::INFO => ":information_source:",
36                tracing::Level::WARN => ":warning:",
37                tracing::Level::ERROR => ":x:",
38            };
39            let blocks = serde_json::json!([
40                {
41                    "type": "context",
42                    "elements": [
43                        {
44                            "type": "mrkdwn",
45                            "text": format!("{} - {} *{}*", app_name, event_level_emoji, event_level),
46                        }
47                    ]
48                },
49                {
50                    "type": "section",
51                    "text": {
52                        "type": "mrkdwn",
53                        "text": format!("\"_{}_\"", message),
54                    }
55                },
56                {
57                    "type": "section",
58                    "fields": [
59                        {
60                            "type": "mrkdwn",
61                            "text": format!("*Target Span*\n{}::{}", target, span)
62                        },
63                        {
64                            "type": "mrkdwn",
65                            "text": format!("*Source*\n{}#L{}", source_file, source_line)
66                        }
67                    ]
68                },
69                {
70                    "type": "section",
71                    "text": {
72                        "type": "mrkdwn",
73                        "text": "*Metadata:*"
74                    }
75                },
76                {
77                    "type": "section",
78                    "text": {
79                        "type": "mrkdwn",
80                        "text": format!("```\n{}\n```", metadata)
81                    }
82                }
83            ]);
84            let blocks_json = blocks.to_string();
85            SlackMessagePayload {
86                text: None,
87                blocks: Some(blocks_json),
88                webhook_url: inputs.webhook_url.to_string(),
89            }
90        }
91        #[cfg(not(feature = "blocks"))]
92        {
93            let event_level = event.metadata().level().as_str();
94            let source_file = event.metadata().file().unwrap_or("Unknown");
95            let source_line = event.metadata().line().unwrap_or(0);
96            let payload = format!(
97                concat!(
98                    "*Trace from {}*\n",
99                    "*Event [{}]*: \"{}\"\n",
100                    "*Target*: _{}_\n",
101                    "*Span*: _{}_\n",
102                    "*Metadata*:\n",
103                    "```",
104                    "{}",
105                    "```\n",
106                    "*Source*: _{}#L{}_",
107                ),
108                app_name, event_level, message, span, target, metadata, source_file, source_line,
109            );
110            SlackMessagePayload {
111                text: Some(payload),
112                blocks: None,
113                webhook_url: webhook_url.to_string(),
114            }
115        }
116    }
117}
118
119/// The message sent to Slack. The logged record being "drained" will be
120/// converted into this format.
121#[derive(Debug, Clone, Serialize)]
122pub(crate) struct SlackMessagePayload {
123    #[serde(skip_serializing_if = "Option::is_none")]
124    text: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    blocks: Option<String>,
127    #[serde(skip_serializing)]
128    webhook_url: String,
129}
130
131impl WebhookMessage for SlackMessagePayload {
132    fn webhook_url(&self) -> &str {
133        self.webhook_url.as_str()
134    }
135
136    fn serialize(&self) -> String {
137        serde_json::to_string(self).expect("failed to serialize slack message")
138    }
139}
140
141/// Configuration describing how to forward tracing events to Slack.
142pub struct SlackConfig {
143    pub(crate) webhook_url: String,
144}
145
146impl SlackConfig {
147    pub fn new(webhook_url: String) -> Self {
148        Self { webhook_url }
149    }
150
151    /// Create a new config for forwarding messages to Slack using configuration
152    /// available in the environment.
153    ///
154    /// Required env vars:
155    ///   * SLACK_WEBHOOK_URL
156    pub fn new_from_env() -> Self {
157        Self::new(std::env::var("SLACK_WEBHOOK_URL").expect("slack webhook url in env"))
158    }
159}
160
161impl Default for SlackConfig {
162    fn default() -> Self {
163        Self::new_from_env()
164    }
165}
166
167impl Config for SlackConfig {
168    fn webhook_url(&self) -> &str {
169        &self.webhook_url
170    }
171
172    fn new_from_env() -> Self where Self: Sized {
173        Self::new_from_env()
174    }
175}
176
177#[cfg(test)]
178mod tests {
179
180}