Skip to content

Commit 561222b

Browse files
authored
Report parser name and location in XContent deprecation warnings (#53805)
It's simple to deprecate a field used in an ObjectParser just by adding deprecation markers to the relevant ParseField objects. The warnings themselves don't currently have any context - they simply say that a deprecated field has been used, but not where in the input xcontent it appears. This commit adds the parent object parser name and XContentLocation to these deprecation messages. Note that the context is automatically stripped from warning messages when they are asserted on by integration tests and REST tests, because randomization of xcontent type during these tests means that the XContentLocation is not constant * Strip xcontentloc in yaml tests * Handle negative pos values
1 parent 2794ab7 commit 561222b

File tree

18 files changed

+145
-65
lines changed

18 files changed

+145
-65
lines changed

libs/x-content/src/main/java/org/elasticsearch/common/ParseField.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
package org.elasticsearch.common;
2020

2121
import org.elasticsearch.common.xcontent.DeprecationHandler;
22+
import org.elasticsearch.common.xcontent.XContentLocation;
2223

2324
import java.util.Collections;
2425
import java.util.HashSet;
2526
import java.util.Objects;
2627
import java.util.Set;
28+
import java.util.function.Supplier;
2729

2830
/**
2931
* Holds a field that can be found in a request while parsing and its different
@@ -115,6 +117,22 @@ public ParseField withAllDeprecated() {
115117
* names for this {@link ParseField}.
116118
*/
117119
public boolean match(String fieldName, DeprecationHandler deprecationHandler) {
120+
return match(null, () -> XContentLocation.UNKNOWN, fieldName, deprecationHandler);
121+
}
122+
123+
/**
124+
* Does {@code fieldName} match this field?
125+
* @param parserName
126+
* the name of the parent object holding this field
127+
* @param location
128+
* the XContentLocation of the field
129+
* @param fieldName
130+
* the field name to match against this {@link ParseField}
131+
* @param deprecationHandler called if {@code fieldName} is deprecated
132+
* @return true if <code>fieldName</code> matches any of the acceptable
133+
* names for this {@link ParseField}.
134+
*/
135+
public boolean match(String parserName, Supplier<XContentLocation> location, String fieldName, DeprecationHandler deprecationHandler) {
118136
Objects.requireNonNull(fieldName, "fieldName cannot be null");
119137
// if this parse field has not been completely deprecated then try to
120138
// match the preferred name
@@ -127,11 +145,11 @@ public boolean match(String fieldName, DeprecationHandler deprecationHandler) {
127145
for (String depName : deprecatedNames) {
128146
if (fieldName.equals(depName)) {
129147
if (fullyDeprecated) {
130-
deprecationHandler.usedDeprecatedField(fieldName);
148+
deprecationHandler.usedDeprecatedField(parserName, location, fieldName);
131149
} else if (allReplacedWith == null) {
132-
deprecationHandler.usedDeprecatedName(fieldName, name);
150+
deprecationHandler.usedDeprecatedName(parserName, location, fieldName, name);
133151
} else {
134-
deprecationHandler.usedDeprecatedField(fieldName, allReplacedWith);
152+
deprecationHandler.usedDeprecatedField(parserName, location, fieldName, allReplacedWith);
135153
}
136154
return true;
137155
}

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/DeprecationHandler.java

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.elasticsearch.common.xcontent;
2121

22+
import java.util.function.Supplier;
23+
2224
/**
2325
* Callback for notifying the creator of the {@link XContentParser} that
2426
* parsing hit a deprecated field.
@@ -32,20 +34,35 @@ public interface DeprecationHandler {
3234
*/
3335
DeprecationHandler THROW_UNSUPPORTED_OPERATION = new DeprecationHandler() {
3436
@Override
35-
public void usedDeprecatedField(String usedName, String replacedWith) {
36-
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
37-
+ usedName + "] which is a deprecated name for [" + replacedWith + "]");
37+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName, String replacedWith) {
38+
if (parserName != null) {
39+
throw new UnsupportedOperationException("deprecated fields not supported in [" + parserName + "] but got ["
40+
+ usedName + "] at [" + location.get() + "] which is a deprecated name for [" + replacedWith + "]");
41+
} else {
42+
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
43+
+ usedName + "] which is a deprecated name for [" + replacedWith + "]");
44+
}
3845
}
3946
@Override
40-
public void usedDeprecatedName(String usedName, String modernName) {
41-
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
42-
+ usedName + "] which has been replaced with [" + modernName + "]");
47+
public void usedDeprecatedName(String parserName, Supplier<XContentLocation> location, String usedName, String modernName) {
48+
if (parserName != null) {
49+
throw new UnsupportedOperationException("deprecated fields not supported in [" + parserName + "] but got ["
50+
+ usedName + "] at [" + location.get() + "] which has been replaced with [" + modernName + "]");
51+
} else {
52+
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
53+
+ usedName + "] which has been replaced with [" + modernName + "]");
54+
}
4355
}
4456

4557
@Override
46-
public void usedDeprecatedField(String usedName) {
47-
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
48-
+ usedName + "] which has been deprecated entirely");
58+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName) {
59+
if (parserName != null) {
60+
throw new UnsupportedOperationException("deprecated fields not supported in [" + parserName + "] but got ["
61+
+ usedName + "] at [" + location.get() + "] which has been deprecated entirely");
62+
} else {
63+
throw new UnsupportedOperationException("deprecated fields not supported here but got ["
64+
+ usedName + "] which has been deprecated entirely");
65+
}
4966
}
5067
};
5168

