Skip to content

Commit db8e9ea

Browse files
committed
Add LogFormatUtils
1. Helper method to eliminate duplication in formatting (de-)serialized values for logging introduced with prior commit #e62298. 2. Helper method for TRACE vs DEBUG logging with different details. Issue: SPR-17254
1 parent 41d4cb5 commit db8e9ea

24 files changed

+212
-289
lines changed

spring-core/src/main/java/org/springframework/core/codec/CharSequenceEncoder.java

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.core.ResolvableType;
2929
import org.springframework.core.io.buffer.DataBuffer;
3030
import org.springframework.core.io.buffer.DataBufferFactory;
31+
import org.springframework.core.log.LogFormatUtils;
3132
import org.springframework.lang.Nullable;
3233
import org.springframework.util.MimeType;
3334
import org.springframework.util.MimeTypeUtils;
@@ -68,15 +69,11 @@ public Flux<DataBuffer> encode(Publisher<? extends CharSequence> inputStream,
6869
Charset charset = getCharset(mimeType);
6970

7071
return Flux.from(inputStream).map(charSequence -> {
71-
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
72-
String logPrefix = Hints.getLogPrefix(hints);
73-
String s = logPrefix + "Writing " + formatValue(charSequence, logger.isTraceEnabled());
74-
if (logger.isTraceEnabled()) {
75-
logger.trace(s);
76-
}
77-
else {
78-
logger.debug(s);
79-
}
72+
if (!Hints.isLoggingSuppressed(hints)) {
73+
LogFormatUtils.traceDebug(logger, traceOn -> {
74+
String formatted = LogFormatUtils.formatValue(charSequence, !traceOn);
75+
return Hints.getLogPrefix(hints) + "Writing " + formatted;
76+
});
8077
}
8178
CharBuffer charBuffer = CharBuffer.wrap(charSequence);
8279
ByteBuffer byteBuffer = charset.encode(charBuffer);
@@ -95,14 +92,6 @@ private Charset getCharset(@Nullable MimeType mimeType) {
9592
return charset;
9693
}
9794

98-
private String formatValue(@Nullable Object value, boolean logFullValue) {
99-
if (value == null) {
100-
return "";
101-
}
102-
String s = value instanceof CharSequence ? "\"" + value + "\"" : value.toString();
103-
return logFullValue || s.length() < 100 ? s : s.substring(0, 100) + " (truncated)...";
104-
}
105-
10695

10796
/**
10897
* Create a {@code CharSequenceEncoder} that supports only "text/plain".

spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.core.io.buffer.DataBuffer;
3434
import org.springframework.core.io.buffer.DataBufferUtils;
3535
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
36+
import org.springframework.core.log.LogFormatUtils;
3637
import org.springframework.lang.Nullable;
3738
import org.springframework.util.Assert;
3839
import org.springframework.util.MimeType;
@@ -206,15 +207,10 @@ protected String decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementT
206207
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
207208
DataBufferUtils.release(dataBuffer);
208209
String value = charBuffer.toString();
209-
if (logger.isDebugEnabled()) {
210-
String s = Hints.getLogPrefix(hints) + "Decoded " + formatValue(value, logger.isTraceEnabled());
211-
if (logger.isTraceEnabled()) {
212-
logger.trace(s);
213-
}
214-
else {
215-
logger.debug(s);
216-
}
217-
}
210+
LogFormatUtils.traceDebug(logger, traceOn -> {
211+
String formatted = LogFormatUtils.formatValue(value, !traceOn);
212+
return Hints.getLogPrefix(hints) + "Decoded " + formatted;
213+
});
218214
return value;
219215
}
220216

@@ -227,14 +223,6 @@ private static Charset getCharset(@Nullable MimeType mimeType) {
227223
}
228224
}
229225

230-
private String formatValue(@Nullable Object value, boolean logFullValue) {
231-
if (value == null) {
232-
return "";
233-
}
234-
String s = value instanceof CharSequence ? "\"" + value + "\"" : value.toString();
235-
return logFullValue || s.length() < 100 ? s : s.substring(0, 100) + " (truncated)...";
236-
}
237-
238226

239227
/**
240228
* Create a {@code StringDecoder} for {@code "text/plain"}.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.core.log;
17+
18+
import java.util.function.Function;
19+
20+
import org.apache.commons.logging.Log;
21+
22+
import org.springframework.lang.Nullable;
23+
24+
/**
25+
* Utility methods for formatting and logging messages.
26+
*
27+
* <p>Mainly for internal use within the framework with Apache Commons Logging,
28+
* typically in the form of the {@code spring-jcl} bridge but also compatible
29+
* with other Commons Logging bridges.
30+
*
31+
* @author Rossen Stoyanchev
32+
* @since 5.1
33+
*/
34+
public final class LogFormatUtils {
35+
36+
private LogFormatUtils() {
37+
}
38+
39+
40+
/**
41+
* Format the given value via {@code toString()}, quoting it if it is a
42+
* {@link CharSequence}, and possibly truncating at 100 if limitLength is
43+
* set to true.
44+
* @param value the value to format
45+
* @param limitLength whether to truncate large formatted values (over 100).
46+
* @return the formatted value
47+
* @since 5.1
48+
*/
49+
public static String formatValue(@Nullable Object value, boolean limitLength) {
50+
if (value == null) {
51+
return "";
52+
}
53+
String s = value instanceof CharSequence ? "\"" + value + "\"" : value.toString();
54+
return limitLength && s.length() > 100 ? s.substring(0, 100) + " (truncated)..." : s;
55+
}
56+
57+
/**
58+
* Use this to log a message with different levels of detail (or different
59+
* messages) at TRACE vs DEBUG log levels. Effectively, a substitute for:
60+
* <pre class="code">
61+
* if (logger.isDebugEnabled()) {
62+
* String s = logger.isTraceEnabled() ? "..." : "...";
63+
* if (logger.isTraceEnabled()) {
64+
* logger.trace(s);
65+
* }
66+
* else {
67+
* logger.debug(s);
68+
* }
69+
* }
70+
* </pre>
71+
* @param logger the logger to use to log the message
72+
* @param messageFactory function that accepts a boolean set to the value
73+
* of {@link Log#isTraceEnabled()}.
74+
*/
75+
public static void traceDebug(Log logger, Function<Boolean, String> messageFactory) {
76+
if (logger.isDebugEnabled()) {
77+
String logMessage = messageFactory.apply(logger.isTraceEnabled());
78+
if (logger.isTraceEnabled()) {
79+
logger.trace(logMessage);
80+
}
81+
else {
82+
logger.debug(logMessage);
83+
}
84+
}
85+
}
86+
87+
}

spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageReader.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.ResolvableType;
3232
import org.springframework.core.codec.Hints;
3333
import org.springframework.core.io.buffer.DataBufferUtils;
34+
import org.springframework.core.log.LogFormatUtils;
3435
import org.springframework.http.MediaType;
3536
import org.springframework.http.ReactiveHttpInputMessage;
3637
import org.springframework.lang.Nullable;
@@ -114,18 +115,10 @@ public Mono<MultiValueMap<String, String>> readMono(ResolvableType elementType,
114115
}
115116

116117
private void logFormData(MultiValueMap<String, String> formData, Map<String, Object> hints) {
117-
if (logger.isDebugEnabled()) {
118-
String s = Hints.getLogPrefix(hints) + "Read " +
119-
(isEnableLoggingRequestDetails() ?
120-
formatValue(formData, logger.isTraceEnabled()) :
121-
"form fields " + formData.keySet() + " (content masked)");
122-
if (logger.isTraceEnabled()) {
123-
logger.trace(s);
124-
}
125-
else {
126-
logger.debug(s);
127-
}
128-
}
118+
LogFormatUtils.traceDebug(logger, traceOn -> Hints.getLogPrefix(hints) + "Read " +
119+
(isEnableLoggingRequestDetails() ?
120+
LogFormatUtils.formatValue(formData, !traceOn) :
121+
"form fields " + formData.keySet() + " (content masked)"));
129122
}
130123

131124
private Charset getMediaTypeCharset(@Nullable MediaType mediaType) {

spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.ResolvableType;
3232
import org.springframework.core.codec.Hints;
3333
import org.springframework.core.io.buffer.DataBuffer;
34+
import org.springframework.core.log.LogFormatUtils;
3435
import org.springframework.http.MediaType;
3536
import org.springframework.http.ReactiveHttpOutputMessage;
3637
import org.springframework.lang.Nullable;
@@ -131,18 +132,7 @@ public Mono<Void> write(Publisher<? extends MultiValueMap<String, String>> input
131132
Assert.notNull(charset, "No charset"); // should never occur
132133

133134
return Mono.from(inputStream).flatMap(form -> {
134-
if (logger.isDebugEnabled()) {
135-
String s = Hints.getLogPrefix(hints) + "Writing " +
136-
(isEnableLoggingRequestDetails() ?
137-
formatValue(form, logger.isTraceEnabled()) :
138-
"form fields " + form.keySet() + " (content masked)");
139-
if (logger.isTraceEnabled()) {
140-
logger.trace(s);
141-
}
142-
else {
143-
logger.debug(s);
144-
}
145-
}
135+
logFormData(form, hints);
146136
String value = serializeForm(form, charset);
147137
ByteBuffer byteBuffer = charset.encode(value);
148138
DataBuffer buffer = message.bufferFactory().wrap(byteBuffer);
@@ -163,6 +153,13 @@ else if (mediaType.getCharset() == null) {
163153
}
164154
}
165155

156+
private void logFormData(MultiValueMap<String, String> form, Map<String, Object> hints) {
157+
LogFormatUtils.traceDebug(logger, traceOn -> Hints.getLogPrefix(hints) + "Writing " +
158+
(isEnableLoggingRequestDetails() ?
159+
LogFormatUtils.formatValue(form, !traceOn) :
160+
"form fields " + form.keySet() + " (content masked)"));
161+
}
162+
166163
protected String serializeForm(MultiValueMap<String, String> formData, Charset charset) {
167164
StringBuilder builder = new StringBuilder();
168165
formData.forEach((name, values) ->

spring-web/src/main/java/org/springframework/http/codec/LoggingCodecSupport.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.apache.commons.logging.Log;
2020

2121
import org.springframework.http.HttpLogging;
22-
import org.springframework.lang.Nullable;
2322

2423
/**
2524
* Base class for {@link org.springframework.core.codec.Encoder},
@@ -56,20 +55,4 @@ public boolean isEnableLoggingRequestDetails() {
5655
return this.enableLoggingRequestDetails;
5756
}
5857

59-
/**
60-
* Format the given value via {{toString()}}, either in full or truncated
61-
* if it has 100 or more characters.
62-
* @param value the value to format
63-
* @param logFullValue whether to log in full or truncate if necessary
64-
* @return the formatted value
65-
* @since 5.1
66-
*/
67-
protected String formatValue(@Nullable Object value, boolean logFullValue) {
68-
if (value == null) {
69-
return "";
70-
}
71-
String s = value instanceof CharSequence ? "\"" + value + "\"" : value.toString();
72-
return logFullValue || s.length() < 100 ? s : s.substring(0, 100) + " (truncated)...";
73-
}
74-
7558
}

spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.core.codec.DecodingException;
3939
import org.springframework.core.codec.Hints;
4040
import org.springframework.core.io.buffer.DataBuffer;
41+
import org.springframework.core.log.LogFormatUtils;
4142
import org.springframework.http.codec.HttpMessageDecoder;
4243
import org.springframework.http.server.reactive.ServerHttpRequest;
4344
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -115,15 +116,11 @@ private Flux<Object> decodeInternal(Flux<TokenBuffer> tokens, ResolvableType ele
115116
return tokens.map(tokenBuffer -> {
116117
try {
117118
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
118-
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
119-
boolean traceOn = logger.isTraceEnabled();
120-
String s = Hints.getLogPrefix(hints) + "Decoded [" + formatValue(value, traceOn) + "]";
121-
if (traceOn) {
122-
logger.trace(s);
123-
}
124-
else {
125-
logger.debug(s);
126-
}
119+
if (!Hints.isLoggingSuppressed(hints)) {
120+
LogFormatUtils.traceDebug(logger, traceOn -> {
121+
String formatted = LogFormatUtils.formatValue(value, !traceOn);
122+
return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]";
123+
});
127124
}
128125
return value;
129126
}

spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.core.codec.Hints;
4545
import org.springframework.core.io.buffer.DataBuffer;
4646
import org.springframework.core.io.buffer.DataBufferFactory;
47+
import org.springframework.core.log.LogFormatUtils;
4748
import org.springframework.http.MediaType;
4849
import org.springframework.http.codec.HttpMessageEncoder;
4950
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -141,15 +142,11 @@ public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory buffe
141142
private DataBuffer encodeValue(Object value, @Nullable MimeType mimeType, DataBufferFactory bufferFactory,
142143
ResolvableType elementType, @Nullable Map<String, Object> hints, JsonEncoding encoding) {
143144

144-
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
145-
boolean traceOn = logger.isTraceEnabled();
146-
String s = Hints.getLogPrefix(hints) + "Encoding [" + formatValue(value, traceOn) + "]";
147-
if (traceOn) {
148-
logger.trace(s);
149-
}
150-
else {
151-
logger.debug(s);
152-
}
145+
if (!Hints.isLoggingSuppressed(hints)) {
146+
LogFormatUtils.traceDebug(logger, traceOn -> {
147+
String formatted = LogFormatUtils.formatValue(value, !traceOn);
148+
return Hints.getLogPrefix(hints) + "Encoding [" + formatted + "]";
149+
});
153150
}
154151

155152
JavaType javaType = getJavaType(elementType.getType(), null);

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,4 @@ protected MethodParameter getParameter(ResolvableType type) {
125125
@Nullable
126126
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);
127127

128-
String formatValue(@Nullable Object value, boolean logFullValue) {
129-
if (value == null) {
130-
return "";
131-
}
132-
String s = value instanceof CharSequence ? "\"" + value + "\"" : value.toString();
133-
return logFullValue || s.length() < 100 ? s : s.substring(0, 100) + " (truncated)...";
134-
}
135-
136128
}

spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageReader.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.springframework.core.ResolvableType;
3030
import org.springframework.core.codec.Hints;
31+
import org.springframework.core.log.LogFormatUtils;
3132
import org.springframework.http.MediaType;
3233
import org.springframework.http.ReactiveHttpInputMessage;
3334
import org.springframework.http.codec.HttpMessageReader;
@@ -94,18 +95,10 @@ public Mono<MultiValueMap<String, Part>> readMono(ResolvableType elementType,
9495
return this.partReader.read(elementType, inputMessage, allHints)
9596
.collectMultimap(Part::name)
9697
.doOnNext(map -> {
97-
if (logger.isDebugEnabled()) {
98-
String s = Hints.getLogPrefix(hints) + "Parsed " +
99-
(isEnableLoggingRequestDetails() ?
100-
formatValue(map, logger.isTraceEnabled()) :
101-
"parts " + map.keySet() + " (content masked)");
102-
if (logger.isTraceEnabled()) {
103-
logger.trace(s);
104-
}
105-
else {
106-
logger.debug(s);
107-
}
108-
}
98+
LogFormatUtils.traceDebug(logger, traceOn -> Hints.getLogPrefix(hints) + "Parsed " +
99+
(isEnableLoggingRequestDetails() ?
100+
LogFormatUtils.formatValue(map, !traceOn) :
101+
"parts " + map.keySet() + " (content masked)"));
109102
})
110103
.map(this::toMultiValueMap);
111104
}

0 commit comments

Comments
 (0)