@@ -124,84 +124,6 @@ private class MetricsSummary: TimerHandler {
124124 }
125125}
126126
127- /// Used to sanitize labels into a format compatible with Prometheus label requirements.
128- /// Useful when using `PrometheusMetrics` via `SwiftMetrics` with clients which do not necessarily know
129- /// about prometheus label formats, and may be using e.g. `.` or upper-case letters in labels (which Prometheus
130- /// does not allow).
131- ///
132- /// let sanitizer: LabelSanitizer = ...
133- /// let prometheusLabel = sanitizer.sanitize(nonPrometheusLabel)
134- ///
135- /// By default `PrometheusLabelSanitizer` is used by `PrometheusMetricsFactory`
136- public protocol LabelSanitizer {
137- /// Sanitize the passed in label to a Prometheus accepted value.
138- ///
139- /// - parameters:
140- /// - label: The created label that needs to be sanitized.
141- ///
142- /// - returns: A sanitized string that a Prometheus backend will accept.
143- func sanitize( _ label: String ) -> String
144- }
145-
146- /// Default implementation of `LabelSanitizer` that sanitizes any characters not
147- /// allowed by Prometheus to an underscore (`_`).
148- ///
149- /// See `https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels` for more info.
150- public struct PrometheusLabelSanitizer : LabelSanitizer {
151- private static let uppercaseAThroughZ = UInt8 ( ascii: " A " ) ... UInt8 ( ascii: " Z " )
152- private static let lowercaseAThroughZ = UInt8 ( ascii: " a " ) ... UInt8 ( ascii: " z " )
153- private static let zeroThroughNine = UInt8 ( ascii: " 0 " ) ... UInt8 ( ascii: " 9 " )
154-
155- public init ( ) { }
156-
157- public func sanitize( _ label: String ) -> String {
158- if PrometheusLabelSanitizer . isSanitized ( label) {
159- return label
160- } else {
161- return PrometheusLabelSanitizer . sanitizeLabel ( label)
162- }
163- }
164-
165- /// Returns a boolean indicating whether the label is already sanitized.
166- private static func isSanitized( _ label: String ) -> Bool {
167- return label. utf8. allSatisfy ( PrometheusLabelSanitizer . isValidCharacter ( _: ) )
168- }
169-
170- /// Returns a boolean indicating whether the character may be used in a label.
171- private static func isValidCharacter( _ codePoint: String . UTF8View . Element ) -> Bool {
172- switch codePoint {
173- case PrometheusLabelSanitizer . lowercaseAThroughZ,
174- PrometheusLabelSanitizer . zeroThroughNine,
175- UInt8 ( ascii: " : " ) ,
176- UInt8 ( ascii: " _ " ) :
177- return true
178- default :
179- return false
180- }
181- }
182-
183- private static func sanitizeLabel( _ label: String ) -> String {
184- let sanitized : [ UInt8 ] = label. utf8. map { character in
185- if PrometheusLabelSanitizer . isValidCharacter ( character) {
186- return character
187- } else {
188- return PrometheusLabelSanitizer . sanitizeCharacter ( character)
189- }
190- }
191-
192- return String ( decoding: sanitized, as: UTF8 . self)
193- }
194-
195- private static func sanitizeCharacter( _ character: UInt8 ) -> UInt8 {
196- if PrometheusLabelSanitizer . uppercaseAThroughZ. contains ( character) {
197- // Uppercase, so shift to lower case.
198- return character + ( UInt8 ( ascii: " a " ) - UInt8( ascii: " A " ) )
199- } else {
200- return UInt8 ( ascii: " _ " )
201- }
202- }
203- }
204-
205127/// Defines the base for a bridge between PrometheusClient and swift-metrics.
206128/// Used by `SwiftMetrics.prometheus()` to get an instance of `PrometheusClient` from `MetricsSystem`
207129///
@@ -260,13 +182,13 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
260182 public func makeCounter( label: String , dimensions: [ ( String , String ) ] ) -> CounterHandler {
261183 let label = configuration. labelSanitizer. sanitize ( label)
262184 let counter = client. createCounter ( forType: Int64 . self, named: label, withLabelType: DimensionLabels . self)
263- return MetricsCounter ( counter: counter, dimensions: dimensions)
185+ return MetricsCounter ( counter: counter, dimensions: dimensions. sanitized ( ) )
264186 }
265187
266188 public func makeFloatingPointCounter( label: String , dimensions: [ ( String , String ) ] ) -> FloatingPointCounterHandler {
267189 let label = configuration. labelSanitizer. sanitize ( label)
268190 let counter = client. createCounter ( forType: Double . self, named: label, withLabelType: DimensionLabels . self)
269- return MetricsFloatingPointCounter ( counter: counter, dimensions: dimensions)
191+ return MetricsFloatingPointCounter ( counter: counter, dimensions: dimensions. sanitized ( ) )
270192 }
271193
272194 public func makeRecorder( label: String , dimensions: [ ( String , String ) ] , aggregate: Bool ) -> RecorderHandler {
@@ -277,13 +199,13 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
277199 private func makeGauge( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
278200 let label = configuration. labelSanitizer. sanitize ( label)
279201 let gauge = client. createGauge ( forType: Double . self, named: label, withLabelType: DimensionLabels . self)
280- return MetricsGauge ( gauge: gauge, dimensions: dimensions)
202+ return MetricsGauge ( gauge: gauge, dimensions: dimensions. sanitized ( ) )
281203 }
282204
283205 private func makeHistogram( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
284206 let label = configuration. labelSanitizer. sanitize ( label)
285207 let histogram = client. createHistogram ( forType: Double . self, named: label, labels: DimensionHistogramLabels . self)
286- return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
208+ return MetricsHistogram ( histogram: histogram, dimensions: dimensions. sanitized ( ) )
287209 }
288210
289211 public func makeTimer( label: String , dimensions: [ ( String , String ) ] ) -> TimerHandler {
@@ -300,15 +222,24 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
300222 private func makeSummaryTimer( label: String , dimensions: [ ( String , String ) ] , quantiles: [ Double ] ) -> TimerHandler {
301223 let label = configuration. labelSanitizer. sanitize ( label)
302224 let summary = client. createSummary ( forType: Int64 . self, named: label, quantiles: quantiles, labels: DimensionSummaryLabels . self)
303- return MetricsSummary ( summary: summary, dimensions: dimensions)
225+ return MetricsSummary ( summary: summary, dimensions: dimensions. sanitized ( ) )
304226 }
305227
306228 /// There's two different ways to back swift-api `Timer` with Prometheus classes.
307229 /// This method creates `Histogram` backed timer implementation
308230 private func makeHistogramTimer( label: String , dimensions: [ ( String , String ) ] , buckets: Buckets ) -> TimerHandler {
309231 let label = configuration. labelSanitizer. sanitize ( label)
310232 let histogram = client. createHistogram ( forType: Int64 . self, named: label, buckets: buckets, labels: DimensionHistogramLabels . self)
311- return MetricsHistogramTimer ( histogram: histogram, dimensions: dimensions)
233+ return MetricsHistogramTimer ( histogram: histogram, dimensions: dimensions. sanitized ( ) )
234+ }
235+ }
236+
237+ extension Array where Element == ( String , String ) {
238+ func sanitized( ) -> [ ( String , String ) ] {
239+ let sanitizer = DimensionsSanitizer ( )
240+ return self . map {
241+ ( sanitizer. sanitize ( $0. 0 ) , $0. 1 )
242+ }
312243 }
313244}
314245
0 commit comments