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

Add a TracingMacros module providing the @Traced macro #157

Closed
wants to merge 8 commits into from

Conversation

porglezomp
Copy link
Contributor

This provides a @Traced macro which adds a tracing span around the body of the function. This macro allows customizing the operationName, the context, and the kind of the span, same as the withSpan function. It also exposes the span itself into the scope of the function, with a customizable name.

This doesn't attempt to support automatically adding parameters as attributes, because the scoping rules of attached macros aren't very amenable to controlling that. That could be added in the future if it's judged necessary.

Examples:

@Traced("preheat oven")
func preheatOven(temperature: Int) async throws -> Oven {
    span.attributes["oven.targetTemperature"] = temperature
    await sleep(for: .seconds(6))
    return Oven()
}

expands to:

func preheatOven(temperature: Int) async throws -> Oven {
    withSpan("preheat oven") { span in
        span.attributes["oven.targetTemperature"] = temperature
        await sleep(for: .seconds(6))
        return Oven()
    }
}

Notes:

This places the macros into a separate product so that users who don't want to pay the compile-time cost of macros don't have to use it, you opt-in to that cost by depending on the TracingMacros module.

This adds minimum OS versions for Apple platforms in order to be able to depend on SwiftSyntax. This applies to the whole package since there are no per-target platform specifications. Most notably: This raises the macOS minimum deployment target from the (implicit) 10.13 to 10.15 (matching SwiftSyntax).

Resolves #125

This is defined as a separate product so that users who don't want to
pay the compile-time cost of macros don't have to use it, you opt-in to
that cost by depending on the TracingMacros module.

The @Traced macro is only available in Swift 6.0 compilers since
function body macros were introduced in Swift 6.0.

This adds minimum OS versions for Apple platforms in order to be able to
depend on SwiftSyntax. This applies to the whole package since there are
no per-target platform specifications. Most notably: This raises the
macOS minimum deployment target from the (implicit) 10.13 to 10.15.
Based on the effects signature of the attached function we apply
try/await as appropriate. But, if the function is async/throws but those
effects aren't actually used in the function body, this causes a new
warning because the closure isn't inferred to be async and/or throws. To
avoid those warnings, we also apply matching effects specifiers to the
withSpan closure.
First, allow overriding the regular parameters of the withSpan call:
setting the operationName, the context, and the kind. If it's not
specified, it's not passed to the function, which means the function
remains the source of truth for default arguments.

Also allow overriding the span name, which controls the variable binding
in the closure body. This is primarily useful for avoiding shadowing an
outer "span" variable.
@porglezomp
Copy link
Contributor Author

