Skip to content

Commit b927cfe

Browse files
committed
Add DeprecationRestHandler to automatically log deprecated REST calls
This adds a new proxy for RestHandlers and RestControllers so that requests made to deprecated REST APIs can be automatically logged in the ES logs via the DeprecationLogger as well as via a "Warning" header (RFC-7234) for all responses.
1 parent 76057e6 commit b927cfe

23 files changed

+1308
-94
lines changed

core/src/main/java/org/elasticsearch/ElasticsearchException.java

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.elasticsearch.transport.TcpTransport;
3434

3535
import java.io.IOException;
36-
import java.util.ArrayList;
3736
import java.util.Arrays;
3837
import java.util.Collections;
3938
import java.util.HashMap;
@@ -102,16 +101,7 @@ public ElasticsearchException(String msg, Throwable cause, Object... args) {
102101
public ElasticsearchException(StreamInput in) throws IOException {
103102
super(in.readOptionalString(), in.readException());
104103
readStackTrace(this, in);
105-
int numKeys = in.readVInt();
106-
for (int i = 0; i < numKeys; i++) {
107-
final String key = in.readString();
108-
final int numValues = in.readVInt();
109-
final ArrayList<String> values = new ArrayList<>(numValues);
110-
for (int j = 0; j < numValues; j++) {
111-
values.add(in.readString());
112-
}
113-
headers.put(key, values);
114-
}
104+
headers.putAll(in.readMapOfLists());
115105
}
116106

117107
/**
@@ -206,14 +196,7 @@ public void writeTo(StreamOutput out) throws IOException {
206196
out.writeOptionalString(this.getMessage());
207197
out.writeException(this.getCause());
208198
writeStackTraces(this, out);
209-
out.writeVInt(headers.size());
210-
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
211-
out.writeString(entry.getKey());
212-
out.writeVInt(entry.getValue().size());
213-
for (String v : entry.getValue()) {
214-
out.writeString(v);
215-
}
216-
}
199+
out.writeMapOfLists(headers);
217200
}
218201

219202
public static ElasticsearchException readException(StreamInput input, int id) throws IOException {

core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.nio.file.NoSuchFileException;
5454
import java.nio.file.NotDirectoryException;
5555
import java.util.ArrayList;
56+
import java.util.Collections;
5657
import java.util.Date;
5758
import java.util.HashMap;
5859
import java.util.LinkedHashMap;
@@ -414,6 +415,21 @@ public Map<String, Object> readMap() throws IOException {
414415
return (Map<String, Object>) readGenericValue();
415416
}
416417

418+
/**
419+
* Read a map of strings to string lists.
420+
*/
421+
public Map<String, List<String>> readMapOfLists() throws IOException {
422+
int size = readVInt();
423+
if (size == 0) {
424+
return Collections.emptyMap();
425+
}
426+
Map<String, List<String>> map = new HashMap<>(size);
427+
for (int i = 0; i < size; ++i) {
428+
map.put(readString(), readList(StreamInput::readString));
429+
}
430+
return map;
431+
}
432+
417433
@SuppressWarnings({"unchecked"})
418434
@Nullable
419435
public Object readGenericValue() throws IOException {

core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,21 @@ public void writeMap(@Nullable Map<String, Object> map) throws IOException {
403403
writeGenericValue(map);
404404
}
405405

406+
/**
407+
* Writes a map of strings to string lists.
408+
*/
409+
public void writeMapOfLists(Map<String, List<String>> map) throws IOException {
410+
writeVInt(map.size());
411+
412+
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
413+
writeString(entry.getKey());
414+
writeVInt(entry.getValue().size());
415+
for (String v : entry.getValue()) {
416+
writeString(v);
417+
}
418+
}
419+
}
420+
406421
@FunctionalInterface
407422
interface Writer {
408423
void write(StreamOutput o, Object value) throws IOException;

core/src/main/java/org/elasticsearch/common/logging/DeprecationLogger.java

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,70 @@
2020
package org.elasticsearch.common.logging;
2121

2222
import org.elasticsearch.common.SuppressLoggerChecks;
23+
import org.elasticsearch.common.util.concurrent.ThreadContext;
24+
25+
import java.util.Iterator;
26+
import java.util.Set;
27+
import java.util.concurrent.CopyOnWriteArraySet;
2328

2429
/**
2530
* A logger that logs deprecation notices.
2631
*/
2732
public class DeprecationLogger {
2833

34+
/**
35+
* The "Warning" Header comes from RFC-7234. As the RFC describes, it's generally used for caching purposes, but it can be
36+
* used for <em>any</em> warning.
37+
*
38+
* https://tools.ietf.org/html/rfc7234#section-5.5
39+
*/
40+
public static final String DEPRECATION_HEADER = "Warning";
41+
42+
/**
43+
* This is set once by the {@code Node} constructor, but it uses {@link CopyOnWriteArraySet} to ensure that tests can run in parallel.
44+
* <p>
45+
* Integration tests will create separate nodes within the same classloader, thus leading to a shared, {@code static} state.
46+
* In order for all tests to appropriately be handled, this must be able to remember <em>all</em> {@link ThreadContext}s that it is
47+
* given in a thread safe manner.
48+
* <p>
49+
* For actual usage, multiple nodes do not share the same JVM and therefore this will only be set once in practice.
50+
*/
51+
private static final CopyOnWriteArraySet<ThreadContext> THREAD_CONTEXT = new CopyOnWriteArraySet<>();
52+
53+
/**
54+
* Set the {@link ThreadContext} used to add deprecation headers to network responses.
55+
* <p>
56+
* This is expected to <em>only</em> be invoked by the {@code Node}'s constructor (therefore once outside of tests).
57+
*
58+
* @param threadContext The thread context owned by the {@code ThreadPool} (and implicitly a {@code Node})
59+
* @throws IllegalStateException if this {@code threadContext} has already been set
60+
*/
61+
public static void setThreadContext(ThreadContext threadContext) {
62+
assert threadContext != null;
63+
64+
// add returning false means it _did_ have it already
65+
if (THREAD_CONTEXT.add(threadContext) == false) {
66+
throw new IllegalStateException("Double-setting ThreadContext not allowed!");
67+
}
68+
}
69+
70+
/**
71+
* Remove the {@link ThreadContext} used to add deprecation headers to network responses.
72+
* <p>
73+
* This is expected to <em>only</em> be invoked by the {@code Node}'s {@code close} method (therefore once outside of tests).
74+
*
75+
* @param threadContext The thread context owned by the {@code ThreadPool} (and implicitly a {@code Node})
76+
* @throws IllegalStateException if this {@code threadContext} is unknown (and presumably already unset before)
77+
*/
78+
public static void removeThreadContext(ThreadContext threadContext) {
79+
assert threadContext != null;
80+
81+
// remove returning false means it did not have it already
82+
if (THREAD_CONTEXT.remove(threadContext) == false) {
83+
throw new IllegalStateException("Removing unknown ThreadContext not allowed!");
84+
}
85+
}
86+
2987
private final ESLogger logger;
3088

3189
/**
@@ -47,8 +105,37 @@ public DeprecationLogger(ESLogger parentLogger) {
47105
/**
48106
* Logs a deprecated message.
49107
*/
50-
@SuppressLoggerChecks(reason = "safely delegates to logger")
51108
public void deprecated(String msg, Object... params) {
52-
logger.debug(msg, params);
109+
deprecated(THREAD_CONTEXT, msg, params);
110+
}
111+
112+
/**
113+
* Logs a deprecated message to the deprecation log, as well as to the local {@link ThreadContext}.
114+
*
115+
* @param threadContexts The node's {@link ThreadContext} (outside of concurrent tests, this should only ever have one context).
116+
* @param msg The deprecation message.
117+
* @param params The parameters used to fill in the message, if any exist.
118+
*/
119+
@SuppressLoggerChecks(reason = "safely delegates to logger")
120+
void deprecated(Set<ThreadContext> threadContexts, String msg, Object... params) {
121+
Iterator<ThreadContext> iterator = threadContexts.iterator();
122+
123+
if (iterator.hasNext()) {
124+
final String formattedMsg = LoggerMessageFormat.format(msg, params);
125+
126+
while (iterator.hasNext()) {
127+
try {
128+
iterator.next().addResponseHeader(DEPRECATION_HEADER, formattedMsg);
129+
} catch (IllegalStateException e) {
130+
// ignored; it should be removed shortly
131+
}
132+
}
133+
134+
logger.debug(formattedMsg);
135+
} else {
136+
logger.debug(msg, params);
137+
}
138+
53139
}
140+
54141
}

0 commit comments

Comments
 (0)