Skip to content

Commit ad42010

Browse files
committed
Correlate data buffers to request log messages
HttpMessageWriter implementations now attach the request log prefix as a hint to created data buffers when the logger associated with the writer is at DEBUG level. Closes gh-26230
1 parent 10f6a22 commit ad42010

File tree

11 files changed

+99
-15
lines changed

11 files changed

+99
-15
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.apache.commons.logging.Log;
2323

24+
import org.springframework.core.io.buffer.DataBuffer;
25+
import org.springframework.core.io.buffer.DataBufferUtils;
2426
import org.springframework.lang.Nullable;
2527
import org.springframework.util.CollectionUtils;
2628

@@ -148,4 +150,22 @@ public static Map<String, Object> merge(Map<String, Object> hints, String hintNa
148150
}
149151
}
150152

153+
/**
154+
* If the hints contain a {@link #LOG_PREFIX_HINT} and the given logger has
155+
* DEBUG level enabled, apply the log prefix as a hint to the given buffer
156+
* via {@link DataBufferUtils#touch(DataBuffer, Object)}.
157+
* @param buffer the buffer to touch
158+
* @param hints the hints map to check for a log prefix
159+
* @param logger the logger whose level to check
160+
* @since 5.3.2
161+
*/
162+
public static void touchDataBuffer(DataBuffer buffer, @Nullable Map<String, Object> hints, Log logger) {
163+
if (logger.isDebugEnabled() && hints != null) {
164+
Object logPrefix = hints.get(LOG_PREFIX_HINT);
165+
if (logPrefix != null) {
166+
DataBufferUtils.touch(buffer, logPrefix);
167+
}
168+
}
169+
}
170+
151171
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -130,6 +130,9 @@ private Flux<DataBuffer> writeResourceRegion(
130130
}
131131

132132
Flux<DataBuffer> in = DataBufferUtils.read(resource, position, bufferFactory, this.bufferSize);
133+
if (logger.isDebugEnabled()) {
134+
in = in.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
135+
}
133136
return DataBufferUtils.takeUntilByteCount(in, count);
134137
}
135138

spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,24 @@ public static <T extends DataBuffer> T retain(T dataBuffer) {
487487
}
488488
}
489489

490+
/**
491+
* Associate the given hint with the data buffer if it is a pooled buffer
492+
* and supports leak tracking.
493+
* @param dataBuffer the data buffer to attach the hint to
494+
* @param hint the hint to attach
495+
* @return the input buffer
496+
* @since 5.3.2
497+
*/
498+
@SuppressWarnings("unchecked")
499+
public static <T extends DataBuffer> T touch(T dataBuffer, Object hint) {
500+
if (dataBuffer instanceof PooledDataBuffer) {
501+
return (T) ((PooledDataBuffer) dataBuffer).touch(hint);
502+
}
503+
else {
504+
return dataBuffer;
505+
}
506+
}
507+
490508
/**
491509
* Release the given data buffer, if it is a {@link PooledDataBuffer} and
492510
* has been {@linkplain PooledDataBuffer#isAllocated() allocated}.

spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBuffer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ public PooledDataBuffer retain() {
315315
return new NettyDataBuffer(this.byteBuf.retain(), this.dataBufferFactory);
316316
}
317317

318+
@Override
319+
public PooledDataBuffer touch(Object hint) {
320+
this.byteBuf.touch(hint);
321+
return this;
322+
}
323+
318324
@Override
319325
public boolean release() {
320326
return this.byteBuf.release();

spring-core/src/main/java/org/springframework/core/io/buffer/PooledDataBuffer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -38,6 +38,13 @@ public interface PooledDataBuffer extends DataBuffer {
3838
*/
3939
PooledDataBuffer retain();
4040

41+
/**
42+
* Associate the given hint with the data buffer for debugging purposes.
43+
* @return this buffer
44+
* @since 5.3.2
45+
*/
46+
PooledDataBuffer touch(Object hint);
47+
4148
/**
4249
* Decrease the reference count for this buffer by one,
4350
* and deallocate it once the count reaches zero.

spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/LeakAwareDataBuffer.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -17,6 +17,7 @@
1717
package org.springframework.core.testfixture.io.buffer;
1818

1919
import org.springframework.core.io.buffer.DataBuffer;
20+
import org.springframework.core.io.buffer.DataBufferUtils;
2021
import org.springframework.core.io.buffer.DataBufferWrapper;
2122
import org.springframework.core.io.buffer.PooledDataBuffer;
2223
import org.springframework.util.Assert;
@@ -67,19 +68,19 @@ public boolean isAllocated() {
6768

6869
@Override
6970
public PooledDataBuffer retain() {
70-
DataBuffer delegate = dataBuffer();
71-
if (delegate instanceof PooledDataBuffer) {
72-
((PooledDataBuffer) delegate).retain();
73-
}
71+
DataBufferUtils.retain(dataBuffer());
72+
return this;
73+
}
74+
75+
@Override
76+
public PooledDataBuffer touch(Object hint) {
77+
DataBufferUtils.touch(dataBuffer(), hint);
7478
return this;
7579
}
7680

7781
@Override
7882
public boolean release() {
79-
DataBuffer delegate = dataBuffer();
80-
if (delegate instanceof PooledDataBuffer) {
81-
((PooledDataBuffer) delegate).release();
82-
}
83+
DataBufferUtils.release(dataBuffer());
8384
return isAllocated();
8485
}
8586

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
*/
5858
public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
5959