(Sorry @Traced that the commit messages tag you, I didn't mean to do that! 🙇🏻‍♀️)

@ktoso ktoso self-requested a review November 22, 2024 21:55
@porglezomp
Copy link
Contributor Author

My bad with the trailing commas, I tested on 5.9 but not on 6.0, and I was using a toolchain with swiftlang/swift-evolution#2344

@ktoso ktoso added the 🆕 semver/minor Adds new public API. label Nov 22, 2024
Copy link
Member

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

Very nice! I had some minor comments, please have a look

import Tracing

#if compiler(>=6.0)
@attached(body)
Copy link
Member

Choose a reason for hiding this comment

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

Needs some docs.

The shape is good though! I like the spanName as well, that's quite niffty.

#if compiler(>=6.0)
@attached(body)
public macro Traced(
_ operationName: String? = nil,
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this may want to be an TracedOperationName like this:

public struct TracedOperationName: ExpressibleByStringLiteral{ 
let value: TracedOperationName

package enum TracedOperationName {
case baseName // the default "foo"
case fullName // "foo(bar:)
case string(String)
}


public static var baseName: ... // for all the above

WDYT?

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 was going back and forth on whether it should be implemented with baseName / fullName semantics, so this looks good to me! Should this type be in TracingMacros?

Copy link
Member

Choose a reason for hiding this comment

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

Yup, right next to this Traced declaration would be fine

Copy link
Contributor Author

@porglezomp porglezomp Nov 23, 2024

Choose a reason for hiding this comment

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

Actually I just realized this is also made slightly harder by the macro—we want this conceptually but we only have access to the syntax and not the evaluated value. Two options here:

  1. Directly match looking for .baseName / .fullName / string expressions, and not being able to handle other kinds of expressions.
  2. Or add a bit of runtime support expanding to something like:
withSpan(.baseName.text(baseName: "foo", fullName: "foo(bar:)")) { span in ... }

And then doing the selection in regular code on the expression.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it would be in TracingMacros I think. I think basename as basic is okey

@attached(body)
public macro Traced(
_ operationName: String? = nil,
context: ServiceContext? = nil,
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, technically is the default .current rather than nil?

Should the type be the @autoclosure () -> ServiceContext as well, so we avoid reading the local when tracing is disabled (not bootstrapped)

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'm not entirely sure about the answer here since this is my first major macro.

  • For the default: I'm using nil just to delegate the default handling to withSpan itself—we don't even construct the syntax to pass a parameter when it's omitted here. I don't fully understand how default parameters are even passed to the macro.
  • For the type: since this is a macro it's handled via syntax expansion, so the type is used for checking the interface, not as a runtime type. When the expression is interpolated into the call, I think it will end up becoming an autoclosure at the withSpan callsite?

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 did some experimentation: The default value isn't passed to the macro at all, it seems to just be for documentation purposes?

Copy link
Contributor Author

@porglezomp porglezomp Nov 22, 2024

Choose a reason for hiding this comment

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

Since it seems to be only documentation—do we want to duplicate the default values from withSpan here, or cross-reference to say "look at the default values in the function documentation"? I can see arguments for both. I leaned towards omitting them here to not duplicate code across modules, but it's already duplicated across many overload so it's maybe worth it for the documentation's sake?

Copy link
Member

Choose a reason for hiding this comment

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

Oh good point about the autoclosure not mattering!

the nil maybe is fine after all, since it just means "don't pass anything" makes sense to me

For the docs: i think it's fine to not duplicate the docs, just a short note that it is passed to the withSpan will be enough

Copy link
Member

Choose a reason for hiding this comment

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

I see, yeah let's document to just look at the real method maybe

**Motivation:**

We want to allow richer customization of the operation name in the
`@Traced` macro, in particular the difference between the different ways
you can view a function name.

**Modifications:**

- Add a TracedOperationName type that represents the kinds of operation
  names we want to support.
- Change the `@Traced` macro interface and expansion to use the new
  operation name type.

**Result:**

Now you can write `@Traced(.baseName)`, `@Traced(.fullName)` as well as
`@Traced("custom name here")`, giving more flexibility between types of
operation names.
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
Copy link

Choose a reason for hiding this comment

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

This change is a semver major, are we actually intending to mint a new semver for this?

Copy link
Contributor Author

@porglezomp porglezomp Nov 25, 2024

Choose a reason for hiding this comment

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

This is required because swiftpm doesn't allow specifying this at any finer granularity than the whole package, and it can't handle the dependencies in only a single product.

I was unsure how strictly this would be a breaking change since there was no versions explicitly stated here, but wanted to discuss it.

I'm noticing that these are already the minimum platforms for swift-distributed-tracing-extras and so we could put these macros there instead of here without a semver major bump. I'd be happy with that as a place for this to live, Konrad just initially suggested here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternative PR over there: apple/swift-distributed-tracing-extras#42

Copy link

Choose a reason for hiding this comment

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

Yeah, unfortunately this is a breaking change because SwiftPM doesn’t take the minimum deployment targets into consideration when resolving versions. The result is broken builds, caused by picking up the new minima.

While we’re on the topic of breakage, Swift-syntax also represents a risk. Because of their versioning strategy it’s very easy to produce unsolveable build graphs, or forcing users to not be able to update past the current version. I’m generally nervous about putting this anywhere it might accidentally get picked up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For that at least—am I correct that package versions are only resolved for products that are dependent on, so that at least requires libraries to depend on the macros to get breakage? But yes I don't have any experience with releasing a package providing macros, I have no idea what is sufficient here. Just putting it in -extras? Putting it in a whole separate package?

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 extras might be fine -- this was replaced by apple/swift-distributed-tracing-extras#42

@ktoso
Copy link
Member

ktoso commented Nov 26, 2024

Replaced by: in apple/swift-distributed-tracing-extras#42 -extras

@ktoso ktoso closed this Nov 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🆕 semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Offer @traced macro when macros gain function body replacement capabilities
4 participants