@@ -54,17 +71,17 @@ public void usedDeprecatedField(String usedName) {
5471
*/
5572
DeprecationHandler IGNORE_DEPRECATIONS = new DeprecationHandler() {
5673
@Override
57-
public void usedDeprecatedName(String usedName, String modernName) {
74+
public void usedDeprecatedName(String parserName, Supplier<XContentLocation> location, String usedName, String modernName) {
5875

5976
}
6077

6178
@Override
62-
public void usedDeprecatedField(String usedName, String replacedWith) {
79+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName, String replacedWith) {
6380

6481
}
6582

6683
@Override
67-
public void usedDeprecatedField(String usedName) {
84+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName) {
6885

6986
}
7087
};
@@ -74,20 +91,21 @@ public void usedDeprecatedField(String usedName) {
7491
* @param usedName the provided field name
7592
* @param modernName the modern name for the field
7693
*/
77-
void usedDeprecatedName(String usedName, String modernName);
94+
void usedDeprecatedName(String parserName, Supplier<XContentLocation> location, String usedName, String modernName);
7895

7996
/**
8097
* Called when the provided field name matches the current field but the entire
8198
* field has been marked as deprecated and another field should be used
8299
* @param usedName the provided field name
83100
* @param replacedWith the name of the field that replaced this field
84101
*/
85-
void usedDeprecatedField(String usedName, String replacedWith);
102+
void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName, String replacedWith);
86103

87104
/**
88105
* Called when the provided field name matches the current field but the entire
89106
* field has been marked as deprecated with no replacement
90107
* @param usedName the provided field name
91108
*/
92-
void usedDeprecatedField(String usedName);
109+
void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName);
110+
93111
}

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ private class FieldParser {
571571
}
572572

