@@ -128,14 +128,57 @@ public protocol LabelSanitizer {
128
128
///
129
129
/// See `https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels` for more info.
130
130
public struct PrometheusLabelSanitizer : LabelSanitizer {
131
- let allowedCharacters = " abcdefghijklmnopqrstuvwxyz0123456789_: "
132
-
131
+ private static let uppercaseAThroughZ = UInt8 ( ascii: " A " ) ... UInt8 ( ascii: " Z " )
132
+ private static let lowercaseAThroughZ = UInt8 ( ascii: " a " ) ... UInt8 ( ascii: " z " )
133
+ private static let zeroThroughNine = UInt8 ( ascii: " 0 " ) ... UInt8 ( ascii: " 9 " )
134
+
133
135
public init ( ) { }
134
136
135
137
public func sanitize( _ label: String ) -> String {
136
- return String ( label
137
- . lowercased ( )
138
- . map { ( c: Character ) -> Character in if allowedCharacters. contains ( c) { return c } ; return " _ " } )
138
+ if PrometheusLabelSanitizer . isSanitized ( label) {
139
+ return label
140
+ } else {
141
+ return PrometheusLabelSanitizer . sanitizeLabel ( label)
142
+ }
143
+ }
144
+
145
+ /// Returns a boolean indicating whether the label is already sanitized.
146
+ private static func isSanitized( _ label: String ) -> Bool {
147
+ return label. utf8. allSatisfy ( PrometheusLabelSanitizer . isValidCharacter ( _: ) )
148
+ }
149
+
150
+ /// Returns a boolean indicating whether the character may be used in a label.
151
+ private static func isValidCharacter( _ codePoint: String . UTF8View . Element ) -> Bool {
152
+ switch codePoint {
153
+ case PrometheusLabelSanitizer . lowercaseAThroughZ,
154
+ PrometheusLabelSanitizer . zeroThroughNine,
155
+ UInt8 ( ascii: " : " ) ,
156
+ UInt8 ( ascii: " _ " ) :
157
+ return true
158
+ default :
159
+ return false
160
+ }
161
+ }
162
+
163
+ private static func sanitizeLabel( _ label: String ) -> String {
164
+ let sanitized : [ UInt8 ] = label. utf8. map { character in
165
+ if PrometheusLabelSanitizer . isValidCharacter ( character) {
166
+ return character
167
+ } else {
168
+ return PrometheusLabelSanitizer . sanitizeCharacter ( character)
169
+ }
170
+ }
171
+
172
+ return String ( decoding: sanitized, as: UTF8 . self)
173
+ }
174
+
175
+ private static func sanitizeCharacter( _ character: UInt8 ) -> UInt8 {
176
+ if PrometheusLabelSanitizer . uppercaseAThroughZ. contains ( character) {
177
+ // Uppercase, so shift to lower case.
178
+ return character + ( UInt8 ( ascii: " a " ) - UInt8( ascii: " A " ) )
179
+ } else {
180
+ return UInt8 ( ascii: " _ " )
181
+ }
139
182
}
140
183
}
141
184
@@ -191,13 +234,8 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
191
234
192
235
public func makeCounter( label: String , dimensions: [ ( String , String ) ] ) -> CounterHandler {
193
236
let label = configuration. labelSanitizer. sanitize ( label)
194
- let createHandler = { ( counter: PromCounter ) -> CounterHandler in
195
- return MetricsCounter ( counter: counter, dimensions: dimensions)
196
- }
197
- if let counter: PromCounter < Int64 , DimensionLabels > = client. getMetricInstance ( with: label, andType: . counter) {
198
- return createHandler ( counter)
199
- }
200
- return createHandler ( client. createCounter ( forType: Int64 . self, named: label, withLabelType: DimensionLabels . self) )
237
+ let counter = client. createCounter ( forType: Int64 . self, named: label, withLabelType: DimensionLabels . self)
238
+ return MetricsCounter ( counter: counter, dimensions: dimensions)
201
239
}
202
240
203
241
public func makeRecorder( label: String , dimensions: [ ( String , String ) ] , aggregate: Bool ) -> RecorderHandler {
@@ -207,24 +245,14 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
207
245
208
246
private func makeGauge( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
209
247
let label = configuration. labelSanitizer. sanitize ( label)
210
- let createHandler = { ( gauge: PromGauge ) -> RecorderHandler in
211
- return MetricsGauge ( gauge: gauge, dimensions: dimensions)
212
- }
213
- if let gauge: PromGauge < Double , DimensionLabels > = client. getMetricInstance ( with: label, andType: . gauge) {
214
- return createHandler ( gauge)
215
- }
216
- return createHandler ( client. createGauge ( forType: Double . self, named: label, withLabelType: DimensionLabels . self) )
248
+ let gauge = client. createGauge ( forType: Double . self, named: label, withLabelType: DimensionLabels . self)
249
+ return MetricsGauge ( gauge: gauge, dimensions: dimensions)
217
250
}
218
251
219
252
private func makeHistogram( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
220
253
let label = configuration. labelSanitizer. sanitize ( label)
221
- let createHandler = { ( histogram: PromHistogram ) -> RecorderHandler in
222
- return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
223
- }
224
- if let histogram: PromHistogram < Double , DimensionHistogramLabels > = client. getMetricInstance ( with: label, andType: . histogram) {
225
- return createHandler ( histogram)
226
- }
227
- return createHandler ( client. createHistogram ( forType: Double . self, named: label, labels: DimensionHistogramLabels . self) )
254
+ let histogram = client. createHistogram ( forType: Double . self, named: label, labels: DimensionHistogramLabels . self)
255
+ return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
228
256
}
229
257
230
258
public func makeTimer( label: String , dimensions: [ ( String , String ) ] ) -> TimerHandler {
@@ -240,26 +268,16 @@ public struct PrometheusMetricsFactory: PrometheusWrappedMetricsFactory {
240
268
/// This method creates `Summary` backed timer implementation
241
269
private func makeSummaryTimer( label: String , dimensions: [ ( String , String ) ] , quantiles: [ Double ] ) -> TimerHandler {
242
270
let label = configuration. labelSanitizer. sanitize ( label)
243
- let createHandler = { ( summary: PromSummary ) -> TimerHandler in
244
- return MetricsSummary ( summary: summary, dimensions: dimensions)
245
- }
246
- if let summary: PromSummary < Int64 , DimensionSummaryLabels > = client. getMetricInstance ( with: label, andType: . summary) {
247
- return createHandler ( summary)
248
- }
249
- return createHandler ( client. createSummary ( forType: Int64 . self, named: label, quantiles: quantiles, labels: DimensionSummaryLabels . self) )
271
+ let summary = client. createSummary ( forType: Int64 . self, named: label, quantiles: quantiles, labels: DimensionSummaryLabels . self)
272
+ return MetricsSummary ( summary: summary, dimensions: dimensions)
250
273
}
251
274
252
275
/// There's two different ways to back swift-api `Timer` with Prometheus classes.
253
276
/// This method creates `Histogram` backed timer implementation
254
277
private func makeHistogramTimer( label: String , dimensions: [ ( String , String ) ] , buckets: Buckets ) -> TimerHandler {
255
- let createHandler = { ( histogram: PromHistogram ) -> TimerHandler in
256
- MetricsHistogramTimer ( histogram: histogram, dimensions: dimensions)
257
- }
258
- // PromHistogram should be reused when created for the same label, so we try to look it up
259
- if let histogram: PromHistogram < Int64 , DimensionHistogramLabels > = client. getMetricInstance ( with: label, andType: . histogram) {
260
- return createHandler ( histogram)
261
- }
262
- return createHandler ( client. createHistogram ( forType: Int64 . self, named: label, buckets: buckets, labels: DimensionHistogramLabels . self) )
278
+ let label = configuration. labelSanitizer. sanitize ( label)
279
+ let histogram = client. createHistogram ( forType: Int64 . self, named: label, buckets: buckets, labels: DimensionHistogramLabels . self)
280
+ return MetricsHistogramTimer ( histogram: histogram, dimensions: dimensions)
263
281
}
264
282
}
265
283
0 commit comments