| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift Metrics API open source project
- //
- // Copyright (c) 2018-2026 Apple Inc. and the Swift Metrics API project authors
- // Licensed under Apache License v2.0
- //
- // See LICENSE.txt for license information
- // See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
- //
- // SPDX-License-Identifier: Apache-2.0
- //
- //===----------------------------------------------------------------------===//
- /// A metrics factory that transforms labels and dimensions before forwarding to an upstream factory.
- ///
- /// `MappingMetricsFactory` wraps an existing ``MetricsFactory`` and applies a transformation to the label
- /// and dimensions of every metric before creating it in the upstream factory. This is useful for adding
- /// common dimensions (for example, service name, environment), renaming labels, or filtering dimensions
- /// across all metrics created through this factory.
- ///
- /// ```swift
- /// let factory = upstream.withLabelAndDimensionsMapping { label, dimensions in
- /// (label, dimensions + [("service", "my-service")])
- /// }
- /// let counter = Counter(label: "request_count", dimensions: [("method", "GET")], factory: factory)
- /// counter.increment()
- /// // The upstream factory sees dimensions [("method", "GET"), ("service", "my-service")]
- /// ```
- ///
- /// - Note: The transformation only affects what the upstream factory receives. The metric object itself
- /// (for example, `Counter.label`, `Counter.dimensions`) retains the original values passed at creation
- /// time. This means the label you see on the metric handle may differ from the label stored in the
- /// backend. Keeping the original label on the metric object helps when debugging local code. This
- /// discrepancy only requires attention when correlating local metric handles with data in a remote
- /// backend.
- public struct MappingMetricsFactory<Upstream: MetricsFactory>: Sendable {
- private var upstream: Upstream
- private var transform: @Sendable (String, [(String, String)]) -> (String, [(String, String)])
- /// Create a new `MappingMetricsFactory`.
- ///
- /// - parameters:
- /// - upstream: The upstream ``MetricsFactory`` to forward metric creation to after transformation.
- /// - transform: A closure that maps the label and dimensions to new values before forwarding
- /// to the upstream factory.
- public init(
- upstream: Upstream,
- transform: @escaping @Sendable (String, [(String, String)]) -> (String, [(String, String)])
- ) {
- self.upstream = upstream
- self.transform = transform
- }
- }
- extension MetricsFactory {
- /// Create a new ``MappingMetricsFactory`` that applies the given transformation to all metrics
- /// created through it.
- ///
- /// - parameters:
- /// - transform: A closure that maps the label and dimensions to new values.
- /// - returns: A ``MappingMetricsFactory`` wrapping this factory with the given transformation.
- public func withLabelAndDimensionsMapping(
- _ transform:
- @escaping @Sendable (
- String, [(String, String)]
- ) -> (String, [(String, String)])
- ) -> MappingMetricsFactory<Self> {
- MappingMetricsFactory(upstream: self, transform: transform)
- }
- }
- extension MappingMetricsFactory: MetricsFactory {
- /// Create a backing counter handler with transformed label and dimensions.
- ///
- /// - parameters:
- /// - label: The label for the `CounterHandler`.
- /// - dimensions: The dimensions for the `CounterHandler`, as `(name, value)` tuples.
- public func makeCounter(
- label: String,
- dimensions: [(String, String)]
- ) -> CounterHandler {
- let (newLabel, newDimensions) = self.transform(label, dimensions)
- return self.upstream.makeCounter(label: newLabel, dimensions: newDimensions)
- }
- /// Create a backing floating-point counter handler with transformed label and dimensions.
- ///
- /// - parameters:
- /// - label: The label for the `FloatingPointCounterHandler`.
- /// - dimensions: The dimensions for the `FloatingPointCounterHandler`, as `(name, value)` tuples.
- public func makeFloatingPointCounter(
- label: String,
- dimensions: [(String, String)]
- ) -> FloatingPointCounterHandler {
- let (newLabel, newDimensions) = self.transform(label, dimensions)
- return self.upstream.makeFloatingPointCounter(label: newLabel, dimensions: newDimensions)
- }
- /// Create a backing meter handler with transformed label and dimensions.
- ///
- /// - parameters:
- /// - label: The label for the `MeterHandler`.
- /// - dimensions: The dimensions for the `MeterHandler`, as `(name, value)` tuples.
- public func makeMeter(
- label: String,
- dimensions: [(String, String)]
- ) -> MeterHandler {
- let (newLabel, newDimensions) = self.transform(label, dimensions)
- return self.upstream.makeMeter(label: newLabel, dimensions: newDimensions)
- }
- /// Create a backing recorder handler with transformed label and dimensions.
- ///
- /// - parameters:
- /// - label: The label for the `RecorderHandler`.
- /// - dimensions: The dimensions for the `RecorderHandler`, as `(name, value)` tuples.
- /// - aggregate: A Boolean value that indicates whether to aggregate values.
- public func makeRecorder(
- label: String,
- dimensions: [(String, String)],
- aggregate: Bool
- ) -> RecorderHandler {
- let (newLabel, newDimensions) = self.transform(label, dimensions)
- return self.upstream.makeRecorder(label: newLabel, dimensions: newDimensions, aggregate: aggregate)
- }
- /// Create a backing timer handler with transformed label and dimensions.
- ///
- /// - parameters:
- /// - label: The label for the `TimerHandler`.
- /// - dimensions: The dimensions for the `TimerHandler`, as `(name, value)` tuples.
- public func makeTimer(
- label: String,
- dimensions: [(String, String)]
- ) -> TimerHandler {
- let (newLabel, newDimensions) = self.transform(label, dimensions)
- return self.upstream.makeTimer(label: newLabel, dimensions: newDimensions)
- }
- /// Invoked when the corresponding counter's `destroy()` function is invoked.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyCounter(_ handler: CounterHandler) {
- self.upstream.destroyCounter(handler)
- }
- /// Invoked when the corresponding meter's `destroy()` function is invoked.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyMeter(_ handler: MeterHandler) {
- self.upstream.destroyMeter(handler)
- }
- /// Invoked when the corresponding floating-point counter's `destroy()` function is invoked.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyFloatingPointCounter(_ handler: FloatingPointCounterHandler) {
- self.upstream.destroyFloatingPointCounter(handler)
- }
- /// Invoked when the corresponding recorder's `destroy()` function is invoked.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyRecorder(_ handler: RecorderHandler) {
- self.upstream.destroyRecorder(handler)
- }
- /// Invoked when the corresponding timer's `destroy()` function is invoked.
- ///
- /// - parameters:
- /// - handler: The handler to be destroyed.
- public func destroyTimer(_ handler: TimerHandler) {
- self.upstream.destroyTimer(handler)
- }
- }
|