@@ -20,9 +20,13 @@ import cats.data.NonEmptyList
2020import cats .effect .Resource
2121import cats .effect .Sync
2222import cats .syntax .all ._
23+ import io .prometheus .client .CollectorRegistry
2324import io .prometheus .client ._
2425import org .http4s .Method
2526import org .http4s .Status
27+ import org .http4s .metrics .CustomLabels
28+ import org .http4s .metrics .CustomMetricsOps
29+ import org .http4s .metrics .EmptyCustomLabels
2630import org .http4s .metrics .MetricsOps
2731import org .http4s .metrics .TerminationType
2832import org .http4s .metrics .TerminationType .Abnormal
@@ -31,6 +35,7 @@ import org.http4s.metrics.TerminationType.Error
3135import org .http4s .metrics .TerminationType .Timeout
3236import org .http4s .metrics .prometheus .Prometheus .registerCollector
3337import org .http4s .metrics .prometheus .Prometheus .toFlatArray
38+ import org .http4s .util .SizedSeq
3439
3540/** [[MetricsOps ]] algebra capable of recording Prometheus metrics
3641 *
@@ -88,22 +93,19 @@ final class Prometheus[F[_]: Sync] private (
8893 private val prefix : String ,
8994 private val registry : CollectorRegistry ,
9095 private val sampleExemplar : F [Option [Map [String , String ]]],
91- private val customLabelsAndValues : List [(String , String )],
9296 private val responseDurationSecondsHistogramBuckets : NonEmptyList [Double ],
9397) { self =>
9498 private def copy (
9599 prefix : String = self.prefix,
96100 registry : CollectorRegistry = self.registry,
97101 sampleExemplar : F [Option [Map [String , String ]]] = self.sampleExemplar,
98- customLabelsAndValues : List [(String , String )] = self.customLabelsAndValues,
99102 responseDurationSecondsHistogramBuckets : NonEmptyList [Double ] =
100103 self.responseDurationSecondsHistogramBuckets,
101104 ): Prometheus [F ] =
102105 new Prometheus [F ](
103106 prefix = prefix,
104107 registry = registry,
105108 sampleExemplar = sampleExemplar,
106- customLabelsAndValues = customLabelsAndValues,
107109 responseDurationSecondsHistogramBuckets = responseDurationSecondsHistogramBuckets,
108110 )
109111
@@ -113,41 +115,52 @@ final class Prometheus[F[_]: Sync] private (
113115 def withSampleExemplar (sampleExemplar : F [Option [Map [String , String ]]]): Prometheus [F ] =
114116 copy(sampleExemplar = sampleExemplar)
115117
116- def withCustomLabelsAndValues (
117- customLabelsAndValues : List [(String , String )]
118- ): Prometheus [F ] = copy(customLabelsAndValues = customLabelsAndValues)
119-
120118 def withResponseDurationSecondsHistogramBuckets (
121119 responseDurationSecondsHistogramBuckets : NonEmptyList [Double ]
122120 ): Prometheus [F ] =
123121 copy(responseDurationSecondsHistogramBuckets = responseDurationSecondsHistogramBuckets)
124122
125123 /** Build a [[MetricsOps ]] that supports Prometheus metrics */
126- def build : Resource [F , MetricsOps [F ]] = createMetricsCollection.map(createMetricsOps)
124+ def build : Resource [F , MetricsOps [F ]] = buildCustomMetricsOps(EmptyCustomLabels ())
125+
126+ def buildCustomMetricsOps [SL <: SizedSeq [String ]](
127+ pCustomLabels : CustomLabels [SL ]
128+ ): Resource [F , CustomMetricsOps [F , SL ]] =
129+ createMetricsCollection(pCustomLabels).map(createMetricsOps(pCustomLabels))
127130
128- private def createMetricsOps (metrics : MetricsCollection ): MetricsOps [F ] = {
129- val customLabelValues : List [String ] = customLabelsAndValues.map(_._2)
131+ private def createMetricsOps [SL <: SizedSeq [String ]](pCustomLabels : CustomLabels [SL ])(
132+ metrics : MetricsCollection [SL ]
133+ ): CustomMetricsOps [F , SL ] = {
130134 val exemplarLabels : F [Option [Array [String ]]] = sampleExemplar.map(_.map(toFlatArray))
131135
132- new MetricsOps [F ] {
133- override def increaseActiveRequests (classifier : Option [String ]): F [Unit ] =
136+ new CustomMetricsOps [F , SL ] {
137+ override def definingCustomLabels : CustomLabels [SL ] = pCustomLabels
138+
139+ override def increaseActiveRequests (
140+ classifier : Option [String ],
141+ customLabelValues : SL ,
142+ ): F [Unit ] =
134143 Sync [F ].delay {
135144 metrics.activeRequests
136- .labels(label(classifier) +: customLabelValues : _* )
145+ .labels(label(classifier) +: customLabelValues.toSeq : _* )
137146 .inc()
138147 }
139148
140- override def decreaseActiveRequests (classifier : Option [String ]): F [Unit ] =
149+ override def decreaseActiveRequests (
150+ classifier : Option [String ],
151+ customLabelValues : SL ,
152+ ): F [Unit ] =
141153 Sync [F ].delay {
142154 metrics.activeRequests
143- .labels(label(classifier) +: customLabelValues : _* )
155+ .labels(label(classifier) +: customLabelValues.toSeq : _* )
144156 .dec()
145157 }
146158
147159 override def recordHeadersTime (
148160 method : Method ,
149161 elapsed : Long ,
150162 classifier : Option [String ],
163+ customLabelValues : SL ,
151164 ): F [Unit ] =
152165 exemplarLabels.flatMap { exemplarOpt =>
153166 Sync [F ].delay {
@@ -156,7 +169,7 @@ final class Prometheus[F[_]: Sync] private (
156169 label(classifier) +:
157170 reportMethod(method) +:
158171 Phase .report(Phase .Headers ) +:
159- customLabelValues : _*
172+ customLabelValues.toSeq : _*
160173 )
161174 .observeWithExemplar(
162175 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -170,6 +183,7 @@ final class Prometheus[F[_]: Sync] private (
170183 status : Status ,
171184 elapsed : Long ,
172185 classifier : Option [String ],
186+ customLabelValues : SL ,
173187 ): F [Unit ] =
174188 exemplarLabels.flatMap { exemplarOpt =>
175189 Sync [F ].delay {
@@ -178,7 +192,7 @@ final class Prometheus[F[_]: Sync] private (
178192 label(classifier) +:
179193 reportMethod(method) +:
180194 Phase .report(Phase .Body ) +:
181- customLabelValues : _*
195+ customLabelValues.toSeq : _*
182196 )
183197 .observeWithExemplar(
184198 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -189,7 +203,7 @@ final class Prometheus[F[_]: Sync] private (
189203 label(classifier) +:
190204 reportMethod(method) +:
191205 reportStatus(status) +:
192- customLabelValues : _*
206+ customLabelValues.toSeq : _*
193207 )
194208 .incWithExemplar(exemplarOpt.orNull: _* )
195209 }
@@ -199,23 +213,28 @@ final class Prometheus[F[_]: Sync] private (
199213 elapsed : Long ,
200214 terminationType : TerminationType ,
201215 classifier : Option [String ],
216+ customLabelValues : SL ,
202217 ): F [Unit ] =
203218 terminationType match {
204- case Abnormal (e) => recordAbnormal(elapsed, classifier, e)
205- case Error (e) => recordError(elapsed, classifier, e)
206- case Canceled => recordCanceled(elapsed, classifier)
207- case Timeout => recordTimeout(elapsed, classifier)
219+ case Abnormal (e) => recordAbnormal(elapsed, classifier, customLabelValues, e)
220+ case Error (e) => recordError(elapsed, classifier, customLabelValues, e)
221+ case Canceled => recordCanceled(elapsed, classifier, customLabelValues )
222+ case Timeout => recordTimeout(elapsed, classifier, customLabelValues )
208223 }
209224
210- private def recordCanceled (elapsed : Long , classifier : Option [String ]): F [Unit ] =
225+ private def recordCanceled (
226+ elapsed : Long ,
227+ classifier : Option [String ],
228+ customLabelValues : SL ,
229+ ): F [Unit ] =
211230 exemplarLabels.flatMap { exemplarOpt =>
212231 Sync [F ].delay {
213232 metrics.abnormalTerminations
214233 .labels(
215234 label(classifier) +:
216235 AbnormalTermination .report(AbnormalTermination .Canceled ) +:
217236 label(Option .empty) +:
218- customLabelValues : _*
237+ customLabelValues.toSeq : _*
219238 )
220239 .observeWithExemplar(
221240 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -227,6 +246,7 @@ final class Prometheus[F[_]: Sync] private (
227246 private def recordAbnormal (
228247 elapsed : Long ,
229248 classifier : Option [String ],
249+ customLabelValues : SL ,
230250 cause : Throwable ,
231251 ): F [Unit ] =
232252 exemplarLabels.flatMap { exemplarOpt =>
@@ -236,7 +256,7 @@ final class Prometheus[F[_]: Sync] private (
236256 label(classifier) +:
237257 AbnormalTermination .report(AbnormalTermination .Abnormal ) +:
238258 label(Option (cause.getClass.getName)) +:
239- customLabelValues : _*
259+ customLabelValues.toSeq : _*
240260 )
241261 .observeWithExemplar(
242262 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -248,6 +268,7 @@ final class Prometheus[F[_]: Sync] private (
248268 private def recordError (
249269 elapsed : Long ,
250270 classifier : Option [String ],
271+ customLabelValues : SL ,
251272 cause : Throwable ,
252273 ): F [Unit ] =
253274 exemplarLabels.flatMap { exemplarOpt =>
@@ -257,7 +278,7 @@ final class Prometheus[F[_]: Sync] private (
257278 label(classifier) +:
258279 AbnormalTermination .report(AbnormalTermination .Error ) +:
259280 label(Option (cause.getClass.getName)) +:
260- customLabelValues : _*
281+ customLabelValues.toSeq : _*
261282 )
262283 .observeWithExemplar(
263284 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -266,15 +287,19 @@ final class Prometheus[F[_]: Sync] private (
266287 }
267288 }
268289
269- private def recordTimeout (elapsed : Long , classifier : Option [String ]): F [Unit ] =
290+ private def recordTimeout (
291+ elapsed : Long ,
292+ classifier : Option [String ],
293+ customLabelValues : SL ,
294+ ): F [Unit ] =
270295 exemplarLabels.flatMap { exemplarOpt =>
271296 Sync [F ].delay {
272297 metrics.abnormalTerminations
273298 .labels(
274299 label(classifier) +:
275300 AbnormalTermination .report(AbnormalTermination .Timeout ) +:
276301 label(Option .empty) +:
277- customLabelValues : _*
302+ customLabelValues.toSeq : _*
278303 )
279304 .observeWithExemplar(
280305 SimpleTimer .elapsedSecondsFromNanos(0 , elapsed),
@@ -311,16 +336,18 @@ final class Prometheus[F[_]: Sync] private (
311336 }
312337 }
313338
314- private def createMetricsCollection : Resource [F , MetricsCollection ] = {
315- val customLabels : List [String ] = customLabelsAndValues.map(_._1)
339+ private def createMetricsCollection [SL <: SizedSeq [String ]](
340+ pCustomLabels : CustomLabels [SL ]
341+ ): Resource [F , MetricsCollection [SL ]] = {
342+ val customLabels = pCustomLabels.labels
316343
317344 val responseDuration : Resource [F , Histogram ] = registerCollector(
318345 Histogram
319346 .build()
320347 .buckets(responseDurationSecondsHistogramBuckets.toList: _* )
321348 .name(prefix + " _" + " response_duration_seconds" )
322349 .help(" Response Duration in seconds." )
323- .labelNames(" classifier" +: " method" +: " phase" +: customLabels : _* )
350+ .labelNames(" classifier" +: " method" +: " phase" +: customLabels.toSeq : _* )
324351 .create(),
325352 registry,
326353 )
@@ -330,7 +357,7 @@ final class Prometheus[F[_]: Sync] private (
330357 .build()
331358 .name(prefix + " _" + " active_request_count" )
332359 .help(" Total Active Requests." )
333- .labelNames(" classifier" +: customLabels : _* )
360+ .labelNames(" classifier" +: customLabels.toSeq : _* )
334361 .create(),
335362 registry,
336363 )
@@ -340,7 +367,7 @@ final class Prometheus[F[_]: Sync] private (
340367 .build()
341368 .name(prefix + " _" + " request_count" )
342369 .help(" Total Requests." )
343- .labelNames(" classifier" +: " method" +: " status" +: customLabels : _* )
370+ .labelNames(" classifier" +: " method" +: " status" +: customLabels.toSeq : _* )
344371 .create(),
345372 registry,
346373 )
@@ -350,7 +377,7 @@ final class Prometheus[F[_]: Sync] private (
350377 .build()
351378 .name(prefix + " _" + " abnormal_terminations" )
352379 .help(" Total Abnormal Terminations." )
353- .labelNames(" classifier" +: " termination_type" +: " cause" +: customLabels : _* )
380+ .labelNames(" classifier" +: " termination_type" +: " cause" +: customLabels.toSeq : _* )
354381 .create(),
355382 registry,
356383 )
@@ -432,13 +459,12 @@ object Prometheus {
432459 prefix = " org_http4s_server" ,
433460 registry = registry,
434461 sampleExemplar = Option .empty[Map [String , String ]].pure,
435- customLabelsAndValues = List .empty,
436462 responseDurationSecondsHistogramBuckets = DefaultHistogramBuckets ,
437463 )
438464
439465}
440466
441- final case class MetricsCollection (
467+ final case class MetricsCollection [ SL <: SizedSeq [ String ]] (
442468 responseDuration : Histogram ,
443469 activeRequests : Gauge ,
444470 requests : Counter ,
0 commit comments