Skip to main content

saluki_core/data_model/event/service_check/
mod.rs

1//! Service checks.
2
3use saluki_common::iter::ReusableDeduplicator;
4use saluki_context::tags::TagSet;
5use serde::{ser::SerializeMap as _, Serialize, Serializer};
6use stringtheory::MetaString;
7
8/// Service status.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum CheckStatus {
11    /// The service is operating normally.
12    Ok,
13
14    /// The service is in a warning state.
15    Warning,
16
17    /// The service is in a critical state.
18    Critical,
19
20    /// The service is in an unknown state.
21    Unknown,
22}
23
24/// A service check.
25///
26/// Service checks represent the status of a service at a particular point in time. Checks are simplistic, with a basic
27/// message, status enum (OK vs warning vs critical, etc), timestamp, and tags.
28#[derive(Clone, Debug, PartialEq)]
29pub struct ServiceCheck {
30    name: MetaString,
31    status: CheckStatus,
32    timestamp: Option<u64>,
33    hostname: MetaString,
34    message: MetaString,
35    tags: TagSet,
36    origin_tags: TagSet,
37}
38
39impl ServiceCheck {
40    /// Returns the name of the check.
41    pub fn name(&self) -> &str {
42        &self.name
43    }
44
45    /// Returns the status of the check.
46    pub fn status(&self) -> CheckStatus {
47        self.status
48    }
49
50    /// Returns the timestamp of the check.
51    ///
52    /// This is a Unix timestamp, or the number of seconds since the Unix epoch.
53    pub fn timestamp(&self) -> Option<u64> {
54        self.timestamp
55    }
56
57    /// Returns the host where the check originated from.
58    pub fn hostname(&self) -> Option<&str> {
59        if self.hostname.is_empty() {
60            None
61        } else {
62            Some(&self.hostname)
63        }
64    }
65
66    /// Returns the message associated with the check.
67    pub fn message(&self) -> Option<&str> {
68        if self.message.is_empty() {
69            None
70        } else {
71            Some(&self.message)
72        }
73    }
74
75    /// Returns the tags associated with the check.
76    pub fn tags(&self) -> &TagSet {
77        &self.tags
78    }
79
80    /// Returns the origin tags associated with the check.
81    pub fn origin_tags(&self) -> &TagSet {
82        &self.origin_tags
83    }
84
85    /// Creates a `ServiceCheck` from the given name and status
86    pub fn new(name: impl Into<MetaString>, status: CheckStatus) -> Self {
87        Self {
88            name: name.into(),
89            status,
90            timestamp: None,
91            hostname: MetaString::empty(),
92            message: MetaString::empty(),
93            tags: TagSet::default(),
94            origin_tags: TagSet::default(),
95        }
96    }
97
98    /// Set the timestamp.
99    ///
100    /// Represented as a Unix timestamp, or the number of seconds since the Unix epoch.
101    ///
102    /// This variant is specifically for use in builder-style APIs.
103    pub fn with_timestamp(mut self, timestamp: impl Into<Option<u64>>) -> Self {
104        self.timestamp = timestamp.into();
105        self
106    }
107
108    /// Set the hostname where the service check originated from.
109    ///
110    /// This variant is specifically for use in builder-style APIs.
111    pub fn with_hostname(mut self, hostname: impl Into<Option<MetaString>>) -> Self {
112        self.hostname = match hostname.into() {
113            Some(hostname) => hostname,
114            None => MetaString::empty(),
115        };
116        self
117    }
118
119    /// Set the hostname where the service check originated from.
120    pub fn set_hostname(&mut self, hostname: impl Into<Option<MetaString>>) {
121        self.hostname = match hostname.into() {
122            Some(hostname) => hostname,
123            None => MetaString::empty(),
124        };
125    }
126
127    /// Set the tags of the service check
128    ///
129    /// This variant is specifically for use in builder-style APIs.
130    pub fn with_tags(mut self, tags: impl Into<TagSet>) -> Self {
131        self.tags = tags.into();
132        self
133    }
134
135    /// Set the message of the service check
136    ///
137    /// This variant is specifically for use in builder-style APIs.
138    pub fn with_message(mut self, message: impl Into<Option<MetaString>>) -> Self {
139        self.message = match message.into() {
140            Some(message) => message,
141            None => MetaString::empty(),
142        };
143        self
144    }
145
146    /// Set the origin tags of the service check
147    ///
148    /// This variant is specifically for use in builder-style APIs.
149    pub fn with_origin_tags(mut self, origin_tags: impl Into<TagSet>) -> Self {
150        self.origin_tags = origin_tags.into();
151        self
152    }
153}
154
155impl Serialize for ServiceCheck {
156    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157    where
158        S: Serializer,
159    {
160        let mut map = serializer.serialize_map(None)?;
161        map.serialize_entry("check", &self.name)?;
162        if !self.hostname.is_empty() {
163            map.serialize_entry("host_name", &self.hostname)?;
164        }
165        if !self.message.is_empty() {
166            map.serialize_entry("message", &self.message)?;
167        }
168        map.serialize_entry("status", &self.status)?;
169
170        let tags = DeduplicatedTagsSerializable {
171            tags: &self.tags,
172            origin_tags: &self.origin_tags,
173        };
174        map.serialize_entry("tags", &tags)?;
175
176        if let Some(timestamp) = self.timestamp.as_ref() {
177            map.serialize_entry("timestamp", timestamp)?;
178        }
179        map.end()
180    }
181}
182
183impl CheckStatus {
184    /// Returns the integer representation of this status.
185    pub const fn as_u8(&self) -> u8 {
186        match self {
187            Self::Ok => 0,
188            Self::Warning => 1,
189            Self::Critical => 2,
190            Self::Unknown => 3,
191        }
192    }
193}
194
195impl Serialize for CheckStatus {
196    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197    where
198        S: Serializer,
199    {
200        serializer.serialize_u8(self.as_u8())
201    }
202}
203
204/// Error type for parsing CheckStatus.
205#[derive(Debug, Clone)]
206pub struct ParseCheckStatusError;
207
208impl std::fmt::Display for ParseCheckStatusError {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(f, "invalid check status")
211    }
212}
213
214impl std::error::Error for ParseCheckStatusError {}
215
216impl TryFrom<u8> for CheckStatus {
217    type Error = ParseCheckStatusError;
218
219    fn try_from(value: u8) -> Result<Self, Self::Error> {
220        match value {
221            0 => Ok(Self::Ok),
222            1 => Ok(Self::Warning),
223            2 => Ok(Self::Critical),
224            3 => Ok(Self::Unknown),
225            _ => Err(ParseCheckStatusError),
226        }
227    }
228}
229
230// Helper type to let us serialize deduplicated tags.
231struct DeduplicatedTagsSerializable<'a> {
232    tags: &'a TagSet,
233    origin_tags: &'a TagSet,
234}
235
236impl<'a> Serialize for DeduplicatedTagsSerializable<'a> {
237    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238    where
239        S: Serializer,
240    {
241        let chained_tags = self.tags.into_iter().chain(self.origin_tags);
242
243        let mut tags_deduplicator = ReusableDeduplicator::new();
244        let deduplicated_tags = tags_deduplicator.deduplicated(chained_tags);
245        serializer.collect_seq(deduplicated_tags)
246    }
247}