@@ -86,6 +86,41 @@ private class MetricsSummary: TimerHandler {
86
86
}
87
87
}
88
88
89
+ /// Used to sanitize labels into a format compatible with Prometheus label requirements.
90
+ /// Useful when using `PrometheusMetrics` via `SwiftMetrics` with clients which do not necessarily know
91
+ /// about prometheus label formats, and may be using e.g. `.` or upper-case letters in labels (which Prometheus
92
+ /// does not allow).
93
+ ///
94
+ /// let sanitizer: LabelSanitizer = ...
95
+ /// let prometheusLabel = sanitizer.sanitize(nonPrometheusLabel)
96
+ ///
97
+ /// By default `PrometheusLabelSanitizer` is used by `PrometheusClient`
98
+ public protocol LabelSanitizer {
99
+ /// Sanitize the passed in label to a Prometheus accepted value.
100
+ ///
101
+ /// - parameters:
102
+ /// - label: The created label that needs to be sanitized.
103
+ ///
104
+ /// - returns: A sanitized string that a Prometheus backend will accept.
105
+ func sanitize( _ label: String ) -> String
106
+ }
107
+
108
+ /// Default implementation of `LabelSanitizer` that sanitizes any characters not
109
+ /// allowed by Prometheus to an underscore (`_`).
110
+ ///
111
+ /// See `https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels` for more info.
112
+ public struct PrometheusLabelSanitizer : LabelSanitizer {
113
+ let allowedCharacters = " abcdefghijklmnopqrstuvwxyz0123456789_: "
114
+
115
+ public init ( ) { }
116
+
117
+ public func sanitize( _ label: String ) -> String {
118
+ return String ( label
119
+ . lowercased ( )
120
+ . map { ( c: Character ) -> Character in if allowedCharacters. contains ( c) { return c } ; return " _ " } )
121
+ }
122
+ }
123
+
89
124
extension PrometheusClient : MetricsFactory {
90
125
public func destroyCounter( _ handler: CounterHandler ) {
91
126
guard let handler = handler as? MetricsCounter else { return }
@@ -107,6 +142,7 @@ extension PrometheusClient: MetricsFactory {
107
142
}
108
143
109
144
public func makeCounter( label: String , dimensions: [ ( String , String ) ] ) -> CounterHandler {
145
+ let label = self . sanitizer. sanitize ( label)
110
146
let createHandler = { ( counter: PromCounter ) -> CounterHandler in
111
147
return MetricsCounter ( counter: counter, dimensions: dimensions)
112
148
}
@@ -117,10 +153,12 @@ extension PrometheusClient: MetricsFactory {
117
153
}
118
154
119
155
public func makeRecorder( label: String , dimensions: [ ( String , String ) ] , aggregate: Bool ) -> RecorderHandler {
156
+ let label = self . sanitizer. sanitize ( label)
120
157
return aggregate ? makeHistogram ( label: label, dimensions: dimensions) : makeGauge ( label: label, dimensions: dimensions)
121
158
}
122
159
123
160
private func makeGauge( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
161
+ let label = self . sanitizer. sanitize ( label)
124
162
let createHandler = { ( gauge: PromGauge ) -> RecorderHandler in
125
163
return MetricsGauge ( gauge: gauge, dimensions: dimensions)
126
164
}
@@ -131,6 +169,7 @@ extension PrometheusClient: MetricsFactory {
131
169
}
132
170
133
171
private func makeHistogram( label: String , dimensions: [ ( String , String ) ] ) -> RecorderHandler {
172
+ let label = self . sanitizer. sanitize ( label)
134
173
let createHandler = { ( histogram: PromHistogram ) -> RecorderHandler in
135
174
return MetricsHistogram ( histogram: histogram, dimensions: dimensions)
136
175
}
@@ -141,6 +180,7 @@ extension PrometheusClient: MetricsFactory {
141
180
}
142
181
143
182
public func makeTimer( label: String , dimensions: [ ( String , String ) ] ) -> TimerHandler {
183
+ let label = self . sanitizer. sanitize ( label)
144
184
let createHandler = { ( summary: PromSummary ) -> TimerHandler in
145
185
return MetricsSummary ( summary: summary, dimensions: dimensions)
146
186
}
0 commit comments