MappingMetricsFactory.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Metrics API open source project
  4. //
  5. // Copyright (c) 2018-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. /// A metrics factory that transforms labels and dimensions before forwarding to an upstream factory.
  15. ///
  16. /// `MappingMetricsFactory` wraps an existing ``MetricsFactory`` and applies a transformation to the label
  17. /// and dimensions of every metric before creating it in the upstream factory. This is useful for adding
  18. /// common dimensions (for example, service name, environment), renaming labels, or filtering dimensions
  19. /// across all metrics created through this factory.
  20. ///
  21. /// ```swift
  22. /// let factory = upstream.withLabelAndDimensionsMapping { label, dimensions in
  23. /// (label, dimensions + [("service", "my-service")])
  24. /// }
  25. /// let counter = Counter(label: "request_count", dimensions: [("method", "GET")], factory: factory)
  26. /// counter.increment()
  27. /// // The upstream factory sees dimensions [("method", "GET"), ("service", "my-service")]
  28. /// ```
  29. ///
  30. /// - Note: The transformation only affects what the upstream factory receives. The metric object itself
  31. /// (for example, `Counter.label`, `Counter.dimensions`) retains the original values passed at creation
  32. /// time. This means the label you see on the metric handle may differ from the label stored in the
  33. /// backend. Keeping the original label on the metric object helps when debugging local code. This
  34. /// discrepancy only requires attention when correlating local metric handles with data in a remote
  35. /// backend.
  36. public struct MappingMetricsFactory<Upstream: MetricsFactory>: Sendable {
  37. private var upstream: Upstream
  38. private var transform: @Sendable (String, [(String, String)]) -> (String, [(String, String)])
  39. /// Create a new `MappingMetricsFactory`.
  40. ///
  41. /// - parameters:
  42. /// - upstream: The upstream ``MetricsFactory`` to forward metric creation to after transformation.
  43. /// - transform: A closure that maps the label and dimensions to new values before forwarding
  44. /// to the upstream factory.
  45. public init(
  46. upstream: Upstream,
  47. transform: @escaping @Sendable (String, [(String, String)]) -> (String, [(String, String)])
  48. ) {
  49. self.upstream = upstream
  50. self.transform = transform
  51. }
  52. }
  53. extension MetricsFactory {
  54. /// Create a new ``MappingMetricsFactory`` that applies the given transformation to all metrics
  55. /// created through it.
  56. ///
  57. /// - parameters:
  58. /// - transform: A closure that maps the label and dimensions to new values.
  59. /// - returns: A ``MappingMetricsFactory`` wrapping this factory with the given transformation.
  60. public func withLabelAndDimensionsMapping(
  61. _ transform:
  62. @escaping @Sendable (
  63. String, [(String, String)]
  64. ) -> (String, [(String, String)])
  65. ) -> MappingMetricsFactory<Self> {
  66. MappingMetricsFactory(upstream: self, transform: transform)
  67. }
  68. }
  69. extension MappingMetricsFactory: MetricsFactory {
  70. /// Create a backing counter handler with transformed label and dimensions.
  71. ///
  72. /// - parameters:
  73. /// - label: The label for the `CounterHandler`.
  74. /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
  75. public func makeCounter(
  76. label: String,
  77. dimensions: [(String, String)]
  78. ) -> CounterHandler {
  79. let (newLabel, newDimensions) = self.transform(label, dimensions)
  80. return self.upstream.makeCounter(label: newLabel, dimensions: newDimensions)
  81. }
  82. /// Create a backing floating-point counter handler with transformed label and dimensions.
  83. ///
  84. /// - parameters:
  85. /// - label: The label for the `FloatingPointCounterHandler`.
  86. /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
  87. public func makeFloatingPointCounter(
  88. label: String,
  89. dimensions: [(String, String)]
  90. ) -> FloatingPointCounterHandler {
  91. let (newLabel, newDimensions) = self.transform(label, dimensions)
  92. return self.upstream.makeFloatingPointCounter(label: newLabel, dimensions: newDimensions)
  93. }
  94. /// Create a backing meter handler with transformed label and dimensions.
  95. ///
  96. /// - parameters:
  97. /// - label: The label for the `MeterHandler`.
  98. /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
  99. public func makeMeter(
  100. label: String,
  101. dimensions: [(String, String)]
  102. ) -> MeterHandler {
  103. let (newLabel, newDimensions) = self.transform(label, dimensions)
  104. return self.upstream.makeMeter(label: newLabel, dimensions: newDimensions)
  105. }
  106. /// Create a backing recorder handler with transformed label and dimensions.
  107. ///
  108. /// - parameters:
  109. /// - label: The label for the `RecorderHandler`.
  110. /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
  111. /// - aggregate: A Boolean value that indicates whether to aggregate values.
  112. public func makeRecorder(
  113. label: String,
  114. dimensions: [(String, String)],
  115. aggregate: Bool
  116. ) -> RecorderHandler {
  117. let (newLabel, newDimensions) = self.transform(label, dimensions)
  118. return self.upstream.makeRecorder(label: newLabel, dimensions: newDimensions, aggregate: aggregate)
  119. }
  120. /// Create a backing timer handler with transformed label and dimensions.
  121. ///
  122. /// - parameters:
  123. /// - label: The label for the `TimerHandler`.
  124. /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
  125. public func makeTimer(
  126. label: String,
  127. dimensions: [(String, String)]
  128. ) -> TimerHandler {
  129. let (newLabel, newDimensions) = self.transform(label, dimensions)
  130. return self.upstream.makeTimer(label: newLabel, dimensions: newDimensions)
  131. }
  132. /// Invoked when the corresponding counter's `destroy()` function is invoked.
  133. ///
  134. /// - parameters:
  135. /// - handler: The handler to be destroyed.
  136. public func destroyCounter(_ handler: CounterHandler) {
  137. self.upstream.destroyCounter(handler)
  138. }
  139. /// Invoked when the corresponding meter's `destroy()` function is invoked.
  140. ///
  141. /// - parameters:
  142. /// - handler: The handler to be destroyed.
  143. public func destroyMeter(_ handler: MeterHandler) {
  144. self.upstream.destroyMeter(handler)
  145. }
  146. /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
  147. ///
  148. /// - parameters:
  149. /// - handler: The handler to be destroyed.
  150. public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
  151. self.upstream.destroyFloatingPointCounter(handler)
  152. }
  153. /// Invoked when the corresponding recorder's `destroy()` function is invoked.
  154. ///
  155. /// - parameters:
  156. /// - handler: The handler to be destroyed.
  157. public func destroyRecorder(_ handler: RecorderHandler) {
  158. self.upstream.destroyRecorder(handler)
  159. }
  160. /// Invoked when the corresponding timer's `destroy()` function is invoked.
  161. ///
  162. /// - parameters:
  163. /// - handler: The handler to be destroyed.
  164. public func destroyTimer(_ handler: TimerHandler) {
  165. self.upstream.destroyTimer(handler)
  166. }
  167. }