Releases: merlimat/slog
Release list
v0.10.0
What's Changed
A performance and correctness pass over the logging hot paths β every change validated with JMH benchmarks and regression tests.
Fixes
- Fix stale-level races in the Log4j2 cached level scheme: the generation and effective level are packed into a single word, and generation bumps are atomic (#10)
- Route through
AsyncLoggerin full-async mode (AsyncLoggerContextSelector) instead of callingLoggerConfigdirectly, which silently ran appender I/O on the application thread (#12) - Pool log events only when Log4j2 thread-locals are enabled: with thread-locals disabled (the web-app default),
AsyncLoggerConfigenqueues events by reference and a reused event could cross-contaminate records (#13) - Guard the pooled event against reentrant logging (e.g. an attr supplier that logs), which could corrupt the in-flight outer event (#16)
Performance
- Measure
timed()durations with a monotonic clock: allocation-free, immune to wall-clock adjustments, ~20% faster (#11) - Save and restore only the touched MDC keys in the SLF4J handler instead of copying the whole MDC map on every structured event (#14)
- Flatten logger context into a single array at logger-build time: +14% throughput and β60% allocation on context-bearing events (#15)
- Skip per-thread event pooling on virtual threads, where pool memory would scale with the number of live threads (#17)
- Store event attrs in a single interleaved array: 80 β 64 B/op on the fluent path (#18)
- Clear the pooled event right after emit, so the last event's throwable and attr values are not pinned on idle threads (#19)
Full Changelog: v0.9.9...v0.10.0
v0.9.9
v0.9.8
Bug Fixes
- Fix MDC propagation under the Log4j2 backend.
Log4j2Logger.buildContextDatawas building the event'sContextDatafrom only slog's own context attrs and event attrs, ignoring the caller thread'sThreadContext. Layouts using%X{key}and appenders that readevent.getContextData().getValue(key)saw nothing β even when the thread had MDC entries set when the log call ran. Fixed by mergingThreadContext.getImmutableContext()into the event'sStringMapbefore slog's own attrs (which override on key collision). Fast path is preserved β when MDC is empty the only added work is oneThreadContext.isEmpty()check, no allocation. (#6)
New Features
-
Add
Event.ctx(Logger other)so a static or shared logger can inherit another logger's context for a single event, avoiding the per-call allocation of a child logger. Ordering mirrorsLoggerBuilder.ctx: inherited attrs first, then this logger's own context, then per-event attrs; multiplectx()calls append in invocation order. Allocation-free in the common static-logger case viaAttrChain.withPrefixshort-circuits. (#5)static final Logger log = Logger.get(MyService.class); void handle(Logger requestLog, String msgId) { log.info() .ctx(requestLog) // adopt request-scoped context .attr("msgId", msgId) .log("processed"); }
Full Changelog: v0.9.7...v0.9.8
v0.9.7
Bug Fixes
- Fix NPE in
ContextMapLookupwhen a${ctx:key}lookup resolves against a slog event with no context attributes βLog4j2Logger.buildContextDatareturnednullfor the no-context case, which causedContextMapLookup.lookup()to NPE onevent.getContextData().getValue(key). This showed up underRoutingAppender, whose route key pattern is evaluated per event viaStrSubstitutor. Fixed by returningContextDataFactory.emptyFrozenContextData()instead, courtesy of @nodece in #3.
Dependencies
- Bump
log4j-corefrom 2.25.3 to 2.25.4 (#2)
Full Changelog: v0.9.6...v0.9.7
v0.9.6
Bug Fixes
- Fix NPE in
JsonTemplateLayoutwhen using NDC resolver βMutableLogEvent.clear()nulls the context stack, and since slog bypassesThreadContext, the stack stayed null. Log4j2'sThreadContextStackResolverdoes not null-check it, causing an NPE with templates likeEcsLayout.json. Fixed by setting the context stack toEMPTY_STACKafterclear().
v0.9.5
What's New
Rate Limiting
- Added
onceEvery(int)andonceEvery(Duration)rate limiting to the Event API, allowing log events to be throttled to avoid flooding
Security
- Added security policy for vulnerability reporting
- Upgraded Jackson to 2.21.1 to fix GHSA-72hv-8253-57qq
- Added explicit permissions to CI and Release workflows
Other
- Added Google Flogger to benchmarks
- Added installation instructions to README
- Added Dependabot configuration
Full Changelog: v0.9.4...v0.9.5
v0.9.4
What's New
Performance optimizations
- Cached log level with generation counter β
isXxxEnabled()checks now use aVarHandle.getOpaque()generation-counter scheme, avoiding calls into the Log4j2 hierarchy on every check. Disabled path is 2.4β2.8Γ faster than native Log4j2/SLF4J. - Zero-alloc simple emit path β
MutableLogEvent, message, and context map are pooled inThreadLocals. Simple enabled path allocates 0 B/op. - Native Object values in context data β attribute values are passed as
Objectto Log4j2'sStringMap, avoiding unnecessarytoString()conversion at emit time. - Eliminate LogRecord from production β fields are passed directly through the
emit()signature;LogRecordandTestLoggermoved to the test source set. - Parallel arrays in EventImpl β fluent event attributes use inline
String[]/Object[]arrays instead ofArrayList<Attr>, reducing allocation from 144 β 80 B/op (14Γ less garbage than SLF4J fluent).
API improvements
ThrowingSupplier<T>β new functional interface replacingjava.util.function.Supplierfor attribute and message suppliers. Suppliers may now throw checked exceptions; failures are caught and the exception message is recorded as the attribute value, so a failing supplier never crashes the application.
Benchmark results (vs native framework calls)
| Path | slog | Log4j2 | SLF4J |
|---|---|---|---|
| Disabled (level check) | 1,005 ops/ΞΌs | 413 | 360 |
| Simple enabled | 24.6 ops/ΞΌs | 14.6 | 11.2 |
| Fluent enabled (3 attrs) | 13.2 ops/ΞΌs | 6.4 (positional) | 4.2 (fluent) |
| Simple alloc | 0 B/op | 24 B/op | 24 B/op |
| Fluent alloc | 80 B/op | 40 B/op | 1,104 B/op |
v0.9.3
What's New in v0.9.3
π Performance Optimizations
Major overhaul of the Log4j2 emit path, achieving 1.8x faster simple logging and 2x faster fluent structured logging compared to v0.9.2:
- Zero-allocation emit path β pool
MutableLogEventandSortedArrayStringMapin ThreadLocals, reusing them across log calls instead of allocating on every emit - Bypass ThreadContext entirely β structured attributes are set directly on the log event's context data, eliminating
ThreadContext.put()/clear()/restore()overhead - Remove redundant timestamp β eliminated double
clock.instant()call (one in slog, one in Log4j2) by letting Log4j2 handle timestamping viaclock.millis() - Skip context save/restore β when there are no structured attributes, skip the context data pipeline entirely
Benchmark Results (ops/ΞΌs, higher is better)
Enabled path (INFO call):
| Benchmark | ops/ΞΌs |
|---|---|
| slog Simple | 24.3 |
| SLF4J Simple | 13.9 |
| Log4j2 Simple | 13.6 |
| slog Fluent (3 attrs) | 9.5 |
| slog Fluent+Ctx | 7.8 |
| Log4j2 Positional | 7.4 |
| SLF4J Positional | 6.2 |
| SLF4J Fluent | 4.1 |
π¦ API & Library Changes
- Logger is now a pure interface β implementation moved to package-private classes
- Primitive
attr()overloads βattr(String, int),attr(String, long), etc. to avoid autoboxing when the log level is disabled Supplier<?>support β lazy attribute evaluation withattr(String, Supplier<?>)andlog(Supplier<String>)- JMH benchmark module β
./gradlew :benchmark:jmhto run benchmarks; supports async-profiler flame graphs
π Documentation
- Added Performance section to README with charts, comparison summary, and benchmark instructions
- Added Lombok
@CustomLogintegration instructions - Added package-level Javadoc for
io.github.merlimat.slog - Fixed Javadoc version extraction from release tag
Full Changelog: v0.9.2...v0.9.3
v0.9.2
What's Changed
New Features
- Add
Consumer<Event>overload for deferred logging of expensive messages β avoids constructing the log event when the level is disabled (068760d)
Bug Fixes
- Fix caller location resolution in Log4j2 output β stack frames now correctly point to the caller instead of the library (d5c44ef)
Internal
- Make
isEnabled(Level)private and use it consistently in all logging methods (cf53900)
v0.9.1
slog v0.9.1
Changes since v0.9.0
- No hard runtime dependencies β SLF4J is now
compileOnlylike Log4j2. slog auto-discovers whichever backend is on the classpath; throwsIllegalStateExceptionif neither is found - slf4j-simple support β new
Slf4jSimpleHandlerinlines attrs into the log message since slf4j-simple doesn't render MDC - Fix all javadoc warnings β proper
@param/@returntags on all public methods - Upgrade jackson-databind to 2.19.0 (test dependency, CVE fix)
Maven
<dependency>
<groupId>io.github.merlimat.slog</groupId>
<artifactId>slog</artifactId>
<version>0.9.1</version>
</dependency>Gradle
implementation("io.github.merlimat.slog:slog:0.9.1")