-
Notifications
You must be signed in to change notification settings - Fork 34
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
Conversation
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.
(Sorry @Traced that the commit messages tag you, I didn't mean to do that! 🙇🏻♀️) |
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 |
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.
Very nice! I had some minor comments, please have a look
import Tracing | ||
|
||
#if compiler(>=6.0) | ||
@attached(body) |
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.
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, |
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 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?
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 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
?
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.
Yup, right next to this Traced
declaration would be fine
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.
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:
- Directly match looking for
.baseName
/.fullName
/ string expressions, and not being able to handle other kinds of expressions. - 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.
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.
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, |
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.
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)
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'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 towithSpan
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?
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 did some experimentation: The default value isn't passed to the macro at all, it seems to just be for documentation purposes?
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.
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?
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.
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
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 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), | ||
], |
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.
This change is a semver major, are we actually intending to mint a new semver for this?
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.
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.
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.
Alternative PR over there: apple/swift-distributed-tracing-extras#42
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.
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.
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.
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?
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 think extras might be fine -- this was replaced by apple/swift-distributed-tracing-extras#42
Replaced by: in apple/swift-distributed-tracing-extras#42 -extras |
This provides a
@Traced
macro which adds a tracing span around the body of the function. This macro allows customizing theoperationName
, thecontext
, and thekind
of the span, same as thewithSpan
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:
expands to:
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