60+
private static final Log logger = HttpLogging.forLogName(EncoderHttpMessageWriter.class);
61+
62+
6063
private final Encoder<T> encoder;
6164

6265
private final List<MediaType> mediaTypes;
@@ -125,17 +128,23 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme
125128
return message.setComplete().then(Mono.empty());
126129
}))
127130
.flatMap(buffer -> {
131+
Hints.touchDataBuffer(buffer, hints, logger);
128132
message.getHeaders().setContentLength(buffer.readableByteCount());
129133
return message.writeWith(Mono.just(buffer)
130134
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
131135
});
132136
}
133137

134138
if (isStreamingMediaType(contentType)) {
135-
return message.writeAndFlushWith(body.map(buffer ->
136-
Mono.just(buffer).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)));
139+
return message.writeAndFlushWith(body.map(buffer -> {
140+
Hints.touchDataBuffer(buffer, hints, logger);
141+
return Mono.just(buffer).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
142+
}));
137143
}
138144

145+
if (logger.isDebugEnabled()) {
146+
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
147+
}
139148
return message.writeWith(body);
140149
}
141150

@@ -166,6 +175,9 @@ private static MediaType addDefaultCharset(MediaType main, @Nullable MediaType d
166175
return main;
167176
}
168177

178+
private static void touch(DataBuffer buffer, Map<String, Object> hints) {
179+
}
180+
169181
private boolean isStreamingMediaType(@Nullable MediaType mediaType) {
170182
if (mediaType == null || !(this.encoder instanceof HttpMessageEncoder)) {
171183
return false;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -132,6 +132,9 @@ private Mono<Void> writeResource(Resource resource, ResolvableType type, @Nullab
132132
Mono<Resource> input = Mono.just(resource);
133133
DataBufferFactory factory = message.bufferFactory();
134134
Flux<DataBuffer> body = this.encoder.encode(input, factory, type, resourceMediaType, hints);
135+
if (logger.isDebugEnabled()) {
136+
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
137+
}
135138
return message.writeWith(body);
136139
});
137140
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525

26+
import org.apache.commons.logging.Log;
2627
import org.reactivestreams.Publisher;
2728
import reactor.core.publisher.Flux;
2829
import reactor.core.publisher.Mono;
@@ -35,6 +36,7 @@
3536
import org.springframework.core.io.buffer.DataBufferFactory;
3637
import org.springframework.core.io.buffer.DataBufferUtils;
3738
import org.springframework.core.io.buffer.PooledDataBuffer;
39+
import org.springframework.http.HttpLogging;
3840
import org.springframework.http.MediaType;
3941
import org.springframework.http.ReactiveHttpOutputMessage;
4042
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -57,6 +59,8 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
5759

5860
private static final List<MediaType> WRITABLE_MEDIA_TYPES = Collections.singletonList(MediaType.TEXT_EVENT_STREAM);
5961

62+
private static final Log logger = HttpLogging.forLogName(ServerSentEventHttpMessageWriter.class);
63+
6064

6165
@Nullable
6266
private final Encoder<?> encoder;
@@ -167,9 +171,11 @@ private <T> Flux<DataBuffer> encodeEvent(StringBuilder eventContent, T data, Res
167171
if (this.encoder == null) {
168172
throw new CodecException("No SSE encoder configured and the data is not String.");
169173
}
174+
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints);
175+
Hints.touchDataBuffer(buffer, hints, logger);
170176
return Flux.just(factory.join(Arrays.asList(
171177
encodeText(eventContent, mediaType, factory),
172-
((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints),
178+
buffer,
173179
encodeText("\n\n", mediaType, factory))));
174180
}
175181

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ private Mono<Void> writeMultipart(MultiValueMap<String, ?> map,
199199
.concatWith(generateLastLine(boundary, bufferFactory))
200200
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
201201

202+
if (logger.isDebugEnabled()) {
203+
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
204+
}
205+
202206
return outputMessage.writeWith(body);
203207
}
204208

0 commit comments

Comments
 (0)