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

Allow for simpler creation of start-only and end-only SpanProcessors. #5923

Merged
merged 6 commits into from
Dec 4, 2023

Conversation

breedx-splk
Copy link
Contributor

The current design of SpanProcessor is such that it allows implementations to designate whether they operate at start time, end time, or both. A number of implementations only do one small thing at start time or one small thing at end time, and not both.

This results in a fair amount of boilerplate that gets in the way of readability.

Because SpanProcessor is stable, we can't alter the existing api surface, so this just adds a couple of helper classes. If this idea is interesting, you could also peek at the first commit, which used static factory methods on the interface to return anonymous classes.

@breedx-splk breedx-splk requested a review from a team as a code owner October 17, 2023 23:24
@codecov
Copy link

codecov bot commented Oct 18, 2023

Codecov Report

Attention: 2 lines in your changes are missing coverage. Please review.

Files Coverage Δ
.../extension/incubator/trace/OnEndSpanProcessor.java 88.88% <88.88%> (ø)
...xtension/incubator/trace/OnStartSpanProcessor.java 88.88% <88.88%> (ø)

... and 10 files with indirect coverage changes

📢 Thoughts on this report? Let us know!.

@jack-berg
Copy link
Member

I like this idea. Couple of thoughts:

  • Can we incubate it for a bit?
  • What do you think about this slight variation, where we change the new processors from concrete classes to interfaces which extend SpanProcessor, and annotate them with @FunctionalInterface to allow lambda implementations?

@breedx-splk
Copy link
Contributor Author

  • Can we incubate it for a bit?

Sure, what's the mechanism for doing that?

  • What do you think about this slight variation, where we change the new processors from concrete classes to interfaces which extend SpanProcessor, and annotate them with @FunctionalInterface to allow lambda implementations?

I sketched that as well. I think I concluded that I disliked allowing implementations of OnEndSpanProcessor to possibly implement isEndRequired(){ return false; } and even to implement onStart(). Same, but the other way around with OnStartSpanProcessor.

Maybe I'm being a little too rigid there, but it just seems to give a lot of room for users to build weird, ill-fitting implementations. However, I might could be talked into it....

@jack-berg
Copy link
Member

Sure, what's the mechanism for doing that?

Move it to an internal package or, preferably, /sdk-extensions/incubator.

I think I concluded that I disliked allowing implementations of OnEndSpanProcessor to possibly implement isEndRequired(){ return false; } and even to implement onStart(). Same, but the other way around with OnStartSpanProcessor.

That's a good point. I guess the thing that gives me pause about the approach in this PR is the use of Consumer / BiConsumer instead of having dedicated functional interfaces. What if we added dedicated OnSpanStart and OnSpanEnd functional interfaces and accepted these as arguments for the concrete processor implementations? It should be identical ergonomics as you have, but the dedicated interfaces give us more flexibility to model the contract if new methods ever get added to SpanProcessor. For example, there have been discussions of adding a beforeEnd method, which would still be allow to mutate the span. If a method is ever added with a third argument, we wouldn't be able to offer a symmetric API because there's no TriConsumer.

@breedx-splk
Copy link
Contributor Author

That's a good point. I guess the thing that gives me pause about the approach in this PR is the use of Consumer / BiConsumer instead of having dedicated functional interfaces. What if we added dedicated OnSpanStart and OnSpanEnd functional interfaces and accepted these as arguments for the concrete processor implementations? It should be identical ergonomics as you have, but the dedicated interfaces give us more flexibility to model the contract if new methods ever get added to SpanProcessor. For example, there have been discussions of adding a beforeEnd method, which would still be allow to mutate the span. If a method is ever added with a third argument, we wouldn't be able to offer a symmetric API because there's no TriConsumer.

Ok, I don't think it hurts to add these, so I'm doing that...but I also think that if the beforeEnd() comes along and it has 3 arguments, we could create another processor called BeforeEndSpanProcessor and that could have the special custom functional interface. I just can't imagine a case now where we'd need to change those new functional interfaces or add methods to them or whatever.


@FunctionalInterface
public interface OnEnd {
void apply(ReadableSpan span);
Copy link
Member

Choose a reason for hiding this comment

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

apply or onEnd?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used apply for both OnStart and OnEnd. I read it as a functional interface, so an implementation or lambda instance of OnEnd is the function and the function gets applied. At least that's how my brain thinks of it.

I think the name isn't hugely important here, but OnEnd.onEnd() seems a little silly. I think one could also make the case that it's a consumer, so it should be called accept(), but then OnStart has two args.

I guess I think apply is the most straightforward.

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 too silly?

@FunctionalInterface
public interface OnEndSpanProcessor extends SpanProcessor {

  // this is the kind of silly part...
  static SpanProcessor create(OnEndSpanProcessor onEnd) {
    return onEnd;
  }

  @Override
  default boolean isEndRequired() {
    return true;
  }

  @Override
  default void onStart(Context parentContext, ReadWriteSpan span) {
    // nop
  }

  @Override
  default boolean isStartRequired() {
    return false;
  }
}

Copy link
Member

Choose a reason for hiding this comment

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

I think that's what I suggested in this comment @trask.

Copy link
Member

Choose a reason for hiding this comment

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

thx for the link 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is this too silly?

It's not, but it does allow people to still do silly things with it. :)

@jack-berg jack-berg added this to the 1.33.0 milestone Nov 30, 2023
@jack-berg
Copy link
Member

Will merge this tomorrow if there are no further comments.

@jack-berg jack-berg merged commit e447e34 into open-telemetry:main Dec 4, 2023
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants