Rust

Das Crate honk-me ist ein Async-Client auf Tokio und reqwest mit rustls, also ohne OpenSSL. Das Feature blocking ergänzt einen synchronen Client für Skripte und CLIs.

Paket
honk-me Veröffentlicht
Quellcode
github.com/honk-me/honk-rust · MIT-Lizenz
Voraussetzungen
Rust 1.85 oder neuer.

Installation

Für synchronen Code: cargo add honk-me --features blocking.

Terminal
cargo add honk-me
cargo add tokio --features macros,rt-multi-thread   # if you don't have a runtime yet

Mit einem Ingest-Schlüssel (honk_…) kann jeder an sein Projekt senden. Er gehört auf Server, in Jobs und in CI-Secrets, nie in einen Browser, eine Mobil- oder Desktop-App.

Ein Ereignis senden

Oder explizit: Honk::new("https://honk-me.app", key)?. Honk lässt sich günstig klonen: Erstelle eine Instanz und teile sie, dann bleiben die Verbindungen offen.

use honk_me::Honk;

#[tokio::main]
async fn main() -> honk_me::Result<()> {
    let honk = Honk::from_env()?; // HONK_URL, HONK_KEY (+ HONK_SOURCE, HONK_ENVIRONMENT, HONK_CHANNEL)
    honk.beep("Backup finished", "nightly pg_dump took 42 s").await?;
    Ok(())
}

Rezept: eine Kundenanfrage auf deinem Handy

Eine Gruppe pro Anfrage und dein eigener Idempotency-Key, damit ein wiederholter Webhook dein Handy nie zweimal vibrieren lässt.

use honk_me::{Category, Message, Priority, Severity};

let msg = Message::new(format!("{} asked for a quote: {}", req.name, req.summary))
    .title("New quote request")
    .severity(Severity::LOUD)
    .priority(Priority::High)
    .category(Category::Customers)
    .group_key(format!("requests/{}", req.id))
    .url(format!("https://shop.example.com/admin/requests/{}", req.id)); // https only, "Open link"
honk.send(&msg, format!("request-{}", req.id).as_str()).await?;

Die Honk-Skala und Vorfälle

Jeder Helfer gibt ein PendingSend zurück: Häng weitere Felder an und warte es dann mit .await ab.

use honk_me::Priority;

honk.loud("Disk 91% full", "db-1 /var is at 91%")
    .group_key("disk/db-1/var")
    .priority(Priority::High)
    .metadata("used_percent", 91)
    .await?;

honk.problem("db/backup", "Backup failed", "pg_dump exited with 1").await?;
honk.recovery("db/backup", "Backup OK", "pg_dump finished").await?;

Der blockierende Client

Er führt den Async-Client auf einer privaten Single-Thread-Runtime aus, mit denselben Retries. Ruf ihn nicht aus einer Async-Runtime heraus auf.

// Cargo.toml: honk-me = { version = "0.2", features = ["blocking"] }
use honk_me::blocking::Honk;

let honk = Honk::from_env()?;
honk.beep("Backup finished", "nightly pg_dump took 42 s").send()?;
honk.send(&honk_me::Message::new("Imported 1 204 rows"), None)?;

Fehler

Jede Variante von Error trägt ein Failure mit dem Status, dem Code, jedem ungültigen Feld und retry_after.

use honk_me::Error;

match honk.send(&msg, "order-1042-failed").await {
    Ok(_) => {}
    Err(Error::Validation(f)) => tracing::error!(?f.fields, "a bug: don't retry"),
    Err(e) if e.is_retryable() => queue.retry_later(e.idempotency_key(), e.retry_after()),
    Err(e) => return Err(e.into()),
}

Retries ohne Doppelversand

  • Jedes Senden hat einen Idempotency-Key: deinen oder eine neue UUIDv7. Jeder Retry nutzt denselben Schlüssel, und innerhalb von 24 Stunden beantwortet Honk eine erneut gesendete Anfrage mit der ursprünglichen ID und duplicate: true. Geht eine Antwort verloren, entsteht also nie eine zweite Nachricht.
  • Wiederholt werden nur Netzwerkfehler, Timeouts, 429 und 5xx, mit exponentiellem Backoff und vollem Jitter, nie früher als das Retry-After des Servers. Andere 4xx-Antworten werden nie wiederholt: Korrigiere stattdessen die Anfrage.
  • Jeder Versuch bricht nach 5 Sekunden ab, und nach insgesamt 30 Sekunden ist Schluss. Wäre die nötige Wartezeit länger, etwa bis ein Tageskontingent um Mitternacht zurückgesetzt wird, schlägt der Aufruf sofort fehl und sagt dir, wann du es erneut versuchen kannst.
  • Felder werden vor dem Senden geprüft, und alle ungültigen Felder werden auf einmal gemeldet.