Skip to content
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

Support declarative configuration #12265

Merged
merged 8 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -15,7 +15,6 @@
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.io.InputStream;
import java.nio.file.Files;
Expand All @@ -29,7 +28,7 @@ public class JmxMetricInsightInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

if (config.getBoolean("otel.jmx.enabled", true)) {
JmxMetricInsight service =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.lang.reflect.Method;

Expand All @@ -21,7 +20,7 @@ public class OshiMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.oshi.enabled", defaultEnabled)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;

/** An {@link AgentListener} that enables runtime metrics during agent startup. */
Expand All @@ -21,7 +20,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
RuntimeMetricsBuilder builder = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.lang.instrument.Instrumentation;

Expand All @@ -19,7 +19,8 @@ public class JarAnalyzerInstaller implements BeforeAgentListener {

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredOpenTelemetrySdk);

boolean enabled =
config.getBoolean("otel.instrumentation.runtime-telemetry.package-emitter.enabled", false);
if (!enabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -30,7 +29,7 @@ public class Java8RuntimeMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)
Expand Down
2 changes: 2 additions & 0 deletions javaagent-extension-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies {
// autoconfigure is unstable, do not expose as api
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")

testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")

// Used by byte-buddy but not brought in as a transitive dependency.
compileOnly("com.google.code.findbugs:annotations")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
package io.opentelemetry.javaagent.extension;

import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.Ordered;
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;

Expand All @@ -25,4 +28,22 @@ public interface AgentListener extends Ordered {
* on an {@link Instrumentation}.
*/
void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk);

/** Resolve {@link ConfigProperties} from the {@code autoConfiguredOpenTelemetrySdk}. */
static ConfigProperties resolveConfigProperties(
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties sdkConfigProperties =
AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
if (sdkConfigProperties != null) {
return sdkConfigProperties;
}
StructuredConfigProperties structuredConfigProperties =
AutoConfigureUtil.getStructuredConfig(autoConfiguredOpenTelemetrySdk);
if (structuredConfigProperties != null) {
return new StructuredConfigPropertiesBridge(structuredConfigProperties);
}
// Should never happen
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalStateException(
"AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or StructuredConfigProperties. This is likely a programming error in opentelemetry-java");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension;

import static io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties.empty;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import javax.annotation.Nullable;

/**
* A {@link ConfigProperties} which resolves properties based on {@link StructuredConfigProperties}.
*
* <p>Only properties starting with "otel.instrumentation." are resolved. Others return null (or
* default value if provided).
*
* <p>To resolve:
*
* <ul>
* <li>"otel.instrumentation" refers to the ".instrumentation.java" node
* <li>The portion of the property after "otel.instrumentation." is split into segments based on
* ".".
* <li>For each N-1 segment, we walk down the tree to find the relevant leaf {@link
* StructuredConfigProperties}.
* <li>We extract the property from the resolved {@link StructuredConfigProperties} using the last
* segment as the property key.
* </ul>
*
* <p>For example, given the following YAML, asking for {@code
* ConfigProperties#getString("otel.instrumentation.common.string_key")} yields "value":
*
* <pre>
* instrumentation:
* java:
* common:
* string_key: value
* </pre>
*/
final class StructuredConfigPropertiesBridge implements ConfigProperties {
Copy link
Member

Choose a reason for hiding this comment

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

would make sense to instrumentation-api-incubator so that in can be used in extensions

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 wasn't sure where to include this. @open-telemetry/java-instrumentation-maintainers is this or instrumentation-api-incubator the right thing for this type of thing?

Copy link
Member

Choose a reason for hiding this comment

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

javaagent modules can't be used outside the agent - please correct me if I'm wrong @laurit

Copy link
Member

Choose a reason for hiding this comment

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

the current location seems good

would make sense to instrumentation-api-incubator so that in can be used in extensions

@zeitlinger maybe what you really want is open-telemetry/opentelemetry-java#6549?

Copy link
Member

Choose a reason for hiding this comment

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

I want to use the bridge in the starter at some point here:

But maybe that's a next step.


private static final String OTEL_INSTRUMENTATION_PREFIX = "otel.instrumentation.";

// The node at .instrumentation.java
private final StructuredConfigProperties instrumentationJavaNode;

StructuredConfigPropertiesBridge(StructuredConfigProperties rootStructuredConfigProperties) {
instrumentationJavaNode =
rootStructuredConfigProperties
.getStructured("instrumentation", empty())
.getStructured("java", empty());
}

@Nullable
@Override
public String getString(String propertyName) {
return getPropertyValue(propertyName, StructuredConfigProperties::getString);
}

@Nullable
@Override
public Boolean getBoolean(String propertyName) {
return getPropertyValue(propertyName, StructuredConfigProperties::getBoolean);
}

@Nullable
@Override
public Integer getInt(String propertyName) {
return getPropertyValue(propertyName, StructuredConfigProperties::getInt);
}

@Nullable
@Override
public Long getLong(String propertyName) {
return getPropertyValue(propertyName, StructuredConfigProperties::getLong);
}

@Nullable
@Override
public Double getDouble(String propertyName) {
return getPropertyValue(propertyName, StructuredConfigProperties::getDouble);
}

@Nullable
@Override
public Duration getDuration(String propertyName) {
Long millis = getPropertyValue(propertyName, StructuredConfigProperties::getLong);
if (millis == null) {
return null;
}
return Duration.ofMillis(millis);
}
Comment on lines +92 to +100
Copy link
Member

Choose a reason for hiding this comment

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

is this intentionally scoping down and not handling durations with units like DefaultConfigProperties?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. All durations in the declarative config schema are integer types assumed to be ms. There is no ability to specify the 1s, 1h, 1ms at this time.


@Override
public List<String> getList(String propertyName) {
List<String> propertyValue =
getPropertyValue(
propertyName,
(properties, lastPart) -> properties.getScalarList(lastPart, String.class));
return propertyValue == null ? Collections.emptyList() : propertyValue;
}

@Override
public Map<String, String> getMap(String propertyName) {
StructuredConfigProperties propertyValue =
getPropertyValue(propertyName, StructuredConfigProperties::getStructured);
if (propertyValue == null) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
propertyValue
.getPropertyKeys()
.forEach(
key -> {
String value = propertyValue.getString(key);
if (value == null) {
return;
}
result.put(key, value);
});
return Collections.unmodifiableMap(result);
}

@Nullable
private <T> T getPropertyValue(
String property, BiFunction<StructuredConfigProperties, String, T> extractor) {
if (!property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
return null;
}
String suffix = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
// Split the remainder of the property on ".", and walk to the N-1 entry
String[] segments = suffix.split("\\.");
if (segments.length == 0) {
return null;
}
StructuredConfigProperties target = instrumentationJavaNode;
if (segments.length > 1) {
for (int i = 0; i < segments.length - 1; i++) {
target = target.getStructured(segments[i], empty());
}
}
String lastPart = segments[segments.length - 1];
return extractor.apply(target, lastPart);
}
}
Loading
Loading