573573
void assertSupports(String parserName, XContentParser parser, String currentFieldName) {
574-
if (parseField.match(currentFieldName, parser.getDeprecationHandler()) == false) {
574+
if (parseField.match(parserName, parser::getTokenLocation, currentFieldName, parser.getDeprecationHandler()) == false) {
575575
throw new XContentParseException(parser.getTokenLocation(),
576576
"[" + parserName + "] parsefield doesn't accept: " + currentFieldName);
577577
}

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentLocation.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
* position of a parsing error to end users and consequently have line and
2626
* column numbers starting from 1.
2727
*/
28-
public class XContentLocation {
28+
public final class XContentLocation {
29+
30+
public static final XContentLocation UNKNOWN = new XContentLocation(-1, -1);
31+
2932
public final int lineNumber;
3033
public final int columnNumber;
3134

libs/x-content/src/test/java/org/elasticsearch/common/ParseFieldTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ public void testDeprecatedWithNoReplacement() {
6666
ParseField field = new ParseField(name).withDeprecation(alternatives).withAllDeprecated();
6767
assertFalse(field.match("not a field name", LoggingDeprecationHandler.INSTANCE));
6868
assertTrue(field.match("dep", LoggingDeprecationHandler.INSTANCE));
69-
assertWarnings("Deprecated field [dep] used, which has been removed entirely");
69+
assertWarnings("Deprecated field [dep] used, this field is unused and will be removed entirely");
7070
assertTrue(field.match("old_dep", LoggingDeprecationHandler.INSTANCE));
71-
assertWarnings("Deprecated field [old_dep] used, which has been removed entirely");
71+
assertWarnings("Deprecated field [old_dep] used, this field is unused and will be removed entirely");
7272
assertTrue(field.match("new_dep", LoggingDeprecationHandler.INSTANCE));
73-
assertWarnings("Deprecated field [new_dep] used, which has been removed entirely");
73+
assertWarnings("Deprecated field [new_dep] used, this field is unused and will be removed entirely");
7474

7575

7676
}

libs/x-content/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class TestStruct {
223223
objectParser.declareField((i, v, c) -> v.test = i.text(), new ParseField("test", "old_test"), ObjectParser.ValueType.STRING);
224224
objectParser.parse(parser, s, null);
225225
assertEquals("foo", s.test);
226-
assertWarnings("Deprecated field [old_test] used, expected [test] instead");
226+
assertWarnings(false, "[foo][1:15] Deprecated field [old_test] used, expected [test] instead");
227227
}
228228

229229
public void testFailOnValueType() throws IOException {

qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public void testConcurrentDeprecationLogger() throws IOException, UserException,
164164
*/
165165
final List<String> warnings = threadContext.getResponseHeaders().get("Warning");
166166
final Set<String> actualWarningValues =
167-
warnings.stream().map(DeprecationLogger::extractWarningValueFromWarningHeader).collect(Collectors.toSet());
167+
warnings.stream().map(s -> DeprecationLogger.extractWarningValueFromWarningHeader(s, true))
168+
.collect(Collectors.toSet());
168169
for (int j = 0; j < 128; j++) {
169170
assertThat(
170171
actualWarningValues,

qa/smoke-test-http/src/test/java/org/elasticsearch/http/DeprecationHttpIT.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ private void doTestDeprecationWarningsAppearInHeaders() throws IOException {
187187
assertThat(deprecatedWarning, matches(WARNING_HEADER_PATTERN.pattern()));
188188
}
189189
final List<String> actualWarningValues =
190-
deprecatedWarnings.stream().map(DeprecationLogger::extractWarningValueFromWarningHeader).collect(Collectors.toList());
190+
deprecatedWarnings.stream().map(s -> DeprecationLogger.extractWarningValueFromWarningHeader(s, true))
191+
.collect(Collectors.toList());
191192
for (Matcher<String> headerMatcher : headerMatchers) {
192193
assertThat(actualWarningValues, hasItem(headerMatcher));
193194
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,16 @@ public void deprecatedAndMaybeLog(final String key, final String msg, final Obje
174174
"GMT" + // GMT
175175
"\")?"); // closing quote (optional, since an older version can still send a warn-date)
176176

177+
public static final Pattern WARNING_XCONTENT_LOCATION_PATTERN = Pattern.compile("^\\[.*?]\\[-?\\d+:-?\\d+] ");
178+
177179
/**
178180
* Extracts the warning value from the value of a warning header that is formatted according to RFC 7234. That is, given a string
179181
* {@code 299 Elasticsearch-6.0.0 "warning value"}, the return value of this method would be {@code warning value}.
180182
*
181183
* @param s the value of a warning header formatted according to RFC 7234.
182184
* @return the extracted warning value
183185
*/
184-
public static String extractWarningValueFromWarningHeader(final String s) {
186+
public static String extractWarningValueFromWarningHeader(final String s, boolean stripXContentPosition) {
185187
/*
186188
* We know the exact format of the warning header, so to extract the warning value we can skip forward from the front to the first
187189
* quote and we know the last quote is at the end of the string
@@ -196,8 +198,14 @@ public static String extractWarningValueFromWarningHeader(final String s) {
196198
*/
197199
final int firstQuote = s.indexOf('\"');
198200
final int lastQuote = s.length() - 1;
199-
final String warningValue = s.substring(firstQuote + 1, lastQuote);
201+
String warningValue = s.substring(firstQuote + 1, lastQuote);
200202
assert assertWarningValue(s, warningValue);
203+
if (stripXContentPosition) {
204+
Matcher matcher = WARNING_XCONTENT_LOCATION_PATTERN.matcher(warningValue);
205+
if (matcher.find()) {
206+
warningValue = warningValue.substring(matcher.end());
207+
}
208+
}
201209
return warningValue;
202210
}
203211

@@ -232,7 +240,7 @@ void deprecated(final Set<ThreadContext> threadContexts, final String message, f
232240
final String formattedMessage = LoggerMessageFormat.format(message, params);
233241
final String warningHeaderValue = formatWarning(formattedMessage);
234242
assert WARNING_HEADER_PATTERN.matcher(warningHeaderValue).matches();
235-
assert extractWarningValueFromWarningHeader(warningHeaderValue).equals(escapeAndEncode(formattedMessage));
243+
assert extractWarningValueFromWarningHeader(warningHeaderValue, false).equals(escapeAndEncode(formattedMessage));
236244
while (iterator.hasNext()) {
237245
try {
238246
final ThreadContext next = iterator.next();

server/src/main/java/org/elasticsearch/common/xcontent/LoggingDeprecationHandler.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.elasticsearch.common.ParseField;
2424
import org.elasticsearch.common.logging.DeprecationLogger;
2525

26+
import java.util.function.Supplier;
27+
2628
/**
2729
* Logs deprecations to the {@link DeprecationLogger}.
2830
* <p>
@@ -49,17 +51,23 @@ private LoggingDeprecationHandler() {
4951
}
5052

5153
@Override
52-
public void usedDeprecatedName(String usedName, String modernName) {
53-
deprecationLogger.deprecated("Deprecated field [{}] used, expected [{}] instead", usedName, modernName);
54+
public void usedDeprecatedName(String parserName, Supplier<XContentLocation> location, String usedName, String modernName) {
55+
String prefix = parserName == null ? "" : "[" + parserName + "][" + location.get() + "] ";
56+
deprecationLogger.deprecated("{}Deprecated field [{}] used, expected [{}] instead",
57+
prefix, usedName, modernName);
5458
}
5559

5660
@Override
57-
public void usedDeprecatedField(String usedName, String replacedWith) {
58-
deprecationLogger.deprecated("Deprecated field [{}] used, replaced by [{}]", usedName, replacedWith);
61+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName, String replacedWith) {
62+
String prefix = parserName == null ? "" : "[" + parserName + "][" + location.get() + "] ";
63+
deprecationLogger.deprecated("{}Deprecated field [{}] used, replaced by [{}]",
64+
prefix, usedName, replacedWith);
5965
}
6066

6167
@Override
62-
public void usedDeprecatedField(String usedName) {
63-
deprecationLogger.deprecated("Deprecated field [{}] used, which has been removed entirely", usedName);
68+
public void usedDeprecatedField(String parserName, Supplier<XContentLocation> location, String usedName) {
69+
String prefix = parserName == null ? "" : "[" + parserName + "][" + location.get() + "] ";
70+
deprecationLogger.deprecated("{}Deprecated field [{}] used, this field is unused and will be removed entirely",
71+
prefix, usedName);
6472
}
6573
}

server/src/test/java/org/elasticsearch/common/logging/DeprecationLoggerTests.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,18 @@ public void testFailsWhenRemovingUnknownThreadContext() throws IOException {
216216
expectThrows(IllegalStateException.class, () -> DeprecationLogger.removeThreadContext(threadContext));
217217
}
218218

219-
public void testWarningValueFromWarningHeader() throws InterruptedException {
219+
public void testWarningValueFromWarningHeader() {
220220
final String s = randomAlphaOfLength(16);
221221
final String first = DeprecationLogger.formatWarning(s);
222-
assertThat(DeprecationLogger.extractWarningValueFromWarningHeader(first), equalTo(s));
222+
assertThat(DeprecationLogger.extractWarningValueFromWarningHeader(first, false), equalTo(s));
223+
224+
final String withPos = "[context][1:11] Blah blah blah";
225+
final String formatted = DeprecationLogger.formatWarning(withPos);
226+
assertThat(DeprecationLogger.extractWarningValueFromWarningHeader(formatted, true), equalTo("Blah blah blah"));
227+
228+
final String withNegativePos = "[context][-1:-1] Blah blah blah";
229+
assertThat(DeprecationLogger.extractWarningValueFromWarningHeader(DeprecationLogger.formatWarning(withNegativePos), true),
230+
equalTo("Blah blah blah"));
223231
}
224232

225233
public void testEscapeBackslashesAndQuotes() {

server/src/test/java/org/elasticsearch/common/util/concurrent/ThreadContextTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,11 @@ public void testResponseHeaders() {
208208
}
209209

210210
final String value = DeprecationLogger.formatWarning("qux");
211-
threadContext.addResponseHeader("baz", value, DeprecationLogger::extractWarningValueFromWarningHeader);
211+
threadContext.addResponseHeader("baz", value, s -> DeprecationLogger.extractWarningValueFromWarningHeader(s, false));
212212
// pretend that another thread created the same response at a different time
213213
if (randomBoolean()) {
214214
final String duplicateValue = DeprecationLogger.formatWarning("qux");
215-
threadContext.addResponseHeader("baz", duplicateValue, DeprecationLogger::extractWarningValueFromWarningHeader);
215+
threadContext.addResponseHeader("baz", duplicateValue, s -> DeprecationLogger.extractWarningValueFromWarningHeader(s, false));
216216
}
217217

218218
threadContext.addResponseHeader("Warning", "One is the loneliest number");

test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,14 +422,19 @@ protected final void assertSettingDeprecationsAndWarnings(final String[] setting
422422
}
423423

424424
protected final void assertWarnings(String... expectedWarnings) {
425+
assertWarnings(true, expectedWarnings);
426+
}
427+
428+
protected final void assertWarnings(boolean stripXContentPosition, String... expectedWarnings) {
425429
if (enableWarningsCheck() == false) {
426430
throw new IllegalStateException("unable to check warning headers if the test is not set to do so");
427431
}
428432
try {
429433
final List<String> actualWarnings = threadContext.getResponseHeaders().get("Warning");
430434
assertNotNull("no warnings, expected: " + Arrays.asList(expectedWarnings), actualWarnings);
431435
final Set<String> actualWarningValues =
432-
actualWarnings.stream().map(DeprecationLogger::extractWarningValueFromWarningHeader).collect(Collectors.toSet());
436+
actualWarnings.stream().map(s -> DeprecationLogger.extractWarningValueFromWarningHeader(s, stripXContentPosition))
437+
.collect(Collectors.toSet());
433438
for (String msg : expectedWarnings) {
434439
assertThat(actualWarningValues, hasItem(DeprecationLogger.escapeAndEncode(msg)));
435440
}

test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/DoSection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ void checkWarningHeaders(final List<String> warningHeaders, final Version master
331331
final Matcher matcher = WARNING_HEADER_PATTERN.matcher(header);
332332
final boolean matches = matcher.matches();
333333
if (matches) {
334-
final String message = matcher.group(1);
334+
final String message = DeprecationLogger.extractWarningValueFromWarningHeader(header, true);
335335
if (allowed.contains(message)) {
336336
continue;
337337
}

0 commit comments

Comments
 (0)