saluki_core/data_model/event/service_check/
mod.rs

1//! Service checks.
2
3use saluki_common::iter::ReusableDeduplicator;
4use saluki_context::tags::SharedTagSet;
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: SharedTagSet,
36    origin_tags: SharedTagSet,
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) -> &SharedTagSet {
77        &self.tags
78    }
79
80    /// Returns the origin tags associated with the check.
81    pub fn origin_tags(&self) -> &SharedTagSet {
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: SharedTagSet::default(),
94            origin_tags: SharedTagSet::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 tags of the service check
120    ///
121    /// This variant is specifically for use in builder-style APIs.
122    pub fn with_tags(mut self, tags: impl Into<SharedTagSet>) -> Self {
123        self.tags = tags.into();
124        self
125    }
126
127    /// Set the message of the service check
128    ///
129    /// This variant is specifically for use in builder-style APIs.
130    pub fn with_message(mut self, message: impl Into<Option<MetaString>>) -> Self {
131        self.message = match message.into() {
132            Some(message) => message,
133            None => MetaString::empty(),
134        };
135        self
136    }
137
138    /// Set the origin tags of the service check
139    ///
140    /// This variant is specifically for use in builder-style APIs.
141    pub fn with_origin_tags(mut self, origin_tags: SharedTagSet) -> Self {
142        self.origin_tags = origin_tags;
143        self
144    }
145}
146
147impl Serialize for ServiceCheck {
148    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149    where
150        S: Serializer,
151    {
152        let mut map = serializer.serialize_map(None)?;
153        map.serialize_entry("check", &self.name)?;
154        if !self.hostname.is_empty() {
155            map.serialize_entry("host_name", &self.hostname)?;
156        }
157        if !self.message.is_empty() {
158            map.serialize_entry("message", &self.message)?;
159        }
160        map.serialize_entry("status", &self.status)?;
161
162        let tags = DeduplicatedTagsSerializable {
163            tags: &self.tags,
164            origin_tags: &self.origin_tags,
165        };
166        map.serialize_entry("tags", &tags)?;
167
168        if let Some(timestamp) = self.timestamp.as_ref() {
169            map.serialize_entry("timestamp", timestamp)?;
170        }
171        map.end()
172    }
173}
174
175impl CheckStatus {
176    /// Returns the integer representation of this status.
177    pub const fn as_u8(&self) -> u8 {
178        match self {
179            Self::Ok => 0,
180            Self::Warning => 1,
181            Self::Critical => 2,
182            Self::Unknown => 3,
183        }
184    }
185}
186
187impl Serialize for CheckStatus {
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where
190        S: Serializer,
191    {
192        serializer.serialize_u8(self.as_u8())
193    }
194}
195
196/// Error type for parsing CheckStatus.
197#[derive(Debug, Clone)]
198pub struct ParseCheckStatusError;
199
200impl std::fmt::Display for ParseCheckStatusError {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(f, "invalid check status")
203    }
204}
205
206impl std::error::Error for ParseCheckStatusError {}
207
208impl TryFrom<u8> for CheckStatus {
209    type Error = ParseCheckStatusError;
210
211    fn try_from(value: u8) -> Result<Self, Self::Error> {
212        match value {
213            0 => Ok(Self::Ok),
214            1 => Ok(Self::Warning),
215            2 => Ok(Self::Critical),
216            3 => Ok(Self::Unknown),
217            _ => Err(ParseCheckStatusError),
218        }
219    }
220}
221
222// Helper type to let us serialize deduplicated tags.
223struct DeduplicatedTagsSerializable<'a> {
224    tags: &'a SharedTagSet,
225    origin_tags: &'a SharedTagSet,
226}
227
228impl<'a> Serialize for DeduplicatedTagsSerializable<'a> {
229    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230    where
231        S: Serializer,
232    {
233        let chained_tags = self.tags.into_iter().chain(self.origin_tags);
234
235        let mut tags_deduplicator = ReusableDeduplicator::new();
236        let deduplicated_tags = tags_deduplicator.deduplicated(chained_tags);
237        serializer.collect_seq(deduplicated_tags)
238    }
239}