-
Notifications
You must be signed in to change notification settings - Fork 848
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
Define dedicated file configuration SPI ComponentProvider #6457
Changes from 6 commits
af278ba
9d00503
163dcd4
fccde9c
2ada3e5
f9c28e9
ab07587
8ea3bdd
2ed3468
c6af54c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.exporter.prometheus.internal; | ||
|
||
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; | ||
import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder; | ||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; | ||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; | ||
import io.opentelemetry.sdk.metrics.export.MetricReader; | ||
|
||
/** | ||
* File configuration SPI implementation for {@link PrometheusHttpServer}. | ||
* | ||
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change | ||
* at any time. | ||
*/ | ||
public class PrometheusComponentProvider implements ComponentProvider<MetricReader> { | ||
|
||
@Override | ||
public Class<MetricReader> getType() { | ||
return MetricReader.class; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "prometheus"; | ||
} | ||
|
||
@Override | ||
public MetricReader create(StructuredConfigProperties config) { | ||
PrometheusHttpServerBuilder prometheusBuilder = PrometheusHttpServer.builder(); | ||
|
||
Integer port = config.getInt("port"); | ||
if (port != null) { | ||
prometheusBuilder.setPort(port); | ||
} | ||
String host = config.getString("host"); | ||
if (host != null) { | ||
prometheusBuilder.setHost(host); | ||
} | ||
|
||
return prometheusBuilder.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.opentelemetry.exporter.prometheus.internal.PrometheusComponentProvider |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.sdk.autoconfigure.spi.internal; | ||
|
||
import io.opentelemetry.sdk.trace.export.SpanExporter; | ||
|
||
/** | ||
* Provides configured instances of SDK extension components. {@link ComponentProvider} allows SDK | ||
* extension components which are not part of the core SDK to be referenced in file based | ||
* configuration. | ||
* | ||
* @param <T> the type of the SDK extension component. See {@link #getType()}. | ||
*/ | ||
// TODO (jack-berg): list the specific types which are supported in file configuration | ||
jack-berg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public interface ComponentProvider<T> { | ||
|
||
/** | ||
* The type of SDK extension component. For example, if providing instances of a custom span | ||
* exporter, the type would be {@link SpanExporter}. | ||
*/ | ||
Class<T> getType(); | ||
|
||
/** | ||
* The name of the exporter, to be referenced in configuration files. For example, if providing | ||
* instances of a custom span exporter for the "acme" protocol, the name might be "acme". | ||
* | ||
* <p>This name MUST not be the same as any other component provider name which returns components | ||
* of the same {@link #getType() type}. | ||
*/ | ||
String getName(); | ||
|
||
/** | ||
* Configure an instance of the SDK extension component according to the {@code config}. | ||
* | ||
* @param config the configuration provided where the component is referenced in a configuration | ||
* file. | ||
* @return an instance the SDK extension component | ||
*/ | ||
T create(StructuredConfigProperties config); | ||
jack-berg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.sdk.autoconfigure.spi.internal; | ||
|
||
import static io.opentelemetry.api.internal.ConfigUtil.defaultIfNull; | ||
|
||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; | ||
import java.util.List; | ||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* An interface for accessing structured configuration data. | ||
* | ||
* <p>An instance of {@link StructuredConfigProperties} is equivalent to a <a | ||
* href="https://yaml.org/spec/1.2.2/#3211-nodes">YAML mapping node</a>. It has accessors for | ||
* reading scalar properties, {@link #getStructured(String)} for reading children which are | ||
* themselves mappings, and {@link #getStructuredList(String)} for reading children which are | ||
* sequences of mappings. | ||
*/ | ||
public interface StructuredConfigProperties { | ||
|
||
/** | ||
* Returns a {@link String} configuration property. | ||
* | ||
* @return null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar string | ||
*/ | ||
@Nullable | ||
String getString(String name); | ||
|
||
/** | ||
* Returns a {@link String} configuration property. | ||
* | ||
* @return a {@link String} configuration property or {@code defaultValue} if a property with | ||
* {@code name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar string | ||
*/ | ||
default String getString(String name, String defaultValue) { | ||
return defaultIfNull(getString(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link Boolean} configuration property. Implementations should use the same rules as | ||
* {@link Boolean#parseBoolean(String)} for handling the values. | ||
* | ||
* @return null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar boolean | ||
*/ | ||
@Nullable | ||
Boolean getBoolean(String name); | ||
|
||
/** | ||
* Returns a {@link Boolean} configuration property. | ||
* | ||
* @return a {@link Boolean} configuration property or {@code defaultValue} if a property with | ||
* {@code name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar boolean | ||
*/ | ||
default boolean getBoolean(String name, boolean defaultValue) { | ||
return defaultIfNull(getBoolean(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link Integer} configuration property. | ||
* | ||
* @return null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar integer | ||
*/ | ||
@Nullable | ||
Integer getInt(String name); | ||
|
||
/** | ||
* Returns a {@link Integer} configuration property. | ||
* | ||
* @return a {@link Integer} configuration property or {@code defaultValue} if a property with | ||
* {@code name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar integer | ||
*/ | ||
default int getInt(String name, int defaultValue) { | ||
return defaultIfNull(getInt(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link Long} configuration property. | ||
* | ||
* @return null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar long | ||
*/ | ||
@Nullable | ||
Long getLong(String name); | ||
|
||
/** | ||
* Returns a {@link Long} configuration property. | ||
* | ||
* @return a {@link Long} configuration property or {@code defaultValue} if a property with {@code | ||
* name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar long | ||
*/ | ||
default long getLong(String name, long defaultValue) { | ||
return defaultIfNull(getLong(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link Double} configuration property. | ||
* | ||
* @return null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar double | ||
*/ | ||
@Nullable | ||
Double getDouble(String name); | ||
|
||
/** | ||
* Returns a {@link Double} configuration property. | ||
* | ||
* @return a {@link Double} configuration property or {@code defaultValue} if a property with | ||
* {@code name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid scalar double | ||
*/ | ||
default double getDouble(String name, double defaultValue) { | ||
return defaultIfNull(getDouble(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link List} configuration property. Empty values will be removed. Entries which are | ||
* not strings are converted to their string representation. | ||
* | ||
* @return a {@link List} configuration property, or null if the property has not been configured | ||
* @throws ConfigurationException if the property is not a valid sequence of scalars | ||
*/ | ||
@Nullable | ||
List<String> getScalarList(String name); | ||
jack-berg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Returns a {@link List} configuration property. Entries which are not strings are converted to | ||
* their string representation. | ||
* | ||
* @see ConfigProperties#getList(String name) | ||
* @return a {@link List} configuration property or {@code defaultValue} if a property with {@code | ||
* name} has not been configured | ||
* @throws ConfigurationException if the property is not a valid sequence of scalars | ||
*/ | ||
default List<String> getScalarList(String name, List<String> defaultValue) { | ||
return defaultIfNull(getScalarList(name), defaultValue); | ||
} | ||
|
||
/** | ||
* Returns a {@link StructuredConfigProperties} configuration property. | ||
* | ||
* @return a map-valued configuration property, or {@code null} if {@code name} has not been | ||
* configured | ||
* @throws ConfigurationException if the property is not a mapping | ||
*/ | ||
@Nullable | ||
StructuredConfigProperties getStructured(String name); | ||
|
||
/** | ||
* Returns a list of {@link StructuredConfigProperties} configuration property. | ||
* | ||
* @return a list of map-valued configuration property, or {@code null} if {@code name} has not | ||
* been configured | ||
* @throws ConfigurationException if the property is not a sequence of mappings | ||
*/ | ||
@Nullable | ||
List<StructuredConfigProperties> getStructuredList(String name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,9 @@ | |
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.sdk.OpenTelemetrySdk; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; | ||
import io.opentelemetry.sdk.resources.Resource; | ||
import javax.annotation.Nullable; | ||
import javax.annotation.concurrent.Immutable; | ||
|
||
/** | ||
|
@@ -43,8 +45,12 @@ public static AutoConfiguredOpenTelemetrySdkBuilder builder() { | |
} | ||
|
||
static AutoConfiguredOpenTelemetrySdk create( | ||
OpenTelemetrySdk sdk, Resource resource, ConfigProperties config) { | ||
return new AutoValue_AutoConfiguredOpenTelemetrySdk(sdk, resource, config); | ||
OpenTelemetrySdk sdk, | ||
Resource resource, | ||
@Nullable ConfigProperties config, | ||
@Nullable StructuredConfigProperties structuredConfigProperties) { | ||
return new AutoValue_AutoConfiguredOpenTelemetrySdk( | ||
sdk, resource, config, structuredConfigProperties); | ||
} | ||
|
||
/** | ||
|
@@ -60,8 +66,23 @@ static AutoConfiguredOpenTelemetrySdk create( | |
/** Returns the {@link Resource} that was auto-configured. */ | ||
abstract Resource getResource(); | ||
|
||
/** Returns the {@link ConfigProperties} used for auto-configuration. */ | ||
/** | ||
* Returns the {@link ConfigProperties} used for auto-configuration, or {@code null} if file | ||
* configuration was used. | ||
* | ||
* @see #getStructuredConfig() | ||
*/ | ||
@Nullable | ||
abstract ConfigProperties getConfig(); | ||
|
||
/** | ||
* Returns the {@link StructuredConfigProperties} used for auto-configuration, or {@code null} if | ||
* file configuration was not used. | ||
* | ||
* @see #getConfig() | ||
*/ | ||
@Nullable | ||
abstract StructuredConfigProperties getStructuredConfig(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This new method allows callers to determine if autoconfiguration was driven by file configuration or by env vars / system properties. Either this or If file configuration is used, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is kinda the only unfortunate downside of this approach that I can find thus far. It's a small drag that users of the autoconfigured sdk now have to be aware or otherwise check which config was used to get the data they need. It feels like there is room to flatten the structured config into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would characterize it slightly differently: Providers of custom components like exporters and samplers need to be aware that somebody can use file based config or flat system property / env var based config, and provide different SPI implementations for each. This is frustrating, but seems to be the inevitable result of otel getting as far as it did without a proper configuration story. As I mentioned in the PR description, I started with the opposite approach and tried to have a single unified |
||
|
||
AutoConfiguredOpenTelemetrySdk() {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A demonstration ComponentProvider. Notice how config properties are accessed without dot notation since the YAML node is presented directly as the
StructuredConfigProperties
.