Skip to content

Commit 8b3cfd3

Browse files
committed
Make the @Traced macro take a TracedOperationName
**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.
1 parent 24364ca commit 8b3cfd3

File tree

5 files changed

+209
-15
lines changed

5 files changed

+209
-15
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# ``TracingMacros/TracedOperationName``
2+
3+
### Examples
4+
5+
The default behavior is to use the base name of the function, but you can
6+
explicitly specify this as well. This creates a span named `"preheatOven"`:
7+
```swift
8+
@Traced(.baseName)
9+
func preheatOven(temperature: Int)
10+
```
11+
12+
You can request the full name of the function as the span name, this
13+
creates a span named `"preheatOven(temperature:)"`:
14+
```swift
15+
@Traced(.fullName)
16+
func preheatOven(temperature: Int)
17+
```
18+
19+
And it is also initializable with a string literal for fully custom names,
20+
this creates a span explicitly named `"preheat oven"`:
21+
```swift
22+
@Traced("preheat oven")
23+
func preheatOven(temperature: Int)
24+
```
25+
And if you need to load an existing string value as a name, you can use
26+
`.string(someString)` to adapt it.
27+
28+
29+
## Topics
30+
31+
### Create Operation Names
32+
- ``baseName``
33+
- ``fullName``
34+
- ``string(_:)``
35+
- ``init(stringLiteral:)``
36+
37+
### Convert an Operation Name to a String
38+
- ``operationName(baseName:fullName:)``
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ``TracingMacros``
2+
3+
Macro helpers for Tracing.
4+
5+
## Overview
6+
7+
The TracingMacros module provides optional macros to make it easier to write traced code.
8+
9+
The ``Traced(_:context:ofKind:span:)`` macro lets you avoid the extra indentation that comes with
10+
adopting traced code, and avoids having to keep the throws/try and async/await
11+
in-sync with the body. You can just attach `@Traced` to a function and get
12+
started.
13+
14+
## Topics
15+
16+
### Tracing functions
17+
- ``Traced(_:context:ofKind:span:)``
18+
- ``TracedOperationName``
19+

Sources/TracingMacros/TracedMacro.swift

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,82 @@
1414
@_exported import ServiceContextModule
1515
import Tracing
1616

