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

Proof of Concept of "Typed Spans" #195

Merged
merged 7 commits into from
Mar 13, 2020
Merged

Proof of Concept of "Typed Spans" #195

merged 7 commits into from
Mar 13, 2020

Conversation

tylerbenson
Copy link
Member

@tylerbenson tylerbenson commented Feb 29, 2020

Primary goal is to maintain a simple and semantic interface for the code using it. See TypedTracerDemonstration for example usage.

It's proposed that these would replace the current decorator hierarchy.

[skip ci]

Primary goal is to maintain a simple and semantic interface for the code using it. See `HttpClientTracerDemonstration` for example usage.

import io.opentelemetry.context.Scope;

class HttpClientTracerDemonstration {
Copy link
Member Author

Choose a reason for hiding this comment

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

See here for example usage.

@prydin
Copy link
Contributor

prydin commented Mar 3, 2020

My $0.02:
I like it. Great separation of concern. Spans become self-decorating and you concentrate all the knowledge on how a span should behave to the span itself. The Tracer knows everything about creating and naming spans. Nice and clean!

This is also great for testability. The entire instrumentation can easily be unit tested completely decoupled from any bytecode manipulation.

I'm assuming your aim is eventually to move all the typed spans to opentelemetry-java and turning the auto-project into more or less a bunch of Advice classes that delegate all the actual work to the typed spans and tracers. I love that!

My only VERY minor concern is that there may be a lot of boilerplate code to write for defining the tracer and span types for each instrumentation.

// span.onResponse("response instance"); // implicitly called on end.
span.end(response);
} catch (Exception ex) {
span.end(ex);
Copy link
Member

Choose a reason for hiding this comment

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

I like end() taking either response or throwable, makes usage very clear 👍

SampleHttpServerTypedTracer tracer = new SampleHttpServerTypedTracer();

SampleHttpServerTypedSpan span = tracer.startSpan("request instance");
// span.onRequest("request instance"); // implicitly called on start.
Copy link
Member

Choose a reason for hiding this comment

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

Can onRequest() and onResponse() be protected so it's not confusing whether or not they need to be called?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I redid the package structure to allow this... I had to put the tracer and the span in the same package for it to work.

Comment on lines 85 to 93
@Override
public void end() {
delegate.end();
}

@Override
public void end(EndSpanOptions endOptions) {
delegate.end(endOptions);
}
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice if BaseTypedSpan didn't have these two end() methods, so that we could have some typed span hierarchies where they don't exist and it's super clear that you have to call either end(response) or end(throwable)

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is it extends from Span, which allows it to be put into Scope as-is. The alternative would be to override the method and throw an exception.

Copy link
Member

Choose a reason for hiding this comment

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

Good point. I want to say maybe override and mark @Deprecated so that IDEs will give hint that you shouldn't be calling it, but @Deprecated isn't quite right, any idea some other way to get IDEs to hint that you shouldn't call it?

Copy link
Member

@trask trask Mar 3, 2020

Choose a reason for hiding this comment

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

Oh, I found example where I'd seen this kind of thing before, Guava's ImmutableList:

  /**
   * Guaranteed to throw an exception and leave the list unmodified.
   *
   * @throws UnsupportedOperationException always
   * @deprecated Unsupported operation.
   */
  @Deprecated
  @Override
  public final void add(int index, E element) {
    throw new UnsupportedOperationException();
  }

I think it would be helpful to add this kind of override for end() and end(endOptions) methods in ClientTypedSpan and ServerTypedSpan to help avoid confusion of which end(...) method to call.

My extra comment above isn't showing down here, so copying it:

Although API is not supposed to throw exceptions, and we're hoping these might someday be there, so maybe mark deprecated, but no-op instead of throw exception?

Copy link
Member

Choose a reason for hiding this comment

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

Although API is not supposed to throw exceptions, and we're hoping these might someday be there, so maybe mark deprecated, but no-op instead of throw exception?

import io.opentelemetry.trace.Span;

public abstract class ClientTypedTracer<
T extends ClientTypedSpan<T, REQUEST, RESPONSE>, REQUEST, RESPONSE>
Copy link
Member

Choose a reason for hiding this comment

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

Eeek 😆

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, there wasn't a good way to ensure the return types were strongly typed without some extra generics complexity.

@tylerbenson
Copy link
Member Author

@prydin for the amount of code that would need to be written for each integration, see the SampleHttp*Typed* classes. That is similar to the effort required currently for the decorators.

I think there's still some work remaining to ensure that the API is flexible enough for all our needs, but I think it's a good starting point.

@trask
Copy link
Member

trask commented Mar 3, 2020

FWIW, boilerplate doesn't bother me (I'm probably just numb 😁)

super.end();
}

protected abstract T self();
Copy link
Member

@trask trask Mar 3, 2020

Choose a reason for hiding this comment

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

This doesn't look like it's used anymore

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 think it will still be necessary if we override any methods that return T.

Comment on lines 85 to 93
@Override
public void end() {
delegate.end();
}

@Override
public void end(EndSpanOptions endOptions) {
delegate.end(endOptions);
}
Copy link
Member

@trask trask Mar 3, 2020

Choose a reason for hiding this comment

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

Oh, I found example where I'd seen this kind of thing before, Guava's ImmutableList:

  /**
   * Guaranteed to throw an exception and leave the list unmodified.
   *
   * @throws UnsupportedOperationException always
   * @deprecated Unsupported operation.
   */
  @Deprecated
  @Override
  public final void add(int index, E element) {
    throw new UnsupportedOperationException();
  }

I think it would be helpful to add this kind of override for end() and end(endOptions) methods in ClientTypedSpan and ServerTypedSpan to help avoid confusion of which end(...) method to call.

My extra comment above isn't showing down here, so copying it:

Although API is not supposed to throw exceptions, and we're hoping these might someday be there, so maybe mark deprecated, but no-op instead of throw exception?

@tylerbenson
Copy link
Member Author

Related to open-telemetry/opentelemetry-java#964

super(delegate);
}

protected abstract T onRequest(REQUEST request);
Copy link
Member

Choose a reason for hiding this comment

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

I would not provide this method and instead force the user to provide the request already in startSpan, so that all attributes are set on the builder and thus available to samplers (and onStart callbacks of SpanProcessors)

Copy link
Member Author

Choose a reason for hiding this comment

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

This method is not exposed to the user... it's intended for implementation by the concrete class. Sounds like you think it's not necessary though?

@tylerbenson tylerbenson marked this pull request as ready for review March 12, 2020 22:17
Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

Discussed in weekly call. This looks like a good place to start iterating from. Thanks @tylerbenson!

SampleHttpServerTypedSpan span = tracer.startSpan("request instance");
// span.onRequest("request instance"); // implicitly called on start.

try (Scope scope = tracer.withSpan(span)) {

Choose a reason for hiding this comment

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

suggestion: make a static class Request and Response as tracer.startSpan("request instance"); looks very much like a normal start of a span with a pre-canned name

@iNikem iNikem mentioned this pull request Jun 1, 2020
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.

5 participants