Java
io.e-volv:logs-jvm is the SDK for the JVM — Java 17 or later, Kotlin-friendly throughout. A Kotlin core with a Java-first static API ships as logs-jvm; the server integrations (Spring Boot, Logback, servlet, OkHttp, Kafka, JMS) ship as a second artifact, logs-jvm-server, and the core’s zero-Android-dependency design is what the Android client builds on. Both speak the same wire contract as the Node.js, Python and Go SDKs.
Install
Gradle (pin the current release where the snippets say 0.1.0):
implementation("io.e-volv:logs-jvm:0.1.0") // core: batching, gzip, redaction, retry, ids, trace context
implementation("io.e-volv:logs-jvm-server:0.1.0") // server integrations, provided-scope framework depsMaven:
<dependency> <groupId>io.e-volv</groupId> <artifactId>logs-jvm</artifactId> <version>0.1.0</version> </dependency>
Initialise
import io.evolve.logs.EvolveLogs;
import io.evolve.logs.Logs;
import io.evolve.logs.Options;
EvolveLogs.init(
Options.builder()
.key("evk_…") // project ingest key — a server key, from the environment
.url("https://api.e-volv.io/api/public/v1/logs")
.service("orders-api")
.environment("production")
.release("1.4.2")
.build());
Logs.info("order created", Map.of("orderId", "o_1", "total", 42.5));
Logs.error("payment failed", Map.of("orderId", "o_1"));
try {
charge();
} catch (Exception e) {
Logs.exception(e, Map.of("orderId", "o_1")); // exception.type/message/stack
}If key or url is empty, init returns a no-op client and warns once on stderr — the SDK never throws into your code. EvolveLogs.init(...) installs the shared default client that the Logs statics delegate to; EvolveLogs.client() returns it, with flush() and dropped() on it.
Spring Boot
Having logs-jvm-server on the classpath is all it takes — the auto-configuration initialises the SDK, attaches the Logback appender (when Logback is the SLF4J binding), registers the inbound servlet filter and exposes an EvolveTraceInterceptor bean for OkHttp clients the application builds:
evolve.logs.key=evk_…
evolve.logs.service=orders-api
evolve.logs.environment=production
evolve.logs.release=${GIT_SHA:unknown}Traces and spans
Trace context is ambient on a ThreadLocal for plain blocking code, and an explicit io.evolve.logs.Context object for reactive and virtual-thread callers (Logs.currentContext(), Logs.withContext(ctx, runnable)).
try (Span s = Logs.span("db.query", Map.of("table", "orders"))) {
// the span is the ambient context: logs and child spans join it
rows = db.query("…");
} // close() ends the span, recorded as "span db.query completed"Span.close() is idempotent and ends the span successfully. When the body throws, end it failed — call span.end(t) with the throwable, typically in the catch block; from Kotlin, client.withSpan("db.query") { … } does both automatically. A failed span is recorded at severity 17 with exception.type and exception.message. Logs.traceparent() returns the W3C 00-<traceId>-<spanId>-01 header of the ambient context (an empty string outside a trace), and Logs.runWithTraceparent(header, runnable) runs code with the next hop of a producer’s header — a malformed or absent one starts a fresh trace.
Integrations
Logback / SLF4J
io.evolve.logs.server.EvolveLogsAppender in logback.xml, or LogbackBridge.install() programmatically. Levels map to OTel severities (TRACE→1, DEBUG→5, INFO→9, WARN→13, ERROR→17), the MDC becomes attrs and a Throwable becomes exception.*.
Servlet
TraceContextFilter (javax.servlet, provided) continues an inbound traceparent and wraps the request in an http.request span. Exceptions end the span failed and propagate.
OkHttp
EvolveTraceInterceptor injects traceparent into outbound requests made inside a trace, never overwriting an existing header.
Kafka / JMS
EvolveKafkaHeaders / EvolveJmsHeaders carry the traceparent in record headers or message properties (provided-scope client APIs), so a consumer continues the producer’s trace.
Delivery
The shared numbers: flush at 200 events, every 2 seconds or at 512 KB, always gzipped. A 429 honours Retry-After, otherwise exponential backoff from 500 ms (doubling, capped at 10 s), up to three attempts; a 413 halves the batch and counts the excess half. Past twice the batch size the oldest events drop, visible on Logs.dropped(). Logs.flush() sends what is pending, and a JVM shutdown hook flushes automatically.
Package reference: packages/logs-jvm. Back to SDK overview.