Skip to content

Commit aca1ad5

Browse files
feat(crons): Add monitor check-in types to sentry-types (#577)
Adds types that define monitor check-in envelopes. Includes a new EnvelopeItemType and implementations of `From` for `EnvelopeItem` and `Envelope`
1 parent bbba0e9 commit aca1ad5

File tree

4 files changed

+177
-4
lines changed

4 files changed

+177
-4
lines changed

sentry-types/src/protocol/envelope.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use serde::Deserialize;
44
use thiserror::Error;
55
use uuid::Uuid;
66

7-
use super::{
8-
attachment::AttachmentType,
9-
v7::{Attachment, Event, SampleProfile, SessionAggregates, SessionUpdate, Transaction},
7+
use super::v7::{
8+
Attachment, AttachmentType, Event, MonitorCheckIn, SampleProfile, SessionAggregates,
9+
SessionUpdate, Transaction,
1010
};
1111

1212
/// Raised if a envelope cannot be parsed from a given input.
@@ -61,6 +61,9 @@ enum EnvelopeItemType {
6161
/// A Profile Item Type
6262
#[serde(rename = "profile")]
6363
Profile,
64+
/// A Monitor Check In Item Type
65+
#[serde(rename = "check_in")]
66+
MonitorCheckIn,
6467
}
6568

6669
/// An Envelope Item Header.
@@ -109,6 +112,8 @@ pub enum EnvelopeItem {
109112
Attachment(Attachment),
110113
/// A Profile Item.
111114
Profile(SampleProfile),
115+
/// A MonitorCheckIn item.
116+
MonitorCheckIn(MonitorCheckIn),
112117
/// This is a sentinel item used to `filter` raw envelopes.
113118
Raw,
114119
// TODO:
@@ -151,6 +156,12 @@ impl From<SampleProfile> for EnvelopeItem {
151156
}
152157
}
153158

159+
impl From<MonitorCheckIn> for EnvelopeItem {
160+
fn from(check_in: MonitorCheckIn) -> Self {
161+
EnvelopeItem::MonitorCheckIn(check_in)
162+
}
163+
}
164+
154165
/// An Iterator over the items of an Envelope.
155166
#[derive(Clone)]
156167
pub struct EnvelopeItemIter<'s> {
@@ -341,6 +352,9 @@ impl Envelope {
341352
continue;
342353
}
343354
EnvelopeItem::Profile(profile) => serde_json::to_writer(&mut item_buf, profile)?,
355+
EnvelopeItem::MonitorCheckIn(check_in) => {
356+
serde_json::to_writer(&mut item_buf, check_in)?
357+
}
344358
EnvelopeItem::Raw => {
345359
continue;
346360
}
@@ -352,6 +366,7 @@ impl Envelope {
352366
EnvelopeItem::Transaction(_) => "transaction",
353367
EnvelopeItem::Attachment(_) | EnvelopeItem::Raw => unreachable!(),
354368
EnvelopeItem::Profile(_) => "profile",
369+
EnvelopeItem::MonitorCheckIn(_) => "check_in",
355370
};
356371
writeln!(
357372
writer,
@@ -493,6 +508,9 @@ impl Envelope {
493508
ty: header.attachment_type,
494509
})),
495510
EnvelopeItemType::Profile => serde_json::from_slice(payload).map(EnvelopeItem::Profile),
511+
EnvelopeItemType::MonitorCheckIn => {
512+
serde_json::from_slice(payload).map(EnvelopeItem::MonitorCheckIn)
513+
}
496514
}
497515
.map_err(EnvelopeError::InvalidItemPayload)?;
498516

@@ -523,6 +541,14 @@ impl From<Transaction<'static>> for Envelope {
523541
}
524542
}
525543

