Kotlin and Java
One library for both languages: a suspend API for Kotlin, and sendBlocking, sendAsync and builders for Java, on java.net.http.HttpClient. Its only dependencies are the Kotlin standard library and coroutines.
- Package
-
app.honk-me:sdkComing soon - Source
- github.com/honk-me/honk-kotlin · MIT License
- Requirements
- JDK 17 or later.
The Maven Central release waits for the Sonatype namespace. Until then, build it from the repository (./gradlew publishToMavenLocal) or use cURL.
Install
Everything is in the package app.honkme.
// build.gradle.kts
dependencies { implementation("app.honk-me:sdk:0.1.0") } <!-- pom.xml -->
<dependency>
<groupId>app.honk-me</groupId>
<artifactId>sdk</artifactId>
<version>0.1.0</version>
</dependency> Anyone with an ingestion key (honk_…) can post to its project. Keep it on servers, in jobs and in CI secrets, never in a browser, mobile or desktop app.
Send an event
Create one Honk per application and close it on shutdown.
import app.honkme.Honk
val honk = Honk.fromEnvironment() // HONK_URL, HONK_KEY (+ HONK_SOURCE, HONK_ENVIRONMENT, HONK_CHANNEL)
honk.beep("Backup finished", "nightly pg_dump took 42 s") // suspend import app.honkme.Honk;
import app.honkme.Message;
Honk honk = Honk.fromEnvironment();
honk.sendBlocking(Message.beep("Backup finished", "nightly pg_dump took 42 s").build()); Recipe: a customer request with Spring Boot or Ktor
The library has no Spring dependency; this is plain code in a Spring service. Send after the transaction commits, so the customer never waits for the network.
@Configuration
class HonkConfig {
@Bean(destroyMethod = "close")
Honk honk(@Value("${honk.url}") String url, @Value("${honk.key}") String key) {
return Honk.builder().url(url).key(key).build(); // one client, keep-alive
}
}
@Service
class CustomerRequestNotifier {
private static final Logger log = LoggerFactory.getLogger(CustomerRequestNotifier.class);
private final Honk honk;
CustomerRequestNotifier(Honk honk) { this.honk = honk; }
// After the request is saved: never block the customer's request on the network.
@TransactionalEventListener
void on(CustomerRequestCreated event) {
CustomerRequest r = event.request();
Message message = Message.light("New request: " + r.subject(), r.name() + " (" + r.company() + ") asked: " + r.body())
.priority("high") // push right away
.category(Category.CUSTOMERS)
.channel("requests")
.groupKey("requests/" + r.id()) // one group per request
.url("https://shop.example.com/admin/requests/" + r.id()) // https only
.meta("request_id", String.valueOf(r.id()))
.build();
honk.sendAsync(message, "request-" + r.id()) // same request, same key
.exceptionally(e -> { log.warn("honk: {}", e.getMessage()); return null; });
}
} scope.launch {
honk.light("New request: ${r.subject}", "${r.name} (${r.company}) asked: ${r.body.take(2000)}") {
priority(Priority.HIGH)
category(Category.CUSTOMERS)
groupKey("requests/${r.id}")
url("https://shop.example.com/admin/requests/${r.id}")
idempotencyKey("request-${r.id}")
}
} The Honk scale and incidents
In Java, Message.longHonk(…) stands in for long, which is a keyword.
honk.loud("Disk 91%", "/var on app-01") { groupKey("disk/app-01/var") }
honk.problem("db/backup", "Backup failed", "pg_dump exited with 1") // a long honk by default
honk.recovery("db/backup", "Backup OK", "pg_dump finished in 41 s") // a beep by default
honk.send(Message("Imported 1 204 rows", severity = Severity.BEEP, channel = "imports")) Errors
All exceptions are unchecked and extend HonkException, with isRetryable and the idempotency key to retry with.
try {
honk.sendBlocking(Message.longHonk("Payment failed", "Stripe declined order 1042").groupKey("payments/stripe").build());
} catch (HonkValidationException e) {
log.error("bug: {}", e.getFields());
} catch (HonkException e) {
if (e.isRetryable()) queue.retryLater(e.getIdempotencyKey(), e.getRetryAfter());
else throw e;
} Retries that never send twice
- Every send carries an
Idempotency-Key: yours, or a fresh UUIDv7. The same key is reused on every retry, and within 24 hours Honk answers a replay with the original id andduplicate: true, so a lost response never turns into a second message. - Only network errors, timeouts,
429and5xxare retried, with exponential backoff and full jitter, never sooner than the server’sRetry-After. Other4xxresponses are never retried; fix the request instead. - Each attempt times out after 5 seconds and everything stops after 30. A wait that would cross that deadline, like a daily quota that resets at midnight, fails right away and tells you when to retry.
- Fields are validated before sending, and all invalid fields are reported together.