17+
/// A span name for a traced operation, either derived from the function name or explicitly specified.
18+
///
19+
/// When using the ``Traced(_:context:ofKind:span:)`` macro, you can use this to customize the span name.
20+
public struct TracedOperationName: ExpressibleByStringLiteral {
21+
@usableFromInline
22+
let value: Name
23+
24+
@usableFromInline
25+
enum Name {
26+
case baseName
27+
case fullName
28+
case string(String)
29+
}
30+
31+
internal init(value: Name) {
32+
self.value = value
33+
}
34+
35+
/// Use a literal string as an operation name.
36+
public init(stringLiteral: String) {
37+
value = .string(stringLiteral)
38+
}
39+
40+
/// Use the base name of the attached function.
41+
///
42+
/// For `func preheatOven(temperature: Int)` this is `"preheatOven"`.
43+
public static let baseName = TracedOperationName(value: .baseName)
44+
45+
/// Use the full name of the attached function.
46+
///
47+
/// For `func preheatOven(temperature: Int)` this is `"preheatOven(temperature:)"`.
48+
/// This is provided by the `#function` macro.
49+
public static let fullName = TracedOperationName(value: .fullName)
50+
51+
/// Use an explicitly specified operation name.
52+
public static func string(_ text: String) -> Self {
53+
.init(value: .string(text))
54+
}
55+
56+
/// Helper logic to support the `Traced` macro turning this operation name into a string.
57+
/// Provided as an inference guide.
58+
///
59+
/// - Parameters:
60+
/// - baseName: The value to use for the ``baseName`` case. Must be
61+
/// specified explicitly because there's no equivalent of `#function`.
62+
/// - fullName: The value to use for the ``fullName`` case.
63+
@inlinable
64+
@_documentation(visibility: internal)
65+
public static func _getOperationName(_ name: Self, baseName: String, fullName: String = #function) -> String {
66+
switch name.value {
67+
case .baseName: baseName
68+
case .fullName: fullName
69+
case let .string(text): text
70+
}
71+
}
72+
}
73+
1774
#if compiler(>=6.0)
1875
/// Instrument a function to place the entire body inside a span.
1976
///
20-
/// This macro is equivalent to calling ``/Tracing/withSpan`` in the body, but saves an
77+
/// This macro is equivalent to calling ``withSpan`` in the body, but saves an
2178
/// indentation level and duplication. It introduces a `span` variable into the
2279
/// body of the function which can be used to add attributes to the span.
2380
///
24-
/// Parameters are passed directly to ``/Tracing/withSpan`` where applicable,
81+
/// Parameters are passed directly to ``withSpan`` where applicable,
2582
/// and omitting the parameters from the macro omit them from the call, falling
2683
/// back to the default.
2784
///
2885
/// - Parameters:
29-
/// - operationName: The name of the operation being traced. Defaults to the name of the function.
30-
/// - context: The `ServiceContext` providing information on where to start the new ``/Tracing/Span``.
31-
/// - kind: The ``/Tracing/SpanKind`` of the new ``/Tracing/Span``.
86+
/// - operationName: The name of the operation being traced.
87+
/// - context: The `ServiceContext` providing information on where to start the new ``Span``.
88+
/// - kind: The ``SpanKind`` of the new ``Span``.
3289
/// - spanName: The name of the span variable to introduce in the function. Pass `"_"` to omit it.
3390
@attached(body)
3491
public macro Traced(
35-
_ operationName: String? = nil,
92+
_ operationName: TracedOperationName = .baseName,
3693
context: ServiceContext? = nil,
3794
ofKind kind: SpanKind? = nil,
3895
span spanName: String = "span"

Sources/TracingMacrosImplementation/TracedMacro.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,20 @@ public struct TracedMacro: BodyMacro {
3131

3232
// Construct a withSpan call matching the invocation of the @Traced macro
3333
let (operationName, context, kind, spanName) = try extractArguments(from: node)
34+
let baseNameExpr = ExprSyntax(StringLiteralExprSyntax(content: function.name.text))
3435

3536
var withSpanCall = FunctionCallExprSyntax("withSpan()" as ExprSyntax)!
36-
withSpanCall.arguments.append(
37-
LabeledExprSyntax(
38-
expression: operationName ?? ExprSyntax(StringLiteralExprSyntax(content: function.name.text))
39-
)
40-
)
37+
let operationNameExpr: ExprSyntax
38+
if let operationName {
39+
if operationName.is(StringLiteralExprSyntax.self) {
40+
operationNameExpr = operationName
41+
} else {
42+
operationNameExpr = "TracedOperationName._getOperationName(\(operationName), baseName: \(baseNameExpr))"
43+
}
44+
} else {
45+
operationNameExpr = baseNameExpr
46+
}
47+
withSpanCall.arguments.append(LabeledExprSyntax(expression: operationNameExpr))
4148
func appendComma() {
4249
withSpanCall.arguments[withSpanCall.arguments.index(before: withSpanCall.arguments.endIndex)]
4350
.trailingComma = .commaToken()

Tests/TracingMacrosTests/TracedTests.swift

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ import TracingMacros
1818
import TracingMacrosImplementation
1919
import XCTest
2020

21-
#if compiler(>=6.0)
22-
2321
final class TracedMacroTests: XCTestCase {
22+
#if compiler(>=6.0)
2423
func test_tracedMacro_requires_body() {
2524
assertMacroExpansion(
2625
"""
@@ -237,15 +236,49 @@ final class TracedMacroTests: XCTestCase {
237236
"""
238237
let globalName = "example"
239238
240-
@Traced(globalName)
239+
@Traced(.string(globalName))
241240
func example(param: Int) {
242241
span.attributes["param"] = param
243242
}
244243
""",
245244
expandedSource: """
246245
let globalName = "example"
247246
func example(param: Int) {
248-
withSpan(globalName) { span in
247+
withSpan(TracedOperationName._getOperationName(.string(globalName), baseName: "example")) { span in
248+
span.attributes["param"] = param
249+
}
250+
}
251+
""",
252+
macros: ["Traced": TracedMacro.self]
253+
)
254+
255+
assertMacroExpansion(
256+
"""
257+
@Traced(.baseName)
258+
func useBaseName(param: Int) {
259+
span.attributes["param"] = param
260+
}
261+
""",
262+
expandedSource: """
263+
func useBaseName(param: Int) {
264+
withSpan(TracedOperationName._getOperationName(.baseName, baseName: "useBaseName")) { span in
265+
span.attributes["param"] = param
266+
}
267+
}
268+
""",
269+
macros: ["Traced": TracedMacro.self]
270+
)
271+
272+
assertMacroExpansion(
273+
"""
274+
@Traced(.fullName)
275+
func useFullName(param: Int) {
276+
span.attributes["param"] = param
277+
}
278+
""",
279+
expandedSource: """
280+
func useFullName(param: Int) {
281+
withSpan(TracedOperationName._getOperationName(.fullName, baseName: "useFullName")) { span in
249282
span.attributes["param"] = param
250283
}
251284
}
@@ -411,8 +444,31 @@ final class TracedMacroTests: XCTestCase {
411444
macros: ["Traced": TracedMacro.self]
412445
)
413446
}
447+
448+
#endif
449+
450+
func test_operationNameBehavior() {
451+
XCTAssertEqual(
452+
TracedOperationName._getOperationName("example custom", baseName: "test_operationNameBehavior"),
453+
"example custom"
454+
)
455+
XCTAssertEqual(
456+
TracedOperationName._getOperationName(.string("example literal"), baseName: "test_operationNameBehavior"),
457+
"example literal"
458+
)
459+
XCTAssertEqual(
460+
TracedOperationName._getOperationName(.baseName, baseName: "test_operationNameBehavior"),
461+
"test_operationNameBehavior"
462+
)
463+
XCTAssertEqual(
464+
TracedOperationName._getOperationName(.fullName, baseName: "test_operationNameBehavior"),
465+
"test_operationNameBehavior()"
466+
)
467+
}
414468
}
415469

470+
#if compiler(>=6.0)
471+
416472
// MARK: Compile tests
417473

418474
@Traced
@@ -458,6 +514,23 @@ func example(param: Int) {
458514
span.attributes["param"] = param
459515
}
460516

517+
let globalName = "example"
518+
519+
@Traced(.string(globalName))
520+
func withDynamicOperationName(param: Int) {
521+
span.attributes["param"] = param
522+
}
523+
524+
@Traced(.baseName)
525+
func useBaseName(param: Int) {
526+
span.attributes["param"] = param
527+
}
528+
529+
@Traced(.fullName)
530+
func useFullName(param: Int) {
531+
span.attributes["param"] = param
532+
}
533+
461534
@Traced("custom span name", context: .topLevel, ofKind: .client, span: "customSpan")
462535
func exampleWithParams(span: Int) {
463536
customSpan.attributes["span"] = span + 1

0 commit comments

Comments
 (0)