Skip to content

Commit feed44f

Browse files
authored
Merge branch 'main' into fix-mdc-propagation
2 parents 60753c0 + df5a1fa commit feed44f

4 files changed

Lines changed: 142 additions & 1 deletion

File tree

src/main/java/io/github/merlimat/slog/Event.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,35 @@ public interface Event {
107107
*/
108108
Event attr(String key, ThrowingSupplier<?> value);
109109

110+
/**
111+
* Inherits the context attributes from another logger for this event only.
112+
* Useful when the current logger is a static/shared instance but you want
113+
* to attach the context of a request- or component-scoped logger without
114+
* allocating a new child logger.
115+
*
116+
* <p>Ordering mirrors {@link LoggerBuilder#ctx}: the inherited attrs are
117+
* placed <i>before</i> the current logger's own context, followed by the
118+
* per-event attrs. Multiple calls append in invocation order.
119+
*
120+
* <pre>{@code
121+
* static final Logger log = Logger.get(MyService.class);
122+
*
123+
* void handle(Logger requestLog, String msgId) {
124+
* log.info()
125+
* .ctx(requestLog) // adopt request-scoped context
126+
* .attr("msgId", msgId)
127+
* .log("processed");
128+
* }
129+
* }</pre>
130+
*
131+
* <p>Allocation-free when the current logger has no context of its own
132+
* (the common static-logger case).
133+
*
134+
* @param other the logger whose context to inherit
135+
* @return this event, for chaining
136+
*/
137+
Event ctx(Logger other);
138+
110139
/**
111140
* Attaches an exception to this event, including the full stack trace.
112141
* No-op if {@code t} is {@code null}.

src/main/java/io/github/merlimat/slog/impl/EventImpl.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.github.merlimat.slog.impl;
1717

1818
import io.github.merlimat.slog.Event;
19+
import io.github.merlimat.slog.Logger;
1920
import io.github.merlimat.slog.ThrowingSupplier;
2021
import java.time.Clock;
2122
import java.time.Duration;
@@ -35,6 +36,7 @@ final class EventImpl implements Event {
3536
private int attrCount;
3637
private Throwable throwable;
3738
private Instant startTime;
39+
private AttrChain extraContext = AttrChain.EMPTY;
3840

3941
EventImpl(BaseLogger logger, Level level, Clock clock) {
4042
this.logger = logger;
@@ -88,6 +90,15 @@ public Event attr(String key, ThrowingSupplier<?> value) {
8890
return attr(key, (Object) value);
8991
}
9092

93+
@Override
94+
public Event ctx(Logger other) {
95+
AttrChain otherCtx = ((BaseLogger) other).contextAttrs();
96+
if (!otherCtx.isEmpty()) {
97+
extraContext = otherCtx.withPrefix(extraContext);
98+
}
99+
return this;
100+
}
101+
91102
@Override
92103
public Event exception(Throwable t) {
93104
if (t != null) {
@@ -161,8 +172,12 @@ private void emit(String msg) {
161172
? Duration.between(startTime, clock.instant())
162173
: null;
163174

175+
AttrChain contextAttrs = extraContext.isEmpty()
176+
? logger.contextAttrs()
177+
: logger.contextAttrs().withPrefix(extraContext);
178+
164179
logger.emit(logger.name(), level, msg,
165-
logger.contextAttrs(),
180+
contextAttrs,
166181
attrKeys, attrValues, attrCount,
167182
throwable, duration, FQCN);
168183
}

src/main/java/io/github/merlimat/slog/impl/NoopEvent.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.github.merlimat.slog.impl;
1717

1818
import io.github.merlimat.slog.Event;
19+
import io.github.merlimat.slog.Logger;
1920
import io.github.merlimat.slog.ThrowingSupplier;
2021
import java.time.Duration;
2122

@@ -61,6 +62,11 @@ public Event attr(String key, ThrowingSupplier<?> value) {
6162
return this;
6263
}
6364

65+
@Override
66+
public Event ctx(Logger other) {
67+
return this;
68+
}
69+
6470
@Override
6571
public Event exception(Throwable t) {
6672
return this;

src/test/java/io/github/merlimat/slog/impl/LoggerTest.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,97 @@ void consumerVariantWithException() {
562562
assertSame(ex, r.throwable());
563563
}
564564

565+
@Test
566+
void eventCtxAttachesForeignContext() {
567+
Logger requestLog = TestLogger.create("request", enabledLevels, records).with()
568+
.attr("requestId", "req-42")
569+
.attr("traceId", "abc-123")
570+
.build();
571+
572+
Logger log = TestLogger.create("test", enabledLevels, records);
573+
log.info().ctx(requestLog).attr("msgId", "1:2:3").log("processed");
574+
575+
assertEquals(1, records.size());
576+
LogRecord r = records.get(0);
577+
// Logger name stays the static logger's name
578+
assertEquals("test", r.loggerName());
579+
List<Attr> a = attrs(r);
580+
assertEquals(3, a.size());
581+
// ctx attrs come first
582+
assertEquals("requestId", a.get(0).key());
583+
assertEquals("traceId", a.get(1).key());
584+
// then per-event attrs
585+
assertEquals("msgId", a.get(2).key());
586+
}
587+
588+
@Test
589+
void eventCtxOrderedBeforeLoggerOwnContext() {
590+
Logger requestLog = TestLogger.create("request", enabledLevels, records).with()
591+
.attr("requestId", "req-42")
592+
.build();
593+
594+
Logger log = TestLogger.create("test", enabledLevels, records).with()
595+
.attr("component", "worker")
596+
.build();
597+
598+
log.info().ctx(requestLog).attr("msgId", "1:2:3").log("processed");
599+
600+
List<Attr> a = attrs(records.get(0));
601+
assertEquals(3, a.size());
602+
// ctx attrs first, then logger's own, then per-event
603+
assertEquals("requestId", a.get(0).key());
604+
assertEquals("component", a.get(1).key());
605+
assertEquals("msgId", a.get(2).key());
606+
}
607+
608+
@Test
609+
void eventCtxWithEmptyLoggerIsNoOp() {
610+
Logger empty = TestLogger.create("empty", enabledLevels, records);
611+
Logger log = TestLogger.create("test", enabledLevels, records);
612+
613+
log.info().ctx(empty).attr("k", "v").log("msg");
614+
615+
List<Attr> a = attrs(records.get(0));
616+
assertEquals(1, a.size());
617+
assertEquals("k", a.get(0).key());
618+
}
619+
620+
@Test
621+
void eventCtxMultipleCallsAppendInOrder() {
622+
Logger producerLog = TestLogger.create("producer", enabledLevels, records).with()
623+
.attr("topic", "orders")
624+
.build();
625+
Logger requestLog = TestLogger.create("request", enabledLevels, records).with()
626+
.attr("requestId", "req-42")
627+
.build();
628+
629+
Logger log = TestLogger.create("test", enabledLevels, records);
630+
log.info()
631+
.ctx(producerLog)
632+
.ctx(requestLog)
633+
.attr("extra", "val")
634+
.log("combined");
635+
636+
List<Attr> a = attrs(records.get(0));
637+
assertEquals(3, a.size());
638+
assertEquals("topic", a.get(0).key());
639+
assertEquals("requestId", a.get(1).key());
640+
assertEquals("extra", a.get(2).key());
641+
}
642+
643+
@Test
644+
void eventCtxOnDisabledLevelIsNoOp() {
645+
Logger requestLog = TestLogger.create("request", enabledLevels, records).with()
646+
.attr("requestId", "req-42")
647+
.build();
648+
649+
Logger log = TestLogger.create("test", enabledLevels, records);
650+
// DEBUG is disabled in this test setup
651+
log.debug().ctx(requestLog).attr("k", "v").log("nope");
652+
653+
assertEquals(0, records.size());
654+
}
655+
565656
@Test
566657
void duplicateKeysInEventAttrs() {
567658
Logger log = TestLogger.create("test", enabledLevels, records).with()

0 commit comments

Comments
 (0)