Skip to content

Exposing the ability to log deprecated settings at non-critical level #79107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,14 @@ public HttpEntity getEntity() {

/**
* Tests if a string matches the RFC 7234 specification for warning headers.
* This assumes that the warn code is always 299 and the warn agent is always
* Elasticsearch.
* This assumes that the warn code is always 299 or 300 and the warn agent is
* always Elasticsearch.
*
* @param s the value of a warning header formatted according to RFC 7234
* @return {@code true} if the input string matches the specification
*/
private static boolean matchWarningHeaderPatternByPrefix(final String s) {
return s.startsWith("299 Elasticsearch-");
return s.startsWith("299 Elasticsearch-") || s.startsWith("300 Elasticsearch-");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

package org.elasticsearch.common.logging;

import org.apache.logging.log4j.Level;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.common.util.concurrent.ThreadContext;
Expand All @@ -32,10 +33,11 @@
public class HeaderWarning {
/**
* Regular expression to test if a string matches the RFC7234 specification for warning headers. This pattern assumes that the warn code
* is always 299. Further, this pattern assumes that the warn agent represents a version of Elasticsearch including the build hash.
* is always 299 or 300. Further, this pattern assumes that the warn agent represents a version of Elasticsearch including the build
* hash.
*/
public static final Pattern WARNING_HEADER_PATTERN = Pattern.compile(
"299 " + // warn code
"(?:299|300) " + // log level code
"Elasticsearch-" + // warn agent
"\\d+\\.\\d+\\.\\d+(?:-(?:alpha|beta|rc)\\d+)?(?:-SNAPSHOT)?-" + // warn agent
"(?:[a-f0-9]{7}(?:[a-f0-9]{33})?|unknown) " + // warn agent
Expand All @@ -53,15 +55,16 @@ public class HeaderWarning {

/*
* RFC7234 specifies the warning format as warn-code <space> warn-agent <space> "warn-text" [<space> "warn-date"]. Here, warn-code is a
* three-digit number with various standard warn codes specified. The warn code 299 is apt for our purposes as it represents a
* miscellaneous persistent warning (can be presented to a human, or logged, and must not be removed by a cache). The warn-agent is an
* arbitrary token; here we use the Elasticsearch version and build hash. The warn text must be quoted. The warn-date is an optional
* quoted field that can be in a variety of specified date formats; here we use RFC 1123 format.
* three-digit number with various standard warn codes specified, and is left off of this static prefix so that it can be added based
* on the log level received. The warn code will be either 299 or 300 at runtime, which are apt for our purposes as
* they represent miscellaneous persistent warnings (can be presented to a human, or logged, and must not be removed by a cache).
* The warn-agent is an arbitrary token; here we use the Elasticsearch version and build hash. The warn text must be quoted. The
* warn-date is an optional quoted field that can be in a variety of specified date formats; here we use RFC 1123 format.
*/
private static final String WARNING_PREFIX =
String.format(
Locale.ROOT,
"299 Elasticsearch-%s%s-%s",
" Elasticsearch-%s%s-%s",
Version.CURRENT.toString(),
Build.CURRENT.isSnapshot() ? "-SNAPSHOT" : "",
Build.CURRENT.hash());
Expand Down Expand Up @@ -193,10 +196,14 @@ private static boolean assertWarningValue(final String s, final String warningVa
* @return a warning value formatted according to RFC 7234
*/
public static String formatWarning(final String s) {
return formatWarning(DeprecationLogger.CRITICAL, s);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another method seems unnecessary, the only non-test use is in this class, let's just update the few tests using this to pass the level?

}

private static String formatWarning(final Level level, final String s) {
// Assume that the common scenario won't have a string to escape and encode.
int length = WARNING_PREFIX.length() + s.length() + 3;
int length = WARNING_PREFIX.length() + s.length() + 6;
final StringBuilder sb = new StringBuilder(length);
sb.append(WARNING_PREFIX).append(" \"").append(escapeAndEncode(s)).append("\"");
sb.append(level.intLevel() + WARNING_PREFIX).append(" \"").append(escapeAndEncode(s)).append("\"");
return sb.toString();
}

Expand Down Expand Up @@ -311,15 +318,24 @@ public static String getXOpaqueId() {
}

public static void addWarning(String message, Object... params) {
addWarning(THREAD_CONTEXT, message, params);
addWarning(THREAD_CONTEXT, DeprecationLogger.CRITICAL, message, params);
}

public static void addWarning(Level level, String message, Object... params) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding another public method, can you please update the existing uses? It looks like there are ~12 calls to the existing method, and most are in tests, it should be easy to update with eg IntelliJ refactoring tools to add a parameter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I went back and forth on this. I actually did this and backed it out in an attempt to make this PR smaller. I'll consolidate it into one method.

addWarning(THREAD_CONTEXT, level, message, params);
}

// package scope for testing
static void addWarning(Set<ThreadContext> threadContexts, String message, Object... params) {
addWarning(threadContexts, DeprecationLogger.CRITICAL, message, params);
}

// package scope for testing
static void addWarning(Set<ThreadContext> threadContexts, Level level, String message, Object... params) {
final Iterator<ThreadContext> iterator = threadContexts.iterator();
if (iterator.hasNext()) {
final String formattedMessage = LoggerMessageFormat.format(message, params);
final String warningHeaderValue = formatWarning(formattedMessage);
final String warningHeaderValue = formatWarning(level, formattedMessage);
assert WARNING_HEADER_PATTERN.matcher(warningHeaderValue).matches();
assert extractWarningValueFromWarningHeader(warningHeaderValue, false)
.equals(escapeAndEncode(formattedMessage));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ public void append(LogEvent event) {
String messagePattern = esLogMessage.getMessagePattern();
Object[] arguments = esLogMessage.getArguments();

HeaderWarning.addWarning(messagePattern, arguments);
HeaderWarning.addWarning(event.getLevel(), messagePattern, arguments);
} else {
final String formattedMessage = event.getMessage().getFormattedMessage();
HeaderWarning.addWarning(formattedMessage);
HeaderWarning.addWarning(event.getLevel(), formattedMessage);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ public enum Property {
Final,

/**
* mark this setting as deprecated
* mark this setting as deprecated (critical level)
*/
Deprecated,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice, as a followup (since it will touch so many files), to rename this.


/**
* mark this setting as deprecated (warning level)
*/
DeprecatedWarning,

/**
* Node scope
*/
Expand Down Expand Up @@ -371,7 +376,11 @@ public boolean hasIndexScope() {
* Returns <code>true</code> if this setting is deprecated, otherwise <code>false</code>
*/
public boolean isDeprecated() {
return properties.contains(Property.Deprecated);
return properties.contains(Property.Deprecated) || properties.contains(Property.DeprecatedWarning);
}

private boolean isDeprecatedWarningOnly() {
return properties.contains(Property.DeprecatedWarning) && properties.contains(Property.Deprecated) == false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should assert somewhere that both properties do not exist, they should be mutually exclusive. Then this method isn't needed, just check for contains the warning version?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this to the Setting constructor, and updated this method..

}

/**
Expand Down Expand Up @@ -552,10 +561,13 @@ void checkDeprecation(Settings settings) {
if (this.isDeprecated() && this.exists(settings)) {
// It would be convenient to show its replacement key, but replacement is often not so simple
final String key = getKey();
Settings.DeprecationLoggerHolder.deprecationLogger
.critical(DeprecationCategory.SETTINGS, key,
"[{}] setting was deprecated in Elasticsearch and will be removed in a future release! "
+ "See the breaking changes documentation for the next major version.", key);
String message = "[{}] setting was deprecated in Elasticsearch and will be removed in a future release! "
+ "See the breaking changes documentation for the next major version.";
if (this.isDeprecatedWarningOnly()) {
Settings.DeprecationLoggerHolder.deprecationLogger.warn(DeprecationCategory.SETTINGS, key, message, key);
} else {
Settings.DeprecationLoggerHolder.deprecationLogger.critical(DeprecationCategory.SETTINGS, key, message, key);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
package org.elasticsearch.common.logging;

import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator;

import org.apache.logging.log4j.Level;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.ESTestCase;
Expand Down Expand Up @@ -285,4 +287,22 @@ private String range(int lowerInclusive, int upperInclusive) {
.toString();
}

public void testAddWarningNonDefaultLogLevel() {
final int maxWarningHeaderCount = 2;
Settings settings = Settings.builder()
.put("http.max_warning_header_count", maxWarningHeaderCount)
.build();
ThreadContext threadContext = new ThreadContext(settings);
final Set<ThreadContext> threadContexts = Collections.singleton(threadContext);
HeaderWarning.addWarning(threadContexts, Level.WARN, "A simple message 1");
final Map<String, List<String>> responseHeaders = threadContext.getResponseHeaders();

assertThat(responseHeaders.size(), equalTo(1));
final List<String> responses = responseHeaders.get("Warning");
assertThat(responses, hasSize(1));
assertThat(responses.get(0), warningValueMatcher);
assertThat(responses.get(0), containsString("\"A simple message 1\""));
assertThat(responses.get(0), containsString(Integer.toString(Level.WARN.intLevel())));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.AbstractScopedSettings.SettingUpdater;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.elasticsearch.test.ESTestCase;
Expand Down Expand Up @@ -1258,4 +1258,25 @@ public void testDynamicTest() {
assertTrue(setting.isDynamic());
assertEquals(setting.isOperatorOnly(), property == Property.OperatorDynamic);
}

public void testCheckForDeprecation() {
final String criticalSettingName = "foo.bar";
final String warningSettingName = "foo.foo";
final String settingValue = "blat";
final Setting<String> undeprecatedSetting1 = Setting.simpleString(criticalSettingName, settingValue);
final Setting<String> undeprecatedSetting2 = Setting.simpleString(warningSettingName, settingValue);
final Settings settings = Settings.builder()
.put(criticalSettingName, settingValue)
.put(warningSettingName, settingValue).build();
undeprecatedSetting1.checkDeprecation(settings);
undeprecatedSetting2.checkDeprecation(settings);
ensureNoWarnings();
final Setting<String> criticalDeprecatedSetting = Setting.simpleString(criticalSettingName, settingValue, Property.Deprecated);
criticalDeprecatedSetting.checkDeprecation(settings);
assertSettingDeprecationsAndWarningsIncludingLevel(new Setting<?>[]{ criticalDeprecatedSetting });
final Setting<String> deprecatedSettingWarningOnly =
Setting.simpleString(warningSettingName, settingValue, Property.DeprecatedWarning);
deprecatedSettingWarningOnly.checkDeprecation(settings);
assertSettingDeprecationsAndWarningsIncludingLevel(new Setting<?>[]{ deprecatedSettingWarningOnly });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
import org.apache.lucene.util.TestRuleMarkFailure;
import org.apache.lucene.util.TimeUnits;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.bootstrap.BootstrapForTesting;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.core.CheckedRunnable;
import org.elasticsearch.core.RestApiVersion;
import org.elasticsearch.core.SuppressForbidden;
Expand Down Expand Up @@ -458,11 +460,35 @@ protected final void assertSettingDeprecationsAndWarnings(final String[] setting
.toArray(String[]::new));
}

protected final void assertSettingDeprecationsAndWarningsIncludingLevel(final Setting<?>[] settings, final String... warnings) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are only 2 uses of the method above which takes string setting names. Let's collapse all 3 of these methods into one, this is unwieldy having 3 methods of the same name, with no clear distinction of when to call each.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another one I'd gone back and forth on. I've merged them all into one method now.

assertWarnings(
true,
true,
Stream.concat(
Arrays
.stream(settings)
.map(setting -> String.format(
Locale.ROOT, "%s Elasticsearch-%s%s-%s \"[%s] setting was deprecated in Elasticsearch and will be " +
"removed in a future release! See the breaking changes documentation for the next major version.\"",
setting.getProperties().contains(Setting.Property.Deprecated) ? DeprecationLogger.CRITICAL.intLevel() :
Level.WARN.intLevel(),
Version.CURRENT.toString(),
Build.CURRENT.isSnapshot() ? "-SNAPSHOT" : "",
Build.CURRENT.hash(),
setting.getKey())),
Arrays.stream(warnings))
.toArray(String[]::new));
}

protected final void assertWarnings(String... expectedWarnings) {
assertWarnings(true, expectedWarnings);
}

protected final void assertWarnings(boolean stripXContentPosition, String... expectedWarnings) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 4 uses of this method. Let's update those to call the updated signature rather than adding yet another variant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

assertWarnings(stripXContentPosition, false, expectedWarnings);
}

protected final void assertWarnings(boolean stripXContentPosition, boolean includeLevelCheck, String... expectedWarnings) {
if (enableWarningsCheck() == false) {
throw new IllegalStateException("unable to check warning headers if the test is not set to do so");
}
Expand All @@ -473,7 +499,9 @@ protected final void assertWarnings(boolean stripXContentPosition, String... exp
} else {
assertNotNull("no warnings, expected: " + Arrays.asList(expectedWarnings), actualWarnings);
final Set<String> actualWarningValues =
actualWarnings.stream().map(s -> HeaderWarning.extractWarningValueFromWarningHeader(s, stripXContentPosition))
actualWarnings.stream()
.map(s -> includeLevelCheck ? HeaderWarning.escapeAndEncode(s) :
HeaderWarning.extractWarningValueFromWarningHeader(s, stripXContentPosition))
.collect(Collectors.toSet());
for (String msg : expectedWarnings) {
assertThat(actualWarningValues, hasItem(HeaderWarning.escapeAndEncode(msg)));
Expand Down