@@ -86,6 +86,41 @@ private class MetricsSummary: TimerHandler {
8686    } 
8787} 
8888
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+ 
89124extension  PrometheusClient :  MetricsFactory  { 
90125    public  func  destroyCounter( _ handler:  CounterHandler )  { 
91126        guard  let  handler =  handler as?  MetricsCounter  else  {  return  } 
@@ -107,6 +142,7 @@ extension PrometheusClient: MetricsFactory {
107142    } 
108143
109144    public  func  makeCounter( label:  String ,  dimensions:  [ ( String ,  String ) ] )  ->  CounterHandler  { 
145+         let  label  =  self . sanitizer. sanitize ( label) 
110146        let  createHandler  =  {  ( counter:  PromCounter )  ->  CounterHandler  in 
111147            return  MetricsCounter ( counter:  counter,  dimensions:  dimensions) 
112148        } 
@@ -117,10 +153,12 @@ extension PrometheusClient: MetricsFactory {
117153    } 
118154
119155    public  func  makeRecorder( label:  String ,  dimensions:  [ ( String ,  String ) ] ,  aggregate:  Bool )  ->  RecorderHandler  { 
156+         let  label  =  self . sanitizer. sanitize ( label) 
120157        return  aggregate ?  makeHistogram ( label:  label,  dimensions:  dimensions)  :  makeGauge ( label:  label,  dimensions:  dimensions) 
121158    } 
122159
123160    private  func  makeGauge( label:  String ,  dimensions:  [ ( String ,  String ) ] )  ->  RecorderHandler  { 
161+         let  label  =  self . sanitizer. sanitize ( label) 
124162        let  createHandler  =  {  ( gauge:  PromGauge )  ->  RecorderHandler  in 
125163            return  MetricsGauge ( gauge:  gauge,  dimensions:  dimensions) 
126164        } 
@@ -131,6 +169,7 @@ extension PrometheusClient: MetricsFactory {
131169    } 
132170
133171    private  func  makeHistogram( label:  String ,  dimensions:  [ ( String ,  String ) ] )  ->  RecorderHandler  { 
172+         let  label  =  self . sanitizer. sanitize ( label) 
134173        let  createHandler  =  {  ( histogram:  PromHistogram )  ->  RecorderHandler  in 
135174            return  MetricsHistogram ( histogram:  histogram,  dimensions:  dimensions) 
136175        } 
@@ -141,6 +180,7 @@ extension PrometheusClient: MetricsFactory {
141180    } 
142181
143182    public  func  makeTimer( label:  String ,  dimensions:  [ ( String ,  String ) ] )  ->  TimerHandler  { 
183+         let  label  =  self . sanitizer. sanitize ( label) 
144184        let  createHandler  =  {  ( summary:  PromSummary )  ->  TimerHandler  in 
145185            return  MetricsSummary ( summary:  summary,  dimensions:  dimensions) 
146186        } 
0 commit comments