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

core-trace-experimental: add @withSpan macro #675

Closed

Conversation

iRevive
Copy link
Contributor

@iRevive iRevive commented May 10, 2024

An experimental implementation of the #550.
OpenTelemetry Java offers similar functionality using reflections and AOP https://opentelemetry.io/docs/languages/java/automatic/annotations/.

Before we start

The functionality exists in a separate otel4s-core-trace-experimental module.

How it works

The body of a method annotated with @withSpan will be wrapped into a span:

@withSpan
def findUser(
  @spanAttribute userId: Long, 
  @spanAttribute("user.hash") hash: String
): F[User] = ???

// expands into

def findUser(
    userId: Long, 
    hash: String
): F[User] = 
  Tracer[F].span(
    "findUser", 
    Attribute("userId", userId), Attribute("user.hash", hash)
  ).surround(???)

The macro works with variables too:

@withSpan("custom name")
val findUser: IO[User] = ???

// expands into

val findUser: IO[User] = 
  Tracer[IO].span("custom_name").surround(???)

Summary

Pros

  1. Separate tracing from the business layer
  2. Relatively easy to add tracing to the existing methods
  3. Generates boilerplate for you: the name of the span and attributes can be derived automatically
  4. Since the result type remains the same, IDEs should be fine

Cons

  1. It's macros
  2. @spanAttribute contributes to the visual noise
  3. Spring-like

Questions

1) Why @withSpan and @spanAttribute instead of @span and @attribute?

If I recall correctly, problems can arise in case-insensitive systems because we already have Span and Attribute.

However, if that is not the case, I would prefer @span and @attribute.

2) Should the macro capture source information?

We can easily add source attributes to the span, such as Attribute("source.file", "some/path/Service.scala") and Attribute("source.line", 123L).

@iRevive iRevive added the tracing Improvements to tracing module label May 10, 2024
@iRevive iRevive force-pushed the core-trace-experimental/withSpan-macro branch from 9b10452 to 0edcafa Compare May 10, 2024 08:18
Comment on lines +172 to +176
ensureImplicitExist(
q"_root_.org.typelevel.otel4s.AttributeKey.KeySelect[$tpt]",
e =>
s"the argument [${name.decodedName}] cannot be used as an attribute. The type [$tpt] is not supported.${e.getMessage}"
)

Choose a reason for hiding this comment

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

Since KeySelect is sealed there's no way to define a spanAttribute for an unsupported type, normally we'd create an Attribute manually like

case class Foo(value: String)
val attribute = Attribute("foo.value", foo.value)

This means we can't use the macro at all if we require one attribute that is unsupported. Would it be sensible to expose some way of contramapping an existing KeySelect?

Choose a reason for hiding this comment

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

Though thinking about it still won't be possible without some other function from value => key type 😢

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Valid points. The macro wouldn't work with newtypes/type aliases.

I guess we can provide some utility methods to lift arbitrary types into the Attribute.

@NthPortal
Copy link
Contributor

sorry it's taken me so long to have a look at this; I'll try to give a proper review this coming week

Copy link
Contributor

@NthPortal NthPortal left a comment

Choose a reason for hiding this comment

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

I'm not really familiar enough with macros (annotation or otherwise) to give the implementations a confident approval, but the tests look correct and quite thorough to me. Nice work!

I don't quite understand the concern about case-insensitive name collision; they're in separate directories, so I'm not clear on how it would be a problem, and would personally prefer the shorter/simpler names

* the custom name of the attribute to use. If not specified, the name of the
* parameter will be used
*/
class spanAttribute(@unused name: String = "") extends StaticAnnotation {
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be marked @compileTimeOnly as well?

@iRevive
Copy link
Contributor Author

iRevive commented Jun 12, 2024

Closing in favor of typelevel/otel4s-experimental#1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tracing Improvements to tracing module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants