-
Notifications
You must be signed in to change notification settings - Fork 858
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
Add support to use SemanticAttributes with Span.Builder. #1076
Add support to use SemanticAttributes with Span.Builder. #1076
Conversation
dc23ac3
to
bca7dd7
Compare
bca7dd7
to
5ff9226
Compare
What's the disadvantage of adding an overload for each setter to take a // BooleanAttributeSetter
public void set(Span.Builder builder, boolean value) {
builder.setAttribute(key(), value);
} |
It's only about losing chaining really. Or rather, having uglier chaining, since you can also do |
* | ||
* @param arg the input argument | ||
*/ | ||
void exec(T arg); |
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.
On an interface called Acceptor
, I'd expect this method to be called accept
.
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.
thinking out loud here, but what if we had this return T
, rather than void
, making it feel more like a way to extend the builder fluent nature?
@@ -544,6 +545,17 @@ | |||
*/ | |||
Builder setStartTimestamp(long startTimestamp); | |||
|
|||
/** | |||
* Applies the given consumer to this {@link Builder}. |
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.
should probably update the doc to call it an acceptor, rather than consumer.
api/src/main/java/io/opentelemetry/trace/attributes/BooleanAttributeSetter.java
Outdated
Show resolved
Hide resolved
public Acceptor<Span.Builder> set(final boolean value) { | ||
return new Acceptor<Span.Builder>() { | ||
@Override | ||
public void exec(Span.Builder arg) { |
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.
rename the arg to builder
?
api/src/main/java/io/opentelemetry/trace/attributes/DoubleAttributeSetter.java
Outdated
Show resolved
Hide resolved
public Acceptor<Span.Builder> set(final double value) { | ||
return new Acceptor<Span.Builder>() { | ||
@Override | ||
public void exec(Span.Builder arg) { |
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.
rename arg to builder
api/src/main/java/io/opentelemetry/trace/attributes/LongAttributeSetter.java
Outdated
Show resolved
Hide resolved
public Acceptor<Span.Builder> set(final long value) { | ||
return new Acceptor<Span.Builder>() { | ||
@Override | ||
public void exec(Span.Builder arg) { |
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.
you guessed it, rename arg to builder
api/src/main/java/io/opentelemetry/trace/attributes/StringAttributeSetter.java
Outdated
Show resolved
Hide resolved
public Acceptor<Span.Builder> set(@Nullable final String value) { | ||
return new Acceptor<Span.Builder>() { | ||
@Override | ||
public void exec(Span.Builder arg) { |
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.
ditto, rename
} else if (attribute instanceof BooleanAttributeSetter) { | ||
keys.add(((BooleanAttributeSetter) attribute).key()); | ||
spanBuilder.apply(((BooleanAttributeSetter) attribute).set(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.
maybe add an else
that calls fail
, so that this test will highlight something not being covered?
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.
IMHO the test for both this and the old SemConv API needs quite some improvements (such as testing that setAttribute
was actually called, which is unfortunately not possible with only the API due to Mockito not supporting final classes). But I just copied & adapted the tests for the set
method for now.
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'd be all in favor of making those classes non-final!
Co-Authored-By: John Watson <[email protected]>
One more thing to keep in mind is whether we can/should add methods to the public API, i.e. add a new method in |
I played around with this a bit locally, and I think the
|
Yes, I'd like to play around with this a bit more and see if we can do something similar without increasing the API surface. I like the idea of this change a lot, but am also hesitant to add this to the API. |
Or, without changing the API, we could do something like this (which I know isn't quite as fluent):
|
Having to name the attribute twice is a no-go IMHO. You can forget to use the attribute for the value, it still works; use the wrong attribute (different from key, it still works. Also, this is so cumbersome that I fear people will rather use setAttribute after the span is created, just because the syntax looks so much nicer (and in doing so, deprive the sampler of attribute information). |
WDYT? Should I clean up this PR or do we want to look for & decide on another approach? |
What about:
|
On which class/interface? Are you proposing adding this to the |
Yes (and something similar on |
@Oberon00 If we want to do #1081, it seems like we should probably solve both of these issues in one implementation. It seems like adding an |
Yeah, we'd need to bridge that (get the key/value from the Attribute, and create a new Attribute in the embedded SDK). |
@jkwatson So basically we replace the generic Acceptor with an Attribute and instead of |
It enables this usage, which you gave a 👍 to above: OpenTelemetry.getTracerProvider().get("").spanBuilder("foo")
.setAttribute(SemanticAttributes.DB_STATEMENT.withValue("select foo from bar"))
.setAttribute(SemanticAttributes.DB_URL.withValue("jdbc:blah:blah:blah"))
.setAttribute(SemanticAttributes.DB_USER.withValue("username"))
.startSpan(); |
You are right, I think this design is viable, but after thinking it through, does it offer any benefit over the one in this PR? |
BTW, in Java 8 with Lambdas and Optional.map, we would have more design possibilities. |
I almost never cry "performance" when trying to make an API ergonomics decision, but I do wonder a bit if we impose an extra 2 method invocations per attribute if that might be a performance penalty with some JVMs. That being said, I'm really ok with either option, especially if we're going to remove @carlosalberto @bogdandrutu any opinions? |
I don't think the extra 2 method invocations hurt compared to the extra memory allocation required for the temporary Acceptor/Attribute object. In theory the JVM could do escape analysis on this, but it's nice not to rely on that. That's (partly) why I like passing in the name and value as separate args, instead of creating a temporary object to hold them both, e.g.
|
How is this really different than what we have today? That is, what does it buy us over the existing setAttribute methods that take a string key that can be supplied via the existing SemanticAttributes class? We can already do this:
|
Gives you type-safety for SemanticAttributes. If type-safety is not a requirement for SemanticAttributes, then SemanticAttributes could just be |
@trask I think your suggestion would be the most straightforward and performant design, but it also means that at least the AttributeSetter classes cannot be moved out of the API package. If we are OK with that, that's what I would go for. However, we should probably introduce an abstract base class implementing the Span.Builder interface that takes care of the boilerplate of calling |
Oh yes. Somehow I missed the one actual important part of what you suggested. I think I got distracted by the long names, and missed the point. What if we did this, but just called it |
@jkwatson I think we got lost in pseudocode here, the actual name is |
If we do #1081 first, then I'm OK with naming it just "Attribute" as there is no potential for confusion anymore, or at least only for people who work with SDK internals. |
I'm ok-ish with trying @trask approach, but honestly slightly afraid of adding more boilerplate for helping with some chaining (are users expected to really be setting tags quite often? I'd say 'no', as mostly only people doing instrumentation will, which is mostly 'us'). Similar to #1081 I'd be up for a PR that prototypes it out. |
@carlosalberto I am also quite leery about increasing our API surface without some very deep thought and very clear non-trivial impact. It would be good if we had some sort of metric we could apply before adding API surface. Like, "this change will reduce the instrumentation code by 3 lines per span", which means 300 less lines of code in the instrumentation altogether. |
I'll see if I can put together a prototype draft PR for this. Unless someone else wants to get to it before me. |
See #1096 |
/** | ||
* A function to be executed with a single argument, having a side effect. | ||
* | ||
* <p>Adapted from Java 8's {@code java.util.function.Consumer}. |
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.
They may be to call it Consumer
as well?
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.
Oracle and Copyright made me think that it might be better to have a different name.
This is superseded by now. |
Suggestion on how to resolve #1075.
This design ensures that the API would not depend on a hypothetical contrib-semantic-attributes package. If we don't care about that, we could add overloads taking *AttributeSetter directly to the Span.Builder interface, without needing the Acceptor indirection.
This was designed with chainability in mind, i.e. you can do things like
spanBuilder.setAttribute(foo, bar).apply(ATTR1.set(23)).apply(ATTR2.set(43)).startSpan()
.