-
Notifications
You must be signed in to change notification settings - Fork 872
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 experimental declarative configuration #12265
Support experimental declarative configuration #12265
Conversation
import java.util.Map; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class StructuredConfigPropertiesBridgeTest { |
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.
StructuredConfigPropertiesBridge is my proposal for how we keep a sane configuration story in the agent / instrumentation despite the SDK having either flat sys prop / env var or structured YAML configuration options.
The idea is the implement the ConfigProperties
interface using a backing StructuredConfigProperties
instance, and have special logic to determine how the [standard java agent](https://opentelemetry.io/docs/zero-code/java/agent/disable/ properties map to a structured representation.
- Only properties starting with
otel.instrumentation.
resolve otel.instrumentation.
refers to the node at.instrumentation.java
in the declarative config data model- To resolve a property, we take the portion after
otel.instrumentation
and break it up into segments. For each segment (N-1) we walk down the StructuredConfigProperties tree, and read the final segment from the resolve node.
So given the following YAML, reading configProperties.getBoolean("otel.instrumentation.common.default-enabled")
resolves true:
instrumentation:
java:
common:
default-enabled: true
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.
See open-telemetry/opentelemetry-java-examples#492 for a demonstration
(Note, requires building the javaagent locally, including this PR from opentelemetry-java
)
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.
long-term, would we bridge in the other direction? i.e. replace ConfigProperties usage with StructuredConfigProperties everywhere in the Java agent, and have a bridge from ConfigProperties -> StructuredConfigProperties?
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.
That's certainly an option. I think we ought to talk through options and repercussions as a group.
One thing to note is that the access patterns for StructuredConfigProperties
and ConfigProperties
are quite different.
For example, accessing otel.instrumentation.common.default-enabled
is something like:
- ConfigProperties:
config.getBoolean("otel.instrumentation.common.default-enabled")
- StructuredConfigProperties (assuming you're starting at the
.instrumentation.java
node)config.getStructured("config").getBoolean("default-enabled")
- needs additional null handling each time you walk down a node
- diverges further for each level of nesting
Also need to remember that there's now a specification for an instrumentation config API (and java PR here open-telemetry/opentelemetry-java#6549). The java agent works around the historic lack of availability of such a thing, and we'll need to think about how / when / (if?) to cut over. There's lots of instrumentation that reads config - is it feasible for a one time cutover to the new APIs, or do we need to plan for some sort of iterative approach?
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.
needs additional null handling each time you walk down a node
what do you think of optional return types that support subsequent accessors without null check?
diverges further for each level of nesting
I didn't follow what you mean?
we'll need to think about how / when / (if?) to cut over. There's lots of instrumentation that reads config - is it feasible for a one time cutover to the new APIs, or do we need to plan for some sort of iterative approach?
if we do some initial prototyping and like it, we could do a one-time cutover in the 3.0.0 release
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.
diverges further for each level of nesting
I didn't follow what you mean?
Just that the difference in syntax grows the more levels of nesting you have.
what do you think of optional return types that support subsequent accessors without null check?
Well we need some sort of syntactic sugar to not it make so painful, that's for sure.
The problem with returning Optional
is that it creates a tri-state (optional-present, optional-empty, and null). While we can trust that the core implementation of StructuredConfigProperties
never returns null, other user-provided interfaces might not be so well behaved.
Could also define a "get or default" overload of the form: getStructured(String name, StructuredConfigProperties defaultProperties)
If we combine this with a EMPTY_CONFIG
constant, walking the tree becomes:
config.getStructured("common", EMPTY_CONFIG).getBoolean("default-enabled", true);
Not bad.
if we do some initial prototyping and like it, we could do a one-time cutover in the 3.0.0 release
That's true, but I doubt that the instrumentation config API will be stable in time for 3.0.0 which I assume is happening at the end of the year. So then we'll need to consider whether we're comfortable depending on an experimental API, or should wait till 4.0.0 for a cutover.
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.
I see these as feasible variants
- Optional:
config.getStructured("common")
.flatMap(c -> c.getStructured("foo")
.flatMap(c -> c.getBoolean("default-enabled", true));
- getStructured never returns null (but maybe has an
isPresent
method) - I like that one
config.getStructured("common").getStructured("foo").getBoolean("default-enabled", true);
It's true that custom implementations may still return null
(for any of the above) - but they could also throw exceptions.
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.
- Optional:
The SDK has no precedent for returning optional instead of null. I believe that was an intentional design decision long ago, and I'm not interested in breaking symmetry.
- getStructured never returns null (but maybe has an isPresent method) - I like that one
Users need a way to tell determine the when a key was present and empty vs. null, so never returning null (or some equivalent) is not an option. We could add isPresent, but that's the same as calling getString("foo") != null
so no improvement in user calling ergonomics.
PR here to add getStructured default method, which I think has good ergonomics and is symmetric with existing patterns in opentelemetry-java: open-telemetry/opentelemetry-java#6759
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.
So it would look like this for a real example - looks too heavy IMO:
structuredConfigProps
.getStructured("otel", empty())
.getStructured("instrumentation", empty())
.getStructured("http", empty())
.getStructured("client", empty())
.getMap("capture-request-headers"))
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.
There's abstractions in place to read common properties so that only a few places in code have to do deep tree walking. And no code will need to read the .getStructured("otel", empty()).getStructured("instrumentation", empty())
steps since those happen automatically.
* string_key: value | ||
* </pre> | ||
*/ | ||
final class StructuredConfigPropertiesBridge implements ConfigProperties { |
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.
would make sense to instrumentation-api-incubator so that in can be used in extensions
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.
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?
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.
javaagent modules can't be used outside the agent - please correct me if I'm wrong @laurit
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.
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?
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.
I want to use the bridge in the starter at some point here:
opentelemetry-java-instrumentation/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts
Line 65 in 428aa28
implementation(project(":sdk-autoconfigure-support")) |
But maybe that's a next step.
import java.util.Map; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class StructuredConfigPropertiesBridgeTest { |
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.
I see these as feasible variants
- Optional:
config.getStructured("common")
.flatMap(c -> c.getStructured("foo")
.flatMap(c -> c.getBoolean("default-enabled", true));
- getStructured never returns null (but maybe has an
isPresent
method) - I like that one
config.getStructured("common").getStructured("foo").getBoolean("default-enabled", true);
It's true that custom implementations may still return null
(for any of the above) - but they could also throw exceptions.
javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/AgentListener.java
Show resolved
Hide resolved
...api/src/main/java/io/opentelemetry/javaagent/extension/StructuredConfigPropertiesBridge.java
Outdated
Show resolved
Hide resolved
...api/src/main/java/io/opentelemetry/javaagent/extension/StructuredConfigPropertiesBridge.java
Outdated
Show resolved
Hide resolved
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.
lgtm
…y-java-instrumentation into support-declarative-config
@Nullable | ||
@Override | ||
public Duration getDuration(String propertyName) { | ||
Long millis = getPropertyValue(propertyName, StructuredConfigProperties::getLong); | ||
if (millis == null) { | ||
return null; | ||
} | ||
return Duration.ofMillis(millis); | ||
} |
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.
is this intentionally scoping down and not handling durations with units like DefaultConfigProperties
?
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.
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.
When declarative config is used,
AutoConfigureUtil.getConfig(AutoConfiguredOpenTelemetrySdk)
returns null.This adjusts all calls to this method to be able to accommodate the null response.
Introduces
StructuredConfigPropertiesBridge
, which implementsConfigProperties
when structured config is used and reads agent / instrumentation configuration from the YAML source. See comment for more details.