From 9080ae24f98de6353b2582b62ee34ad590ddb95a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 4 Apr 2019 01:52:51 +0200 Subject: [PATCH] First-class support for printf-style format strings in LogMessage LogMessage is an abstract class now: with internal subclasses for Supplier bindings as well as printf-style format strings with a variable number of arguments (some fixed for efficiency, varargs array as fallback), created through corresponding static factory methods. Closes gh-22726 --- .../springframework/core/log/LogAccessor.java | 24 +- .../springframework/core/log/LogMessage.java | 227 ++++++++++++++++-- .../core/log/LogSupportTests.java | 41 +++- 3 files changed, 263 insertions(+), 29 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java b/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java index c34f30c738a..acae717962c 100644 --- a/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java +++ b/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java @@ -180,7 +180,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void fatal(Supplier messageSupplier) { - this.log.fatal(new LogMessage(messageSupplier)); + this.log.fatal(LogMessage.lazy(messageSupplier)); } /** @@ -189,7 +189,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void fatal(Throwable cause, Supplier messageSupplier) { - this.log.fatal(new LogMessage(messageSupplier), cause); + this.log.fatal(LogMessage.lazy(messageSupplier), cause); } /** @@ -197,7 +197,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void error(Supplier messageSupplier) { - this.log.error(new LogMessage(messageSupplier)); + this.log.error(LogMessage.lazy(messageSupplier)); } /** @@ -206,7 +206,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void error(Throwable cause, Supplier messageSupplier) { - this.log.error(new LogMessage(messageSupplier), cause); + this.log.error(LogMessage.lazy(messageSupplier), cause); } /** @@ -214,7 +214,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void warn(Supplier messageSupplier) { - this.log.warn(new LogMessage(messageSupplier)); + this.log.warn(LogMessage.lazy(messageSupplier)); } /** @@ -223,7 +223,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void warn(Throwable cause, Supplier messageSupplier) { - this.log.warn(new LogMessage(messageSupplier), cause); + this.log.warn(LogMessage.lazy(messageSupplier), cause); } /** @@ -231,7 +231,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void info(Supplier messageSupplier) { - this.log.info(new LogMessage(messageSupplier)); + this.log.info(LogMessage.lazy(messageSupplier)); } /** @@ -240,7 +240,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void info(Throwable cause, Supplier messageSupplier) { - this.log.info(new LogMessage(messageSupplier), cause); + this.log.info(LogMessage.lazy(messageSupplier), cause); } /** @@ -248,7 +248,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void debug(Supplier messageSupplier) { - this.log.debug(new LogMessage(messageSupplier)); + this.log.debug(LogMessage.lazy(messageSupplier)); } /** @@ -257,7 +257,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void debug(Throwable cause, Supplier messageSupplier) { - this.log.debug(new LogMessage(messageSupplier), cause); + this.log.debug(LogMessage.lazy(messageSupplier), cause); } /** @@ -265,7 +265,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void trace(Supplier messageSupplier) { - this.log.trace(new LogMessage(messageSupplier)); + this.log.trace(LogMessage.lazy(messageSupplier)); } /** @@ -274,7 +274,7 @@ public class LogAccessor { * @param messageSupplier a lazy supplier for the message to log */ public void trace(Throwable cause, Supplier messageSupplier) { - this.log.trace(new LogMessage(messageSupplier), cause); + this.log.trace(LogMessage.lazy(messageSupplier), cause); } } diff --git a/spring-core/src/main/java/org/springframework/core/log/LogMessage.java b/spring-core/src/main/java/org/springframework/core/log/LogMessage.java index a1ce24465a8..9c0a199fc8c 100644 --- a/spring-core/src/main/java/org/springframework/core/log/LogMessage.java +++ b/spring-core/src/main/java/org/springframework/core/log/LogMessage.java @@ -23,11 +23,15 @@ import org.springframework.util.Assert; /** * A simple log message type for use with Commons Logging, allowing - * for convenient late resolution of a given {@link Supplier} instance - * (typically bound to a Java 8 lambda expression) in {@link #toString()}. + * for convenient lazy resolution of a given {@link Supplier} instance + * (typically bound to a Java 8 lambda expression) or a printf-style + * format string ({@link String#format})in its {@link #toString()}. * * @author Juergen Hoeller * @since 5.2 + * @see #lazy(Supplier) + * @see #format(String, Object) + * @see #format(String, Object...) * @see org.apache.commons.logging.Log#fatal(Object) * @see org.apache.commons.logging.Log#error(Object) * @see org.apache.commons.logging.Log#warn(Object) @@ -35,24 +39,26 @@ import org.springframework.util.Assert; * @see org.apache.commons.logging.Log#debug(Object) * @see org.apache.commons.logging.Log#trace(Object) */ -public class LogMessage { - - private final Supplier supplier; +public abstract class LogMessage implements CharSequence { @Nullable private String result; - /** - * Construct a new {@code LogMessage} for the given supplier. - * @param supplier the lazily resolving supplier - * (typically bound to a Java 8 lambda expression) - */ - public LogMessage(Supplier supplier) { - Assert.notNull(supplier, "Supplier must not be null"); - this.supplier = supplier; + @Override + public int length() { + return toString().length(); + } + + @Override + public char charAt(int index) { + return toString().charAt(index); } + @Override + public CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } /** * This will be called by the logging provider, potentially once @@ -61,9 +67,202 @@ public class LogMessage { @Override public String toString() { if (this.result == null) { - this.result = this.supplier.get().toString(); + this.result = buildString(); } return this.result; } + abstract String buildString(); + + + /** + * Build a lazy resolution message from the given supplier. + * @param supplier the supplier (typically bound to a Java 8 lambda expression) + * @see #toString() + */ + public static LogMessage lazy(Supplier supplier) { + return new LazyMessage(supplier); + } + + /** + * Build a formatted message from the given format string and argument. + * @param format the format string (following {@link String#format} rules) + * @param arg1 the argument + * @see String#format(String, Object...) + */ + public static LogMessage format(String format, Object arg1) { + return new FormatMessage1(format, arg1); + } + + /** + * Build a formatted message from the given format string and arguments. + * @param format the format string (following {@link String#format} rules) + * @param arg1 the first argument + * @param arg2 the second argument + * @see String#format(String, Object...) + */ + public static LogMessage format(String format, Object arg1, Object arg2) { + return new FormatMessage2(format, arg1, arg2); + } + + /** + * Build a formatted message from the given format string and arguments. + * @param format the format string (following {@link String#format} rules) + * @param arg1 the first argument + * @param arg2 the second argument + * @param arg3 the third argument + * @see String#format(String, Object...) + */ + public static LogMessage format(String format, Object arg1, Object arg2, Object arg3) { + return new FormatMessage3(format, arg1, arg2, arg3); + } + + /** + * Build a formatted message from the given format string and arguments. + * @param format the format string (following {@link String#format} rules) + * @param arg1 the first argument + * @param arg2 the second argument + * @param arg3 the third argument + * @param arg4 the fourth argument + * @see String#format(String, Object...) + */ + public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, Object arg4) { + return new FormatMessage4(format, arg1, arg2, arg3, arg4); + } + + /** + * Build a formatted message from the given format string and varargs. + * @param format the format string (following {@link String#format} rules) + * @param args the varargs array (costly, prefer individual arguments) + * @see String#format(String, Object...) + */ + public static LogMessage format(String format, Object... args) { + return new FormatMessageX(format, args); + } + + + private static final class LazyMessage extends LogMessage { + + private Supplier supplier; + + LazyMessage(Supplier supplier) { + Assert.notNull(supplier, "Supplier must not be null"); + this.supplier = supplier; + } + + @Override + String buildString() { + return this.supplier.get().toString(); + } + } + + + private static abstract class FormatMessage extends LogMessage { + + protected final String format; + + FormatMessage(String format) { + Assert.notNull(format, "Format must not be null"); + this.format = format; + } + } + + + private static final class FormatMessage1 extends FormatMessage { + + private final Object arg1; + + FormatMessage1(String format, Object arg1) { + super(format); + this.arg1 = arg1; + } + + @Override + protected String buildString() { + return String.format(this.format, this.arg1); + } + } + + + private static final class FormatMessage2 extends FormatMessage { + + private final Object arg1; + + private final Object arg2; + + FormatMessage2(String format, Object arg1, Object arg2) { + super(format); + this.arg1 = arg1; + this.arg2 = arg2; + } + + @Override + String buildString() { + return String.format(this.format, this.arg1, this.arg2); + } + } + + + private static final class FormatMessage3 extends FormatMessage { + + private final Object arg1; + + private final Object arg2; + + private final Object arg3; + + FormatMessage3(String format, Object arg1, Object arg2, Object arg3) { + super(format); + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + } + + @Override + String buildString() { + return String.format(this.format, this.arg1, this.arg2, this.arg3); + } + } + + + private static final class FormatMessage4 extends FormatMessage { + + private final Object arg1; + + private final Object arg2; + + private final Object arg3; + + private final Object arg4; + + FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) { + super(format); + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + this.arg4 = arg4; + } + + @Override + String buildString() { + return String.format(this.format, this.arg1, this.arg2, this.arg3, this.arg4); + } + } + + + private static final class FormatMessageX extends FormatMessage { + + private final Object[] args; + + FormatMessageX(String format, Object... args) { + super(format); + this.args = args; + } + + @Override + String buildString() { + return String.format(this.format, this.args); + } + } + } diff --git a/spring-core/src/test/java/org/springframework/core/log/LogSupportTests.java b/spring-core/src/test/java/org/springframework/core/log/LogSupportTests.java index e217df92789..a181751d4e4 100644 --- a/spring-core/src/test/java/org/springframework/core/log/LogSupportTests.java +++ b/spring-core/src/test/java/org/springframework/core/log/LogSupportTests.java @@ -27,9 +27,44 @@ import static org.junit.Assert.*; public class LogSupportTests { @Test - public void testLogMessage() { - LogMessage msg = new LogMessage(() -> new StringBuilder("a")); - assertEquals("a", msg.toString()); + public void testLogMessageWithSupplier() { + LogMessage msg = LogMessage.lazy(() -> new StringBuilder("a").append(" b")); + assertEquals("a b", msg.toString()); + assertSame(msg.toString(), msg.toString()); + } + + @Test + public void testLogMessageWithFormat1() { + LogMessage msg = LogMessage.format("a %s", "b"); + assertEquals("a b", msg.toString()); + assertSame(msg.toString(), msg.toString()); + } + + @Test + public void testLogMessageWithFormat2() { + LogMessage msg = LogMessage.format("a %s %s", "b", "c"); + assertEquals("a b c", msg.toString()); + assertSame(msg.toString(), msg.toString()); + } + + @Test + public void testLogMessageWithFormat3() { + LogMessage msg = LogMessage.format("a %s %s %s", "b", "c", "d"); + assertEquals("a b c d", msg.toString()); + assertSame(msg.toString(), msg.toString()); + } + + @Test + public void testLogMessageWithFormat4() { + LogMessage msg = LogMessage.format("a %s %s %s %s", "b", "c", "d", "e"); + assertEquals("a b c d e", msg.toString()); + assertSame(msg.toString(), msg.toString()); + } + + @Test + public void testLogMessageWithFormatX() { + LogMessage msg = LogMessage.format("a %s %s %s %s %s", "b", "c", "d", "e", "f"); + assertEquals("a b c d e f", msg.toString()); assertSame(msg.toString(), msg.toString()); }