Skip to content
This repository was archived by the owner on Jul 11, 2025. It is now read-only.

Commit 8632d7a

Browse files
porglezompktoso
authored andcommitted
Add the TracingMacros module and the @Traced macro
**Motivation:** Adding tracing to functions can be a slightly invasive modification because using `withSpan` requires re-indenting the whole function. It also requires some duplicated modifications when modifying the effects signatures of functions. We want a macro to perform this transformation. **Modifications:** Introduce a TracingMacros module to provide macros for the Tracing package. Add a `@Traced` macro that performs the transformation. 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.
1 parent 20999d9 commit 8632d7a

File tree

5 files changed

+218
-0
lines changed

5 files changed

+218
-0
lines changed

Package.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// swift-tools-version:5.9
2+
import CompilerPluginSupport
23
import PackageDescription
34

45
let package = Package(
@@ -16,9 +17,16 @@ let package = Package(
1617
"TracingOpenTelemetrySemanticConventions",
1718
]
1819
),
20+
.library(
21+
name: "TracingMacros",
22+
targets: [
23+
"TracingMacros",
24+
]
25+
),
1926
],
2027
dependencies: [
2128
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"),
29+
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"),
2230
],
2331
targets: [
2432
.target(
@@ -34,5 +42,32 @@ let package = Package(
3442
.product(name: "Tracing", package: "swift-distributed-tracing"),
3543
]
3644
),
45+
46+
// ==== --------------------------------------------------------------------------------------------------------
47+
// MARK: TracingMacros
48+
49+
.target(
50+
name: "TracingMacros",
51+
dependencies: [
52+
.target(name: "TracingMacrosImplementation"),
53+
.product(name: "Tracing", package: "swift-distributed-tracing"),
54+
]
55+
),
56+
.macro(
57+
name: "TracingMacrosImplementation",
58+
dependencies: [
59+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
60+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
61+
]
62+
),
63+
.testTarget(
64+
name: "TracingMacrosTests",
65+
dependencies: [
66+
.target(name: "TracingMacros"),
67+
.target(name: "TracingMacrosImplementation"),
68+
.product(name: "Tracing", package: "swift-distributed-tracing"),
69+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
70+
]
71+
),
3772
]
3873
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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()`` macro lets you avoid the extra indentation that comes with
10+
adopting traced code. You can just attach `@Traced` to a function and get
11+
started.
12+
13+
## Topics
14+
15+
### Tracing functions
16+
- ``Traced()``
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2020-2024 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
@_exported import ServiceContextModule
15+
import Tracing
16+
17+
#if compiler(>=6.0)
18+
/// Instrument a function to place the entire body inside a span.
19+
///
20+
/// This macro is equivalent to calling ``withSpan`` in the body, but saves an
21+
/// indentation level and duplication.
22+
@attached(body)
23+
public macro Traced() = #externalMacro(module: "TracingMacrosImplementation", type: "TracedMacro")
24+
#endif
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2020-2024 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import SwiftCompilerPlugin
15+
import SwiftSyntax
16+
import SwiftSyntaxBuilder
17+
import SwiftSyntaxMacros
18+
19+
#if compiler(>=6.0)
20+
public struct TracedMacro: BodyMacro {
21+
public static func expansion(
22+
of node: AttributeSyntax,
23+
providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
24+
in context: some MacroExpansionContext
25+
) throws -> [CodeBlockItemSyntax] {
26+
guard let function = declaration.as(FunctionDeclSyntax.self),
27+
let body = function.body
28+
else {
29+
throw MacroExpansionErrorMessage("expected a function with a body")
30+
}
31+
32+
let operationName = StringLiteralExprSyntax(content: function.name.text)
33+
let withSpanCall: ExprSyntax = "withSpan(\(operationName))"
34+
let withSpanExpr: ExprSyntax = "\(withSpanCall) { span in \(body.statements) }"
35+
36+
return ["\(withSpanExpr)"]
37+
}
38+
}
39+
#endif
40+
41+
@main
42+
struct TracingMacroPlugin: CompilerPlugin {
43+
#if compiler(>=6.0)
44+
let providingMacros: [Macro.Type] = [
45+
TracedMacro.self
46+
]
47+
#else
48+
let providingMacros: [Macro.Type] = []
49+
#endif
50+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2020-2024 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftSyntaxMacrosTestSupport
16+
import Tracing
17+
import TracingMacros
18+
import TracingMacrosImplementation
19+
import XCTest
20+
21+
final class TracedMacroTests: XCTestCase {
22+
#if compiler(>=6.0)
23+
func test_tracedMacro_requires_body() {
24+
assertMacroExpansion(
25+
"""
26+
@Traced
27+
func funcWithoutBody()
28+
""",
29+
expandedSource: """
30+
func funcWithoutBody()
31+
""",
32+
diagnostics: [
33+
.init(message: "expected a function with a body", line: 1, column: 1)
34+
],
35+
macros: ["Traced": TracedMacro.self]
36+
)
37+
}
38+
39+
func test_tracedMacro_sync_nothrow() {
40+
assertMacroExpansion(
41+
"""
42+
@Traced
43+
func syncNonthrowingExample(param: Int) {
44+
print(param)
45+
}
46+
""",
47+
expandedSource: """
48+
func syncNonthrowingExample(param: Int) {
49+
withSpan("syncNonthrowingExample") { span in
50+
print(param)
51+
}
52+
}
53+
""",
54+
macros: ["Traced": TracedMacro.self]
55+
)
56+
}
57+
58+
func test_tracedMacro_accessSpan() {
59+
assertMacroExpansion(
60+
"""
61+
@Traced
62+
func example(param: Int) {
63+
span.attributes["param"] = param
64+
}
65+
""",
66+
expandedSource: """
67+
func example(param: Int) {
68+
withSpan("example") { span in
69+
span.attributes["param"] = param
70+
}
71+
}
72+
""",
73+
macros: ["Traced": TracedMacro.self]
74+
)
75+
}
76+
#endif
77+
}
78+
79+
#if compiler(>=6.0)
80+
81+
// MARK: Compile tests
82+
83+
@Traced
84+
func syncNonthrowingExample(param: Int) {
85+
print(param)
86+
}
87+
88+
@Traced
89+
func example(param: Int) {
90+
span.attributes["param"] = param
91+
}
92+
93+
#endif

0 commit comments

Comments
 (0)