544+
impl From<MonitorCheckIn> for Envelope {
545+
fn from(check_in: MonitorCheckIn) -> Self {
546+
let mut envelope = Self::default();
547+
envelope.add_item(check_in);
548+
envelope
549+
}
550+
}
551+
526552
#[cfg(test)]
527553
mod test {
528554
use std::str::FromStr;
@@ -532,7 +558,10 @@ mod test {
532558
use time::OffsetDateTime;
533559

534560
use super::*;
535-
use crate::protocol::v7::{Level, SessionAttributes, SessionStatus, Span};
561+
use crate::protocol::v7::{
562+
Level, MonitorCheckInStatus, MonitorConfig, MonitorSchedule, SessionAttributes,
563+
SessionStatus, Span,
564+
};
536565

537566
fn to_str(envelope: Envelope) -> String {
538567
let mut vec = Vec::new();
@@ -649,6 +678,35 @@ mod test {
649678
)
650679
}
651680

681+
#[test]
682+
fn test_monitor_checkin() {
683+
let check_in_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
684+
685+
let check_in = MonitorCheckIn {
686+
check_in_id,
687+
monitor_slug: "my-monitor".into(),
688+
status: MonitorCheckInStatus::Ok,
689+
duration: Some(123.4),
690+
environment: Some("production".into()),
691+
monitor_config: Some(MonitorConfig {
692+
schedule: MonitorSchedule::Crontab {
693+
value: "12 0 * * *".into(),
694+
},
695+
checkin_margin: Some(5),
696+
max_runtime: Some(30),
697+
timezone: Some("UTC".into()),
698+
}),
699+
};
700+
let envelope: Envelope = check_in.into();
701+
assert_eq!(
702+
to_str(envelope),
703+
r#"{}
704+
{"type":"check_in","length":259}
705+
{"check_in_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","monitor_slug":"my-monitor","status":"ok","environment":"production","duration":123.4,"monitor_config":{"schedule":{"type":"crontab","value":"12 0 * * *"},"checkin_margin":5,"max_runtime":30,"timezone":"UTC"}}
706+
"#
707+
)
708+
}
709+
652710
#[test]
653711
fn test_event_with_attachment() {
654712
let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();

sentry-types/src/protocol/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ pub use v7 as latest;
1515

1616
mod attachment;
1717
mod envelope;
18+
mod monitor;
1819
mod profile;
1920
mod session;

sentry-types/src/protocol/monitor.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use serde::{Deserialize, Serialize, Serializer};
2+
use uuid::Uuid;
3+
4+
/// Represents the status of the monitor check-in
5+
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
6+
#[serde(rename_all = "snake_case")]
7+
pub enum MonitorCheckInStatus {
8+
/// Check-in had no issues during execution.
9+
Ok,
10+
/// Check-in failed or otherwise had some issues.
11+
Error,
12+
/// Check-in is expectred to complete.
13+
InProgress,
14+
/// Monitor did not check in on time.
15+
Missed,
16+
/// No status was passed.
17+
#[serde(other)]
18+
Unknown,
19+
}
20+
21+
/// Configuration object of the monitor schedule.
22+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
23+
#[serde(rename_all = "snake_case")]
24+
#[serde(tag = "type")]
25+
pub enum MonitorSchedule {
26+
/// A Crontab schedule allows you to use a standard UNIX crontab style schedule string to
27+
/// configure when a monitor check-in will be expected on Sentry.
28+
Crontab {
29+
/// The crontab syntax string defining the schedule.
30+
value: String,
31+
},
32+
/// A Interval schedule allows you to configure a periodic check-in, that will occur at an
33+
/// interval after the most recent check-in.
34+
Interval {
35+
/// The interval value.
36+
value: u64,
37+
/// The interval unit of the value.
38+
unit: MonitorIntervalUnit,
39+
},
40+
}
41+
42+
/// The unit for the interval schedule type
43+
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
44+
#[serde(rename_all = "snake_case")]
45+
pub enum MonitorIntervalUnit {
46+
/// Year Interval.
47+
Year,
48+
/// Month Interval.
49+
Month,
50+
/// Week Interval.
51+
Week,
52+
/// Day Interval.
53+
Day,
54+
/// Hour Interval.
55+
Hour,
56+
/// Minute Interval.
57+
Minute,
58+
}
59+
60+
/// The monitor configuration playload for upserting monitors during check-in
61+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
62+
pub struct MonitorConfig {
63+
/// The monitor schedule configuration.
64+
pub schedule: MonitorSchedule,
65+
66+
/// How long (in minutes) after the expected check-in time will we wait until we consider the
67+
/// check-in to have been missed.
68+
#[serde(default, skip_serializing_if = "Option::is_none")]
69+
pub checkin_margin: Option<u64>,
70+
71+
/// How long (in minutes) is the check-in allowed to run for in
72+
/// [`MonitorCheckInStatus::InProgress`] before it is considered failed.in_rogress
73+
#[serde(default, skip_serializing_if = "Option::is_none")]
74+
pub max_runtime: Option<u64>,
75+
76+
/// tz database style timezone string
77+
#[serde(default, skip_serializing_if = "Option::is_none")]
78+
pub timezone: Option<String>,
79+
}
80+
81+
fn serialize_id<S: Serializer>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
82+
serializer.serialize_some(&uuid.as_simple())
83+
}
84+
85+
/// The monitor check-in payload.
86+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
87+
pub struct MonitorCheckIn {
88+
/// Unique identifier of this check-in.
89+
#[serde(serialize_with = "serialize_id")]
90+
pub check_in_id: Uuid,
91+
92+
/// Identifier of the monitor for this check-in.
93+
pub monitor_slug: String,
94+
95+
/// Status of this check-in. Defaults to `"unknown"`.
96+
pub status: MonitorCheckInStatus,
97+
98+
/// The environment to associate the check-in with.
99+
#[serde(default, skip_serializing_if = "Option::is_none")]
100+
pub environment: Option<String>,
101+
102+
/// Duration of this check-in since it has started in seconds.
103+
#[serde(default, skip_serializing_if = "Option::is_none")]
104+
pub duration: Option<f64>,
105+
106+
/// Monitor configuration to support upserts. When provided a monitor will be created on Sentry
107+
/// upon receiving the first check-in.
108+
///
109+
/// If the monitor already exists the configuration will be updated with the values provided in
110+
/// this object.
111+
#[serde(default, skip_serializing_if = "Option::is_none")]
112+
pub monitor_config: Option<MonitorConfig>,
113+
}

sentry-types/src/protocol/v7.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::utils::{ts_rfc3339_opt, ts_seconds_float};
2626

2727
pub use super::attachment::*;
2828
pub use super::envelope::*;
29+
pub use super::monitor::*;
2930
pub use super::profile::*;
3031
pub use super::session::*;
3132

0 commit comments

Comments
 (0)