@@ -32,9 +32,14 @@ public final class StatsdClient: MetricsFactory {
32
32
/// - eventLoopGroupProvider: The `EventLoopGroupProvider` to use, uses`createNew` strategy by default.
33
33
/// - host: The `statsd` server host.
34
34
/// - port: The `statsd` server port.
35
- public init ( eventLoopGroupProvider: EventLoopGroupProvider = . createNew, host: String , port: Int ) throws {
35
+ public init (
36
+ eventLoopGroupProvider: EventLoopGroupProvider = . createNew,
37
+ host: String ,
38
+ port: Int ,
39
+ metricNameSanitizer: @escaping StatsdClient . MetricNameSanitizer = StatsdClient . defaultMetricNameSanitizer
40
+ ) throws {
36
41
let address = try SocketAddress . makeAddressResolvingHost ( host, port: port)
37
- self . client = Client ( eventLoopGroupProvider: eventLoopGroupProvider, address: address)
42
+ self . client = Client ( eventLoopGroupProvider: eventLoopGroupProvider, address: address, metricNameSanitizer : metricNameSanitizer )
38
43
}
39
44
40
45
/// Shutdown the client. This is a noop when using a `shared` `EventLoopGroupProvider` strategy.
@@ -77,7 +82,7 @@ public final class StatsdClient: MetricsFactory {
77
82
}
78
83
79
84
private func make< Item> ( label: String , dimensions: [ ( String , String ) ] , registry: inout [ String : Item ] , maker: ( String , [ ( String , String ) ] ) -> Item ) -> Item {
80
- let id = StatsdUtils . id ( label: label, dimensions: dimensions)
85
+ let id = StatsdUtils . id ( label: label, dimensions: dimensions, sanitizer : self . client . metricNameSanitizer )
81
86
if let item = registry [ id] {
82
87
return item
83
88
}
@@ -128,7 +133,7 @@ private final class StatsdCounter: CounterHandler, Equatable {
128
133
var value = NIOAtomic< Int64> . makeAtomic( value: 0 )
129
134
130
135
init ( label: String , dimensions: [ ( String , String ) ] , client: Client ) {
131
- self . id = StatsdUtils . id ( label: label, dimensions: dimensions)
136
+ self . id = StatsdUtils . id ( label: label, dimensions: dimensions, sanitizer : client . metricNameSanitizer )
132
137
self . client = client
133
138
}
134
139
@@ -174,7 +179,7 @@ private final class StatsdRecorder: RecorderHandler, Equatable {
174
179
let client : Client
175
180
176
181
init ( label: String , dimensions: [ ( String , String ) ] , aggregate: Bool , client: Client ) {
177
- self . id = StatsdUtils . id ( label: label, dimensions: dimensions)
182
+ self . id = StatsdUtils . id ( label: label, dimensions: dimensions, sanitizer : client . metricNameSanitizer )
178
183
self . aggregate = aggregate
179
184
self . client = client
180
185
}
@@ -219,7 +224,7 @@ private final class StatsdTimer: TimerHandler, Equatable {
219
224
let client : Client
220
225
221
226
init ( label: String , dimensions: [ ( String , String ) ] , client: Client ) {
222
- self . id = StatsdUtils . id ( label: label, dimensions: dimensions)
227
+ self . id = StatsdUtils . id ( label: label, dimensions: dimensions, sanitizer : client . metricNameSanitizer )
223
228
self . client = client
224
229
}
225
230
@@ -242,6 +247,8 @@ private final class Client {
242
247
private let eventLoopGroupProvider : StatsdClient . EventLoopGroupProvider
243
248
private let eventLoopGroup : EventLoopGroup
244
249
250
+ internal let metricNameSanitizer : StatsdClient . MetricNameSanitizer
251
+
245
252
private let address : SocketAddress
246
253
247
254
private let isShutdown = NIOAtomic< Bool> . makeAtomic( value: false )
@@ -255,14 +262,19 @@ private final class Client {
255
262
case connected( Channel )
256
263
}
257
264
258
- init ( eventLoopGroupProvider: StatsdClient . EventLoopGroupProvider , address: SocketAddress ) {
265
+ init (
266
+ eventLoopGroupProvider: StatsdClient . EventLoopGroupProvider ,
267
+ address: SocketAddress ,
268
+ metricNameSanitizer: @escaping StatsdClient . MetricNameSanitizer
269
+ ) {
259
270
self . eventLoopGroupProvider = eventLoopGroupProvider
260
271
switch self . eventLoopGroupProvider {
261
272
case . shared( let group) :
262
273
self . eventLoopGroup = group
263
274
case . createNew:
264
275
self . eventLoopGroup = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
265
276
}
277
+ self . metricNameSanitizer = metricNameSanitizer
266
278
self . address = address
267
279
}
268
280
@@ -349,11 +361,43 @@ private final class Client {
349
361
}
350
362
}
351
363
364
+ // MARK: - Metric Name Sanitizer
365
+
366
+ extension StatsdClient {
367
+ /// Used to sanitize labels (and dimensions) into a format compatible with statsd's wire format.
368
+ ///
369
+ /// By default `StatsdClient` uses the `StatsdClient.defaultMetricNameSanitizer`.
370
+ public typealias MetricNameSanitizer = ( String ) -> String
371
+
372
+ /// Default implementation of `LabelSanitizer` that sanitizes any ":" occurrences by replacing them with a replacement character.
373
+ /// Defaults to replacing the illegal characters with "_", e.g. "offending:example" becomes "offending_example".
374
+ ///
375
+ /// See `https://github.com/b/statsd_spec` for more info.
376
+ public static let defaultMetricNameSanitizer : StatsdClient . MetricNameSanitizer = { label in
377
+ let illegalCharacter : Character = " : "
378
+ let replacementCharacter : Character = " _ "
379
+
380
+ guard label. contains ( illegalCharacter) else {
381
+ return label
382
+ }
383
+
384
+ // replacingOccurrences would be used, but is in Foundation which we try to not depend on here
385
+ return String ( label. compactMap { ( c: Character ) -> Character ? in
386
+ c != illegalCharacter ? c : replacementCharacter
387
+ } )
388
+ }
389
+ }
390
+
352
391
// MARK: - Utility
353
392
354
393
private enum StatsdUtils {
355
- static func id( label: String , dimensions: [ ( String , String ) ] ) -> String {
356
- return dimensions. isEmpty ? label : dimensions. reduce ( label) { a, b in " \( a) . \( b. 0 ) . \( b. 1 ) " }
394
+ static func id( label: String , dimensions: [ ( String , String ) ] , sanitizer sanitize: StatsdClient . MetricNameSanitizer ) -> String {
395
+ if dimensions. isEmpty {
396
+ return sanitize ( label)
397
+ } else {
398
+ let labelWithDimensions = dimensions. reduce ( label) { a, b in " \( a) . \( b. 0 ) . \( b. 1 ) " }
399
+ return sanitize ( labelWithDimensions)
400
+ }
357
401
}
358
402
}
359
403
0 commit comments