Skip to content

Commit f1e0156

Browse files
committed
Set a run ID for all --time'ed commands
`?rid=...` is now included for both `/start` and `/` (ping) if `--time` is specified. The identifier is a randomly generated UUID and cannot be configured at this time. A run ID is not generated if `--time` is not specified (i.e. if there's no `/start` ping sent). Fixes #5 Tested: ``` $ for i in 5 4 3; do cargo run -- --uuid ... --time -- sleep $i & sleep 1 done ``` This actually demonstrated the start+complete events don't consistently get delivered, as I saw several instances where one or two completions never showed up. `--verbose` confirms the expected requests were sent, so it seems to be a race in Healthcheck's handling of rapidly-arriving overlapping events. I also started getting rate-limited while testing, which suggests the earlier observations of missing pings weren't simply rate limiting.
1 parent a15d16f commit f1e0156

File tree

3 files changed

+171
-18
lines changed

3 files changed

+171
-18
lines changed

Cargo.lock

Lines changed: 131 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ include = [
2222
static_ssl = ['openssl/vendored']
2323

2424
[dependencies]
25-
clap = { version = "~3.0", default_features = false, features = ["std", "derive", "env", "cargo"] }
25+
clap = { version = "~3.0", default-features = false, features = ["std", "derive", "env", "cargo"] }
2626
clap_derive = "~3.0"
2727
hostname = "0.3"
2828
subprocess = "0.2"
2929
ureq = "2.0"
30+
uuid = { version = "1.17", features = ["v4"] }
3031

3132
[dependencies.openssl]
3233
optional = true

src/main.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::ffi::{OsStr, OsString};
99
use std::time::{Duration, Instant};
1010
use subprocess::{Exec, Redirection, ExitStatus, CaptureData, PopenConfig};
1111
use ureq::{Agent, AgentBuilder, Error, Response};
12+
use uuid::Uuid;
1213

1314
static MAX_BYTES_TO_POST: usize = 10000; // not 10KB, https://healthchecks.io/docs/attaching_logs/
1415
static MAX_STRING_TO_LOG: usize = 1000;
@@ -96,18 +97,25 @@ impl HCAgent {
9697
HCAgent { agent, verbose: cli.verbose, url_prefix: cli.url_prefix() }
9798
}
9899

99-
/// Pings the Healthchecks server to notify that the task denoted by the UUID is starting
100-
fn notify_start(&self) -> Result<Response, Error> {
101-
let req = self.agent.get(&format!("{}/start", self.url_prefix));
100+
/// Pings the Healthchecks server to notify that the task denoted by the URL prefix is starting
101+
/// A run_id UUID is used to associate this event with its completion notification
102+
fn notify_start(&self, run_id: Uuid) -> Result<Response, Error> {
103+
let url = format!("{}/start?rid={}", self.url_prefix, run_id);
104+
let req = self.agent.get(&url);
102105
if self.verbose { eprintln!("Sending request: {:?}", req); }
103106
req.call()
104107
}
105108

106109
/// Pings the Healthchecks server to notify that the task denoted by the URL prefix is done.
110+
/// A run_id UUID is used to associated this event with its start notification, if one was sent
107111
/// If code is non-zero, the task will be considered failed. If code is None the task will be logged
108112
/// but not update the check.
109-
fn notify_complete(&self, code: Option<u8>, output: &str) -> Result<Response, Error> {
110-
let req = self.agent.post(&format!("{}/{}", self.url_prefix, code.map(|x| x.to_string()).unwrap_or_else(|| "log".to_string())));
113+
fn notify_complete(&self, run_id: Option<Uuid>, code: Option<u8>, output: &str) -> Result<Response, Error> {
114+
let mut url = format!("{}/{}", self.url_prefix, code.map(|x| x.to_string()).unwrap_or_else(|| "log".to_string()));
115+
if let Some(run_id) = run_id {
116+
url = format!("{}?rid={}", url, run_id);
117+
}
118+
let req = self.agent.post(&url);
111119
if self.verbose { eprintln!("Sending request: {:?}", req); }
112120
if output.is_empty() {
113121
req.call()
@@ -192,8 +200,11 @@ impl Cli {
192200
}
193201

194202
fn run(cli: Cli, agent: HCAgent) -> Result<Response, Error> {
203+
let mut maybe_run_id = None; // Don't bother reporting a run ID unless we're sending a start ping
195204
if cli.time {
196-
if let Err(e) = agent.notify_start() {
205+
let run_id = Uuid::new_v4();
206+
maybe_run_id = Some(run_id);
207+
if let Err(e) = agent.notify_start(run_id) {
197208
eprintln!("Failed to send start request: {:?}", e);
198209
}
199210
}
@@ -220,7 +231,7 @@ fn run(cli: Cli, agent: HCAgent) -> Result<Response, Error> {
220231
// Trim replacement chars added by from_utf8_lossy since they are multi-byte and can actually
221232
// increase the length of the string.
222233
let code = if cli.log { None } else { Some(code) };
223-
agent.notify_complete(code, output.trim_start_matches(|c| c=='�'))
234+
agent.notify_complete(maybe_run_id, code, output.trim_start_matches(|c| c=='�'))
224235
}
225236

226237
fn main() {
@@ -276,16 +287,23 @@ mod tests {
276287
let suc_m = mockito::mock("POST", "/ping/0").match_body("foo bar").with_status(200).create();
277288
let fail_m = mockito::mock("POST", "/ping/10").match_body("bar baz").with_status(200).create();
278289
let log_m = mockito::mock("POST", "/ping/log").match_body("bang boom").with_status(200).create();
290+
let runid_m = mockito::mock("POST", "/ping/0")
291+
.match_query(mockito::Matcher::Regex("rid=.*".into()))
292+
.match_body("run id")
293+
.with_status(200).create();
279294
let agent = HCAgent{ agent: Agent::new(), verbose: false, url_prefix: format!("{}/{}", mockito::server_url(), "ping") };
280-
let suc_response = agent.notify_complete(Some(0), "foo bar");
281-
let fail_response = agent.notify_complete(Some(10), "bar baz");
282-
let log_response = agent.notify_complete(None, "bang boom");
295+
let suc_response = agent.notify_complete(None, Some(0), "foo bar");
296+
let fail_response = agent.notify_complete(None, Some(10), "bar baz");
297+
let log_response = agent.notify_complete(None, None, "bang boom");
298+
let runid_response = agent.notify_complete(Some(Uuid::from_u128(1234)), Some(0), "run id");
283299
suc_m.assert();
284300
fail_m.assert();
285301
log_m.assert();
302+
runid_m.assert();
286303
suc_response.unwrap();
287304
fail_response.unwrap();
288305
log_response.unwrap();
306+
runid_response.unwrap();
289307
}
290308

291309
mod integ {
@@ -335,11 +353,13 @@ mod tests {
335353

336354
#[test]
337355
fn start() {
338-
let m = mockito::mock("GET", "/start/start").with_status(200).create();
356+
let m = mockito::mock("GET", "/start/start")
357+
.match_query(mockito::Matcher::Regex("rid=.*".into()))
358+
.with_status(200).create();
339359

340360
let cli = fake_cli("start", &[""]);
341361

342-
let response = HCAgent::create(&cli).notify_start();
362+
let response = HCAgent::create(&cli).notify_start(Uuid::from_u128(1234));
343363
m.assert();
344364
response.unwrap();
345365
}
@@ -388,9 +408,13 @@ mod tests {
388408

389409
#[test]
390410
fn timed() {
391-
let start_m = mockito::mock("GET", "/timed/start").with_status(200).create();
411+
let start_m = mockito::mock("GET", "/timed/start")
412+
.match_query(mockito::Matcher::Regex("rid=.*".into()))
413+
.with_status(200).create();
392414
let done_m = mockito::mock("POST", "/timed/0")
393-
.match_body("hello\n").with_status(200).create();
415+
.match_query(mockito::Matcher::Regex("rid=.*".into()))
416+
.match_body("hello\n")
417+
.with_status(200).create();
394418

395419
let mut cli = fake_cli("timed", &["echo", "hello"]);
396420
cli.time = true;

0 commit comments

Comments
 (0)