Metric Factory

The MetricFactory creates an entrypoint to the Metric DSL from an instance of MetricRegistry, with the option of also creating from a CallbackRegistry.

The following examples assume that you have the following imports in scope and that you have constructed a MetricRegistry and CallbackRegistry.

import cats.effect._
import prometheus4cats._

val metricRegistry: MetricRegistry[IO] = MetricRegistry.noop[IO]
val callbackRegistry: CallbackRegistry[IO] = CallbackRegistry.noop[IO]
val registry: MetricRegistry[IO] with CallbackRegistry[IO] = null

MetricFactory or MetricFactory.WithCallbacks

There are two variants of Metric Factory: MetricFactory and MetricFactory.WithCallbacks, the latter extending and providing the same functionality as the former but with the ability to pass callbacks to the DSL.

Whether you are able to obtain a MetricFactory.WithCallbacks depends on two things:

Constructing a No-op MetricFactory

For testing purposes it is possible to construct a MetricFactory that create metrics who perform no operations.

To obtain a no-op instance use the snippet below:

MetricFactory.noop[IO]

A no-op MetricsFactory.WithCallabacks can be obtained via either of the methods shown below:

MetricFactory.WithCallbacks.noop[IO]
MetricFactory.builder.noop[IO]

Constructing from a MetricRegistry

MetricRegistry provides a builder with a fluent API that allows you to create an instance that adds an optional prefix and/or common label set to all metrics.

MetricFactory
  .builder
  .build(metricRegistry)
// res3: MetricFactory[IO[A]] = prometheus4cats.MetricFactory$Builder$$anon$37@46caa635

Literal prefixes are checked at compile time to ensure they conform to the OpenMetrics format. Alternatively you may provide an instance of Metric.Prefix that has been refined at runtime.

MetricFactory
  .builder
  .withPrefix("app_name")
  .build(metricRegistry)

Common labels are a set of labels that are checked at runtime so that the label names conform to the OpenMetrics

format and no more than ten are defined at any one time, which helps to reduce cardinality.

There is no compile time checking of these labels as it is assumed they will come from the runtime environment.

val commonLabels: Either[String, Metric.CommonLabels] =
  Metric.CommonLabels.ofStrings("name" -> "value")

commonLabels.map { labels =>
  MetricFactory
    .builder
    .withCommonLabels(labels)
    .build(metricRegistry)
}

Constructing from a CallbackRegistry

To construct with a CallbackRegistry you also need a MetricRegistry, these may be separate or the same class.

The same builder is used as above, but the type of metrics factory built will be a MetricsFactory.WithCallbacks if you provide a CallbackRegistry:

MetricFactory
  .builder
  .build(metricRegistry, callbackRegistry)
// res6: MetricFactory.WithCallbacks[IO[A]] = prometheus4cats.MetricFactory$Builder$$anon$38@7d8b937c

Alternatively:

MetricFactory
  .builder
  .build(registry)
// res7: MetricFactory.WithCallbacks[IO[A]] = prometheus4cats.MetricFactory$Builder$$anon$39@23e9ebad

As above, you may add CommonLabels and a Prefix as you see fit.

Changing a MetricFactory

It is possible to obtain a new instance of a MetricFactory from an existing one with a different/no prefix or common labels.

The snippet below showcases all the syntax for modifying MetricFactory:

val newCommonLabels: Either[String, Metric.CommonLabels] =
  Metric.CommonLabels.ofStrings("name2" -> "value2")

newCommonLabels.map { labels =>
  MetricFactory
    .builder
    .build(metricRegistry)
    .dropPrefix
    .withPrefix("different_prefix")
    .dropCommonLabels
    .withCommonLabels(labels)
}

Transforming the Effect Type (mapK)

You may transform the effect type (F) of a MetricsFactory with a Cats natural transformation (~>).

MetricsFactory provides a mapK method to do this, while MetricsFactory.WithCallbacks provides an imapK method, mapK is also available on MetricsFactory.WithCallbacks as it extends MetricsFactory, but you will only ever get a MetricsFactory when calling mapK, whereas imapK will yield MetricsFactory.WithCallbacks. This is due to the nature of a callback needing to be run in the original effect to retrieve the metric value.

import cats.~>
import cats.data.EitherT
import cats.syntax.all._

type F[A] = IO[A]
type G[A] = EitherT[IO, Throwable, A]

val fk: F ~> G = EitherT.liftK[F, Throwable]
// fk: F ~> G = cats.data.EitherT$$anon$1@436c6cf

val gk: G ~> F = new (G ~> F) {
  def apply[A](fa: G[A]): F[A] = fa.rethrowT
}
// gk: G ~> F = repl.MdocSession$App$$anon$1@175e2372

val factoryWithCallbacksF = MetricFactory.builder.build(metricRegistry, callbackRegistry)
// factoryWithCallbacksF: MetricFactory.WithCallbacks[IO[A]] = prometheus4cats.MetricFactory$Builder$$anon$38@366f4296

val factoryG = factoryWithCallbacksF.mapK(fk)
// factoryG: MetricFactory[G[A]] = prometheus4cats.MetricFactory$$anon$1@5a377375

val factoryWithCallbacksG = factoryWithCallbacksF.imapK(fk, gk)
// factoryWithCallbacksG: MetricFactory.WithCallbacks[G[A]] = prometheus4cats.MetricFactory$WithCallbacks$$anon$14@6805d642

It is also possible to construct a MetricsFactory.WithCallbacks from a MetricsFactory and CallbackRegistry:

val callbackRegistryG: CallbackRegistry[G] = CallbackRegistry.noop[G]
// callbackRegistryG: CallbackRegistry[G] = prometheus4cats.CallbackRegistry$$anon$1@ea63d83

MetricFactory.builder.build(factoryG, callbackRegistryG)
// res9: MetricFactory.WithCallbacks[G[A]] = prometheus4cats.MetricFactory$Builder$$anon$40@4291b533