Skip to content

Commit fb3d31d

Browse files
authored
Log exception stack trace for Throwable as last vararg (#3960)
Unifies `ConsoleLogger` and `JdkLogger` with `Slf4JLogger` to also append the stack trace in case the last argument of the varargs overloaded methods is a `Throwable`. Check https://www.slf4j.org/faq.html#paramException for details. --------- Signed-off-by: raccoonback <[email protected]>
1 parent adac9d9 commit fb3d31d

3 files changed

Lines changed: 318 additions & 56 deletions

File tree

reactor-core/src/main/java/reactor/util/Loggers.java

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2016-2025 VMware Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -353,7 +353,7 @@ public void trace(String msg) {
353353

354354
@Override
355355
public void trace(String format, Object... arguments) {
356-
logger.log(Level.FINEST, format(format, arguments));
356+
logWithPotentialThrowable(Level.FINEST, format, arguments);
357357
}
358358

359359
@Override
@@ -373,7 +373,7 @@ public void debug(String msg) {
373373

374374
@Override
375375
public void debug(String format, Object... arguments) {
376-
logger.log(Level.FINE, format(format, arguments));
376+
logWithPotentialThrowable(Level.FINE, format, arguments);
377377
}
378378

379379
@Override
@@ -393,7 +393,7 @@ public void info(String msg) {
393393

394394
@Override
395395
public void info(String format, Object... arguments) {
396-
logger.log(Level.INFO, format(format, arguments));
396+
logWithPotentialThrowable(Level.INFO, format, arguments);
397397
}
398398

399399
@Override
@@ -413,7 +413,7 @@ public void warn(String msg) {
413413

414414
@Override
415415
public void warn(String format, Object... arguments) {
416-
logger.log(Level.WARNING, format(format, arguments));
416+
logWithPotentialThrowable(Level.WARNING, format, arguments);
417417
}
418418

419419
@Override
@@ -433,7 +433,7 @@ public void error(String msg) {
433433

434434
@Override
435435
public void error(String format, Object... arguments) {
436-
logger.log(Level.SEVERE, format(format, arguments));
436+
logWithPotentialThrowable(Level.SEVERE, format, arguments);
437437
}
438438

439439
@Override
@@ -442,18 +442,52 @@ public void error(String msg, Throwable t) {
442442
}
443443

444444
@Nullable
445-
final String format(@Nullable String from, @Nullable Object... arguments){
446-
if(from != null) {
445+
private String format(@Nullable String from, @Nullable Object[] arguments) {
446+
return format(from, arguments, false);
447+
}
448+
449+
@Nullable
450+
private String format(@Nullable String from, @Nullable Object[] arguments, boolean skipLast) {
451+
if (from != null) {
447452
String computed = from;
448453
if (arguments != null && arguments.length != 0) {
449-
for (Object argument : arguments) {
450-
computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(argument)));
454+
int lastIndex = arguments.length;
455+
if (skipLast) {
456+
--lastIndex;
457+
}
458+
459+
for (int index = 0; index < lastIndex; ++index) {
460+
computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(arguments[index])));
451461
}
452462
}
453463
return computed;
454464
}
455465
return null;
456466
}
467+
468+
private void logWithPotentialThrowable(Level level, String format, Object... arguments) {
469+
Throwable t = getPotentialThrowable(arguments);
470+
if (t != null) {
471+
logger.log(level, format(format, arguments, true), t);
472+
return;
473+
}
474+
475+
logger.log(level, format(format, arguments));
476+
}
477+
478+
@Nullable
479+
private Throwable getPotentialThrowable(Object... arguments) {
480+
if (arguments == null) {
481+
return null;
482+
}
483+
484+
int length = arguments.length;
485+
if (length > 0 && arguments[length - 1] instanceof Throwable) {
486+
return (Throwable) arguments[length - 1];
487+
}
488+
489+
return null;
490+
}
457491
}
458492

459493
private static class JdkLoggerFactory implements Function<String, Logger> {
@@ -494,19 +528,64 @@ public String getName() {
494528
}
495529

496530
@Nullable
497-
final String format(@Nullable String from, @Nullable Object... arguments){
498-
if(from != null) {
531+
private String format(@Nullable String from, @Nullable Object[] arguments) {
532+
return format(from, arguments, false);
533+
}
534+
535+
@Nullable
536+
private String format(@Nullable String from, @Nullable Object[] arguments, boolean skipLast) {
537+
if (from != null) {
499538
String computed = from;
500539
if (arguments != null && arguments.length != 0) {
501-
for (Object argument : arguments) {
502-
computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(argument)));
540+
int lastIndex = arguments.length;
541+
if (skipLast) {
542+
--lastIndex;
543+
}
544+
545+
for (int index = 0; index < lastIndex; ++index) {
546+
computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(arguments[index])));
503547
}
504548
}
505549
return computed;
506550
}
507551
return null;
508552
}
509553

554+
private synchronized void logWithPotentialThrowable(PrintStream logger, String level, String format, Object... arguments) {
555+
Throwable t = getPotentialThrowable(arguments);
556+
if (t != null) {
557+
logger.format(
558+
"[%s] (%s) %s\n",
559+
level.toUpperCase(),
560+
Thread.currentThread().getName(),
561+
format(format, arguments, true)
562+
);
563+
t.printStackTrace(logger);
564+
return;
565+
}
566+
567+
logger.format(
568+
"[%s] (%s) %s\n",
569+
level.toUpperCase(),
570+
Thread.currentThread().getName(),
571+
format(format, arguments)
572+
);
573+
}
574+
575+
@Nullable
576+
private static Throwable getPotentialThrowable(Object... arguments) {
577+
if (arguments == null) {
578+
return null;
579+
}
580+
581+
int length = arguments.length;
582+
if (length > 0 && arguments[length - 1] instanceof Throwable) {
583+
return (Throwable) arguments[length - 1];
584+
}
585+
586+
return null;
587+
}
588+
510589
@Override
511590
public boolean isTraceEnabled() {
512591
return identifier.verbose;
@@ -521,12 +600,13 @@ public synchronized void trace(String msg) {
521600
}
522601

523602
@Override
524-
public synchronized void trace(String format, Object... arguments) {
603+
public void trace(String format, Object... arguments) {
525604
if (!identifier.verbose) {
526605
return;
527606
}
528-
this.log.format("[TRACE] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments));
607+
logWithPotentialThrowable(this.log, "TRACE", format, arguments);
529608
}
609+
530610
@Override
531611
public synchronized void trace(String msg, Throwable t) {
532612
if (!identifier.verbose) {
@@ -550,11 +630,11 @@ public synchronized void debug(String msg) {
550630
}
551631

552632
@Override
553-
public synchronized void debug(String format, Object... arguments) {
633+
public void debug(String format, Object... arguments) {
554634
if (!identifier.verbose) {
555635
return;
556636
}
557-
this.log.format("[DEBUG] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments));
637+
logWithPotentialThrowable(this.log, "DEBUG", format, arguments);
558638
}
559639

560640
@Override
@@ -577,8 +657,8 @@ public synchronized void info(String msg) {
577657
}
578658

579659
@Override
580-
public synchronized void info(String format, Object... arguments) {
581-
this.log.format("[ INFO] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments));
660+
public void info(String format, Object... arguments) {
661+
logWithPotentialThrowable(this.log, " INFO", format, arguments);
582662
}
583663

584664
@Override
@@ -598,8 +678,8 @@ public synchronized void warn(String msg) {
598678
}
599679

600680
@Override
601-
public synchronized void warn(String format, Object... arguments) {
602-
this.err.format("[ WARN] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments));
681+
public void warn(String format, Object... arguments) {
682+
logWithPotentialThrowable(this.err, " WARN", format, arguments);
603683
}
604684

605685
@Override
@@ -619,8 +699,8 @@ public synchronized void error(String msg) {
619699
}
620700

621701
@Override
622-
public synchronized void error(String format, Object... arguments) {
623-
this.err.format("[ERROR] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments));
702+
public void error(String format, Object... arguments) {
703+
logWithPotentialThrowable(this.err, "ERROR", format, arguments);
624704
}
625705

626706
@Override

reactor-core/src/test/java/reactor/util/ConsoleLoggerTest.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2017-2025 VMware Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,6 +80,17 @@ public void trace2() throws Exception {
8080
"\tat reactor.util.ConsoleLoggerTest");
8181
}
8282

83+
@Test
84+
public void traceWithThrowable() throws Exception {
85+
logger.trace("{} with cause {}", "test", CAUSE);
86+
87+
assertThat(errContent.size()).isZero();
88+
assertThat(outContent.toString())
89+
.startsWith("[TRACE] (" + Thread.currentThread().getName() + ") test with cause {}" +
90+
"\njava.lang.IllegalStateException: cause\n" +
91+
"\tat reactor.util.ConsoleLoggerTest");
92+
}
93+
8394
@Test
8495
public void traceNulls() {
8596
logger.trace("vararg {} is {}", (Object[]) null);
@@ -136,6 +147,17 @@ public void debug2() throws Exception {
136147
"\tat reactor.util.ConsoleLoggerTest");
137148
}
138149

150+
@Test
151+
public void debugWithThrowable() throws Exception {
152+
logger.debug("{} with cause {}", "test", CAUSE);
153+
154+
assertThat(errContent.size()).isZero();
155+
assertThat(outContent.toString())
156+
.startsWith("[DEBUG] (" + Thread.currentThread().getName() + ") test with cause {}" +
157+
"\njava.lang.IllegalStateException: cause\n" +
158+
"\tat reactor.util.ConsoleLoggerTest");
159+
}
160+
139161
@Test
140162
public void debugNulls() {
141163
logger.debug("vararg {} is {}", (Object[]) null);
@@ -192,6 +214,17 @@ public void info2() throws Exception {
192214
"\tat reactor.util.ConsoleLoggerTest");
193215
}
194216

217+
@Test
218+
public void infoWithThrowable() throws Exception {
219+
logger.info("{} with cause {}", "test", CAUSE);
220+
221+
assertThat(errContent.size()).isZero();
222+
assertThat(outContent.toString())
223+
.startsWith("[ INFO] (" + Thread.currentThread().getName() + ") test with cause {}" +
224+
"\njava.lang.IllegalStateException: cause\n" +
225+
"\tat reactor.util.ConsoleLoggerTest");
226+
}
227+
195228
@Test
196229
public void infoNulls() {
197230
logger.info("vararg {} is {}", (Object[]) null);
@@ -236,6 +269,17 @@ public void warn2() throws Exception {
236269
"\tat reactor.util.ConsoleLoggerTest");
237270
}
238271

272+
@Test
273+
public void warnWithThrowable() throws Exception {
274+
logger.warn("{} with cause {}", "test", CAUSE);
275+
276+
assertThat(outContent.size()).isZero();
277+
assertThat(errContent.toString())
278+
.startsWith("[ WARN] (" + Thread.currentThread().getName() + ") test with cause {}" +
279+
"\njava.lang.IllegalStateException: cause\n" +
280+
"\tat reactor.util.ConsoleLoggerTest");
281+
}
282+
239283
@Test
240284
public void warnNulls() {
241285
logger.warn("vararg {} is {}", (Object[]) null);
@@ -279,6 +323,17 @@ public void error2() throws Exception {
279323
"\tat reactor.util.ConsoleLoggerTest");
280324
}
281325

326+
@Test
327+
public void errorWithThrowable() throws Exception {
328+
logger.error("{} with cause {}", "test", CAUSE);
329+
330+
assertThat(outContent.size()).isZero();
331+
assertThat(errContent.toString())
332+
.startsWith("[ERROR] (" + Thread.currentThread().getName() + ") test with cause {}" +
333+
"\njava.lang.IllegalStateException: cause\n" +
334+
"\tat reactor.util.ConsoleLoggerTest");
335+
}
336+
282337
@Test
283338
public void errorNulls() {
284339
logger.error("vararg {} is {}", (Object[]) null);

0 commit comments

Comments
 (0)