WithMetricsFactory.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Metrics API open source project
  4. //
  5. // Copyright (c) 2026 Apple Inc. and the Swift Metrics API 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 Metrics API project authors
  10. //
  11. // SPDX-License-Identifier: Apache-2.0
  12. //
  13. //===----------------------------------------------------------------------===//
  14. /// Runs the given closure with a factory bound to the task-local context.
  15. ///
  16. /// Metrics created within the closure will use the specified factory instead of the global factory. The factory
  17. /// is captured at metric creation time and used for the metric's entire lifetime.
  18. ///
  19. /// ## Example: Testing with isolated factory
  20. ///
  21. /// ```swift
  22. /// @Test
  23. /// func testRequestHandling() async {
  24. /// let testFactory = TestMetrics()
  25. /// let service = await withMetricsFactory(testFactory) {
  26. /// RequestService() // Creates metrics using testFactory
  27. /// }
  28. ///
  29. /// service.handleRequest()
  30. ///
  31. /// let counter = try testFactory.expectCounter("requests")
  32. /// #expect(counter.values == [1])
  33. /// }
  34. /// ```
  35. ///
  36. /// ## Example: Parallel tests with isolated factories
  37. ///
  38. /// ```swift
  39. /// @Test
  40. /// func testParallelRequests() async {
  41. /// let factory1 = TestMetrics()
  42. /// let factory2 = TestMetrics()
  43. ///
  44. /// async let result1 = withMetricsFactory(factory1) {
  45. /// let service = RequestService()
  46. /// return service.handleRequest()
  47. /// }
  48. ///
  49. /// async let result2 = withMetricsFactory(factory2) {
  50. /// let service = RequestService()
  51. /// return service.handleRequest()
  52. /// }
  53. ///
  54. /// _ = try await (result1, result2)
  55. ///
  56. /// // Each factory has isolated metrics
  57. /// #expect(try factory1.expectCounter("requests").values == [1])
  58. /// #expect(try factory2.expectCounter("requests").values == [1])
  59. /// }
  60. /// ```
  61. ///
  62. /// - Parameters:
  63. /// - factory: The metrics factory to use for metric creation within the closure.
  64. /// - operation: The closure to execute with the factory bound.
  65. /// - Returns: The value returned by the closure.
  66. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  67. @inlinable
  68. public func withMetricsFactory<Result, Failure: Error>(
  69. _ factory: MetricsFactory,
  70. _ operation: () throws(Failure) -> Result
  71. ) throws(Failure) -> Result {
  72. do {
  73. return try MetricsSystem.withTaskLocalFactory(factory, operation: operation)
  74. } catch {
  75. // `withTaskLocalFactory` uses `rethrows`, the underlying `$_taskLocalFactory.withValue` uses `throws`,
  76. // so the compiler cannot verify the error type at the boundary. However, the only errors it can propagate
  77. // are those thrown by `operation`, which is declared `throws(Failure)`.
  78. throw error as! Failure
  79. }
  80. }
  81. /// Runs the given async closure with a factory bound to the task-local context.
  82. ///
  83. /// Metrics created within the closure will use the specified factory instead of the global factory. The factory
  84. /// is captured at metric creation time and used for the metric's entire lifetime.
  85. ///
  86. /// ## Example: Testing with isolated factory
  87. ///
  88. /// ```swift
  89. /// @Test
  90. /// func testRequestHandling() async {
  91. /// let testFactory = TestMetrics()
  92. /// let service = await withMetricsFactory(testFactory) {
  93. /// RequestService() // Creates metrics using testFactory
  94. /// }
  95. ///
  96. /// service.handleRequest()
  97. ///
  98. /// let counter = try testFactory.expectCounter("requests")
  99. /// #expect(counter.values == [1])
  100. /// }
  101. /// ```
  102. ///
  103. /// ## Example: Parallel tests with isolated factories
  104. ///
  105. /// ```swift
  106. /// @Test
  107. /// func testParallelRequests() async {
  108. /// let factory1 = TestMetrics()
  109. /// let factory2 = TestMetrics()
  110. ///
  111. /// async let result1 = withMetricsFactory(factory1) {
  112. /// let service = RequestService()
  113. /// return service.handleRequest()
  114. /// }
  115. ///
  116. /// async let result2 = withMetricsFactory(factory2) {
  117. /// let service = RequestService()
  118. /// return service.handleRequest()
  119. /// }
  120. ///
  121. /// _ = try await (result1, result2)
  122. ///
  123. /// // Each factory has isolated metrics
  124. /// #expect(try factory1.expectCounter("requests").values == [1])
  125. /// #expect(try factory2.expectCounter("requests").values == [1])
  126. /// }
  127. /// ```
  128. ///
  129. /// - Parameters:
  130. /// - factory: The metrics factory to use for metric creation within the closure.
  131. /// - operation: The async closure to execute with the factory bound.
  132. /// - Returns: The value returned by the closure.
  133. #if compiler(>=6.2)
  134. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  135. @inlinable
  136. public nonisolated(nonsending) func withMetricsFactory<Result, Failure: Error>(
  137. _ factory: MetricsFactory,
  138. _ operation: nonisolated(nonsending) () async throws(Failure) -> Result
  139. ) async throws(Failure) -> Result {
  140. do {
  141. return try await MetricsSystem.withTaskLocalFactory(factory, operation: operation)
  142. } catch {
  143. // `withTaskLocalFactory` uses `rethrows`, the underlying `$_taskLocalFactory.withValue` uses `throws`,
  144. // so the compiler cannot verify the error type at the boundary. However, the only errors it can propagate
  145. // are those thrown by `operation`, which is declared `throws(Failure)`.
  146. throw error as! Failure
  147. }
  148. }
  149. #else
  150. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
  151. @inlinable
  152. public func withMetricsFactory<Result, Failure: Error>(
  153. _ factory: MetricsFactory,
  154. _ operation: () async throws(Failure) -> Result
  155. ) async throws(Failure) -> Result {
  156. do {
  157. return try await MetricsSystem.withTaskLocalFactory(factory, operation: operation)
  158. } catch {
  159. // `withTaskLocalFactory` uses `rethrows`, the underlying `$_taskLocalFactory.withValue` uses `throws`,
  160. // so the compiler cannot verify the error type at the boundary. However, the only errors it can propagate
  161. // are those thrown by `operation`, which is declared `throws(Failure)`.
  162. throw error as! Failure
  163. }
  164. }
  165. #endif