Go
Le paquet honk est un client compatible avec context, construit uniquement sur la bibliothèque standard et sûr en accès concurrent. Le même module fournit la CLI honk-me pour les scripts, cron et la CI.
- Paquet
-
github.com/honk-me/honk-goPublié - Code source
- github.com/honk-me/honk-go · Licence MIT
- Prérequis
- Go 1.22 ou plus récent.
Installation
La CLI est aussi jointe à chaque release GitHub sous forme de binaires précompilés.
go get github.com/honk-me/honk-go # library (Go 1.22+)
go install github.com/honk-me/honk-go/cmd/honk-me@latest # CLI Une clé d’ingestion (honk_…) permet à quiconque la détient d’envoyer des messages dans son projet. Gardez-la sur vos serveurs, dans vos tâches et dans les secrets de votre CI, jamais dans un navigateur ni dans une app mobile ou de bureau.
Envoyer un événement
Ou explicitement : honk.New(honk.Options{URL: "https://honk-me.app", Key: key}). Une erreur nil signifie que Honk a enregistré le message, pas qu’un push a été distribué.
import honk "github.com/honk-me/honk-go" // package honk
c, err := honk.FromEnv() // HONK_URL, HONK_KEY (+ optional HONK_SOURCE, HONK_ENVIRONMENT, HONK_CHANNEL)
if err != nil {
log.Fatal(err)
}
_, err = c.Beep(ctx, "Backup finished", "nightly pg_dump took 42 s") Recette : une demande client sur votre téléphone
Enregistrez d’abord la demande, puis notifiez en dehors du traitement de la requête. Un groupe et une clé d’idempotence par demande.
var notifier, _ = honk.FromEnv() // create once, reuse (keep-alive)
func onCustomerRequest(r CustomerRequest) {
// ...save the request first, then notify off the request path:
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, err := notifier.Send(ctx, honk.Message{
Title: truncate("New request: "+r.Subject, 150),
Message: truncate(fmt.Sprintf("%s (%s) asked: %s", r.Name, r.Company, r.Body), 2000),
Priority: honk.PriorityHigh, // push right away
Category: honk.CategoryCustomers,
Channel: "requests",
GroupKey: fmt.Sprintf("requests/%d", r.ID), // one group per request
URL: fmt.Sprintf("https://shop.example.com/admin/requests/%d", r.ID),
Metadata: map[string]any{"request_id": strconv.Itoa(r.ID)},
}, honk.WithIdempotencyKey(fmt.Sprintf("request-%d", r.ID)))
if err != nil {
log.Printf("honk: %v", err) // never fail the customer's request
}
}()
}
func truncate(s string, n int) string { // by runes, never splits UTF-8
if r := []rune(s); len(r) > n {
return string(r[:n-1]) + "…"
}
return s
} L’échelle Honk et les incidents
Les helpers acceptent des options comme WithGroupKey, WithChannel et WithIdempotencyKey.
c.Loud(ctx, "Disk 91%", "/var on app-01", honk.WithGroupKey("disk/app-01/var"))
c.Light(ctx, "Deploy started", "v4.2.0", honk.WithChannel("deploys"))
c.Problem(ctx, "db/backup", "Backup failed", "pg_dump exited with 1") // a long honk by default
c.Recovery(ctx, "db/backup", "Backup OK", "pg_dump finished in 41 s") // a beep by default Erreurs
Les erreurs sont des *honk.Error et se comparent aux sentinelles avec errors.Is. Retryable() indique s’il faut réessayer avec la même clé.
_, err := c.Long(ctx, "Payment failed", "Stripe declined order 1042", honk.WithGroupKey("payments/stripe"))
var he *honk.Error
switch {
case err == nil:
case errors.Is(err, honk.ErrValidation):
log.Printf("bug: %v", err) // he.Fields says what to fix
case errors.As(err, &he) && he.Retryable():
requeue(he.IdempotencyKey, he.RetryAfter)
default:
log.Print(err)
} Des nouvelles tentatives sans doublon
- Chaque envoi porte une
Idempotency-Key: la vôtre, ou un UUIDv7 généré pour l’occasion. La même clé est réutilisée à chaque nouvelle tentative, et pendant 24 heures Honk répond à une requête rejouée avec l’identifiant d’origine etduplicate: true: une réponse perdue ne crée jamais de second message. - Une nouvelle tentative n’a lieu qu’en cas d’erreur réseau, de délai dépassé, de
429ou de5xx, avec un backoff exponentiel et un jitter complet, jamais avant leRetry-Afterdu serveur. Les autres réponses4xxne donnent lieu à aucune nouvelle tentative : corrigez plutôt la requête. - Chaque tentative expire au bout de 5 secondes, et tout s’arrête au bout de 30 secondes au total. Une attente qui dépasserait cette échéance, comme un quota quotidien remis à zéro à minuit, échoue immédiatement et indique quand réessayer.
- Les champs sont vérifiés localement avant l’envoi, et tous les champs invalides sont signalés en une seule fois.