diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java index 1edeb072c80..f6a3d0fa825 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java @@ -76,12 +76,18 @@ public > T getEnum(String key, Class enumType, T defaultVal public String getString(String key, String defaultValue, String... aliases) { for (ConfigProvider.Source source : sources) { - String value = source.get(key, aliases); - if (value != null) { + try { + String value = source.get(key, aliases); + if (value != null) { + if (collectConfig) { + ConfigCollector.get().put(key, value, source.origin()); + } + return value; + } + } catch (ConfigSourceException e) { if (collectConfig) { - ConfigCollector.get().put(key, value, source.origin()); + ConfigCollector.get().put(key, e.getRawValue(), source.origin()); } - return value; } } if (collectConfig) { @@ -96,12 +102,18 @@ public String getString(String key, String defaultValue, String... aliases) { */ public String getStringNotEmpty(String key, String defaultValue, String... aliases) { for (ConfigProvider.Source source : sources) { - String value = source.get(key, aliases); - if (value != null && !value.trim().isEmpty()) { + try { + String value = source.get(key, aliases); + if (value != null && !value.trim().isEmpty()) { + if (collectConfig) { + ConfigCollector.get().put(key, value, source.origin()); + } + return value; + } + } catch (ConfigSourceException e) { if (collectConfig) { - ConfigCollector.get().put(key, value, source.origin()); + ConfigCollector.get().put(key, e.getRawValue(), source.origin()); } - return value; } } if (collectConfig) { @@ -119,13 +131,18 @@ public String getStringExcludingSource( if (excludedSource.isAssignableFrom(source.getClass())) { continue; } - - String value = source.get(key, aliases); - if (value != null) { + try { + String value = source.get(key, aliases); + if (value != null) { + if (collectConfig) { + ConfigCollector.get().put(key, value, source.origin()); + } + return value; + } + } catch (ConfigSourceException e) { if (collectConfig) { - ConfigCollector.get().put(key, value, source.origin()); + ConfigCollector.get().put(key, e.getRawValue(), source.origin()); } - return value; } } if (collectConfig) { @@ -202,6 +219,10 @@ private T get(String key, T defaultValue, Class type, String... aliases) } return value; } + } catch (ConfigSourceException e) { + if (collectConfig) { + ConfigCollector.get().put(key, e.getRawValue(), source.origin()); + } } catch (NumberFormatException ex) { // continue } @@ -252,12 +273,18 @@ public Map getMergedMap(String key, String... aliases) { // https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html // We reverse iterate to allow overrides for (int i = sources.length - 1; 0 <= i; i--) { - String value = sources[i].get(key, aliases); - Map parsedMap = ConfigConverter.parseMap(value, key); - if (!parsedMap.isEmpty()) { - origin = sources[i].origin(); + try { + String value = sources[i].get(key, aliases); + Map parsedMap = ConfigConverter.parseMap(value, key); + if (!parsedMap.isEmpty()) { + origin = sources[i].origin(); + } + merged.putAll(parsedMap); + } catch (ConfigSourceException e) { + if (collectConfig) { + ConfigCollector.get().put(key, e.getRawValue(), sources[i].origin()); + } } - merged.putAll(parsedMap); } if (collectConfig) { ConfigCollector.get().put(key, merged, origin); @@ -273,13 +300,19 @@ public Map getMergedTagsMap(String key, String... aliases) { // https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html // We reverse iterate to allow overrides for (int i = sources.length - 1; 0 <= i; i--) { - String value = sources[i].get(key, aliases); - Map parsedMap = - ConfigConverter.parseTraceTagsMap(value, ':', Arrays.asList(',', ' ')); - if (!parsedMap.isEmpty()) { - origin = sources[i].origin(); + try { + String value = sources[i].get(key, aliases); + Map parsedMap = + ConfigConverter.parseTraceTagsMap(value, ':', Arrays.asList(',', ' ')); + if (!parsedMap.isEmpty()) { + origin = sources[i].origin(); + } + merged.putAll(parsedMap); + } catch (ConfigSourceException e) { + if (collectConfig) { + ConfigCollector.get().put(key, e.getRawValue(), sources[i].origin()); + } } - merged.putAll(parsedMap); } if (collectConfig) { ConfigCollector.get().put(key, merged, origin); @@ -295,12 +328,18 @@ public Map getOrderedMap(String key) { // https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html // We reverse iterate to allow overrides for (int i = sources.length - 1; 0 <= i; i--) { - String value = sources[i].get(key); - Map parsedMap = ConfigConverter.parseOrderedMap(value, key); - if (!parsedMap.isEmpty()) { - origin = sources[i].origin(); + try { + String value = sources[i].get(key); + Map parsedMap = ConfigConverter.parseOrderedMap(value, key); + if (!parsedMap.isEmpty()) { + origin = sources[i].origin(); + } + merged.putAll(parsedMap); + } catch (ConfigSourceException e) { + if (collectConfig) { + ConfigCollector.get().put(key, e.getRawValue(), sources[i].origin()); + } } - merged.putAll(parsedMap); } if (collectConfig) { ConfigCollector.get().put(key, merged, origin); @@ -318,13 +357,20 @@ public Map getMergedMapWithOptionalMappings( // We reverse iterate to allow overrides for (String key : keys) { for (int i = sources.length - 1; 0 <= i; i--) { - String value = sources[i].get(key); - Map parsedMap = - ConfigConverter.parseMapWithOptionalMappings(value, key, defaultPrefix, lowercaseKeys); - if (!parsedMap.isEmpty()) { - origin = sources[i].origin(); + try { + String value = sources[i].get(key); + Map parsedMap = + ConfigConverter.parseMapWithOptionalMappings( + value, key, defaultPrefix, lowercaseKeys); + if (!parsedMap.isEmpty()) { + origin = sources[i].origin(); + } + merged.putAll(parsedMap); + } catch (ConfigSourceException e) { + if (collectConfig) { + ConfigCollector.get().put(key, e.getRawValue(), sources[i].origin()); + } } - merged.putAll(parsedMap); } if (collectConfig) { ConfigCollector.get().put(key, merged, origin); @@ -500,7 +546,7 @@ private static Properties loadConfigurationFile(ConfigProvider configProvider) { } public abstract static class Source { - public final String get(String key, String... aliases) { + public final String get(String key, String... aliases) throws ConfigSourceException { String value = get(key); if (value != null) { return value; @@ -514,7 +560,7 @@ public final String get(String key, String... aliases) { return null; } - protected abstract String get(String key); + protected abstract String get(String key) throws ConfigSourceException; public abstract ConfigOrigin origin(); } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigSourceException.java b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigSourceException.java new file mode 100644 index 00000000000..2339f7a8a4c --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/bootstrap/config/provider/ConfigSourceException.java @@ -0,0 +1,23 @@ +package datadog.trace.bootstrap.config.provider; + +/** + * Exception thrown when a ConfigProvider.Source encounters an error (e.g., parsing, IO, or format + * error) while retrieving a configuration value. + */ +public class ConfigSourceException extends Exception { + private final Object rawValue; + + public ConfigSourceException(String message, Object rawValue, Throwable cause) { + super(message, cause); + this.rawValue = rawValue; + } + + public ConfigSourceException(Object rawValue) { + this.rawValue = rawValue; + } + + /** Returns the raw value that caused the exception, if available. */ + public Object getRawValue() { + return rawValue; + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigProviderTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigProviderTest.groovy index b9783ec59c1..787f1fe6d6a 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigProviderTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/ConfigProviderTest.groovy @@ -1,5 +1,6 @@ package datadog.trace.bootstrap.config.provider +import datadog.trace.api.ConfigOrigin import datadog.trace.test.util.DDSpecification import spock.lang.Shared @@ -45,4 +46,63 @@ class ConfigProviderTest extends DDSpecification { "default" | null | "alias2" | "default" null | "alias1" | "alias2" | "alias1" } + + def "ConfigProvider handles ConfigSourceException gracefully"() { + given: + def throwingSource = new ConfigProvider.Source() { + @Override + protected String get(String key) throws ConfigSourceException { + throw new ConfigSourceException("raw") + } + @Override + ConfigOrigin origin() { + ConfigOrigin.ENV + } + } + // Create a provider with a collector + def provider = new ConfigProvider(true, throwingSource) + + expect: + //Any "get" method should return the default value, if provided + provider.getString("any.key", "default") == "default" + provider.getBoolean("any.key", true) == true + provider.getInteger("any.key", 42) == 42 + provider.getLong("any.key", 123L) == 123L + provider.getFloat("any.key", 1.23f) == 1.23f + provider.getDouble("any.key", 2.34d) == 2.34d + provider.getList("any.key", ["a", "b"]) == ["a", "b"] + provider.getSet("any.key", ["x", "y"] as Set) == ["x", "y"] as Set + } + + def "ConfigProvider skips sources that throw ConfigSourceException and uses next available value"() { + given: + def throwingSource = new ConfigProvider.Source() { + @Override + protected String get(String key) throws ConfigSourceException { + throw new ConfigSourceException("raw") + } + @Override + ConfigOrigin origin() { + ConfigOrigin.ENV + } + } + + def workingSource = new ConfigProvider.Source() { + @Override + protected String get(String key) throws ConfigSourceException { + if (key == "any.key") { + return "fromSecondSource" + } + return null + } + @Override + ConfigOrigin origin() { + ConfigOrigin.JVM_PROP + } + } + def provider = new ConfigProvider(true, throwingSource, workingSource) + + expect: + provider.getString("any.key", "default") == "fromSecondSource" + } }