Skip to content
This repository has been archived by the owner on Dec 19, 2023. It is now read-only.

Commit

Permalink
Change package name, port http4s (#22)
Browse files Browse the repository at this point in the history
* Change package name, port http4s

* Port http4s tests

* Move dirs to match packages

* format

* Update readme

---------

Co-authored-by: Keir Lawson <[email protected]>
  • Loading branch information
keirlawson and Keir Lawson authored Oct 31, 2023
1 parent 6502ff5 commit 52c8782
Show file tree
Hide file tree
Showing 16 changed files with 1,219 additions and 65 deletions.
60 changes: 54 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ For comprehensive API documentation check [the scaladoc](https://ovotech.github.
A simple usage example for incrementing a counter, backed by a Micrometer `SimpleMeterRegistry`:

```scala
import com.ovoenergy.meters4s.{Reporter, MetricsConfig}
import meters4s.{Reporter, MetricsConfig}
import cats.effect.IO

val config = MetricsConfig()
Expand All @@ -60,8 +60,8 @@ for {
### With Datadog

```scala
import com.ovoenergy.meters4s.{MetricsConfig, Reporter}
import com.ovoenergy.meters4s.datadog.{DataDog, DataDogConfig}
import meters4s.{MetricsConfig, Reporter}
import meters4s.datadog.{DataDog, DataDogConfig}
import cats.effect.IO

val datadog =
Expand All @@ -81,8 +81,8 @@ import cats.effect._
import cats.effect.std.Console
import cats.effect.syntax.all._
import cats.syntax.all._
import com.ovoenergy.meter4s.prometheus._
import com.ovoenergy.meters4s.{MetricsConfig, Reporter}
import meter4s.prometheus._
import meters4s.{MetricsConfig, Reporter}
import io.micrometer.core.instrument.binder.system.ProcessorMetrics

import scala.concurrent.duration._
Expand Down Expand Up @@ -118,7 +118,55 @@ object PromExampleApp extends IOApp {
}
```

## HTTP4S

`meters4s-http4s` implements [http4s](https://http4s.org/) metrics.

This module records the following meters:

- Timer `default.response-time`
- Timer `default.response-headers-time`
- Gauge `default.active-requests`

The `default.response-time` timer has the `status-code`, `method` and `termination` tags.
The `default.response-headers-time` timer has the `method` tag.
The `default.active-requests` does not have any tag.

In addition to these tags, each metric will record the global tags set in the Config.

It is also possible to set a prefix for the metrics name using the `prefix` configuration setting.

The `default` name can be customised using a classifier function. With the same classifier function, it is possible to record additional tags using this syntax: `classifier[tag1:value1,tag2:value2,tag3:value3]`. The classifier part can be blank as well as the tags part can be empty.

The standard tags values are the following:

- statusCode
- 2xx
- 3xx
- 4xx
- 5xx

- method
- head
- get
- put
- patch
- post
- delete
- options
- move
- trace
- connect
- other

- termination
- normal
- abnormals
- error
- timeout


## Inspiration

This library was heavily inspired by (and in some places copied wholesale
from) [http4s-micrometer-metrics](https://github.com/ovotech/http4s-micrometer-metrics).
from) [http4s-micrometer-metrics](https://github.com/ovotech/http4s-micrometer-metrics).
22 changes: 19 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ReleaseTransformations._

lazy val additionalSupportedScalaVersions = List("2.13.10", "2.12.17")
lazy val http4sVersion = "0.23.23"

lazy val additionalSupportedScalaVersions = List("2.13.12", "2.12.18")

lazy val root = (project in file("."))
.settings(
Expand Down Expand Up @@ -34,7 +36,7 @@ lazy val root = (project in file("."))
.enablePlugins(GhpagesPlugin)
.enablePlugins(SiteScaladocPlugin)
.enablePlugins(ScalaUnidocPlugin)
.aggregate(core, datadog, statsd, prometheus, docs)
.aggregate(core, datadog, statsd, prometheus, docs, http4s)

lazy val commonSettings = Seq(
organization := "com.ovoenergy",
Expand Down Expand Up @@ -72,7 +74,7 @@ lazy val publishSettings = Seq(
lazy val commonDependencies = Seq(
"org.typelevel" %% "cats-core" % "2.9.0",
"org.typelevel" %% "cats-effect" % "3.5.0",
"org.typelevel" %% "munit-cats-effect-3" % "1.0.7" % "test",
"org.typelevel" %% "munit-cats-effect" % "2.0.0-M3" % Test,
"io.micrometer" % "micrometer-core" % "1.10.5",
"org.scala-lang.modules" %% "scala-collection-compat" % "2.10.0",
// See https://github.com/micrometer-metrics/micrometer/issues/1133#issuecomment-452434205
Expand Down Expand Up @@ -120,6 +122,20 @@ lazy val prometheus = project
)
.dependsOn(core)

lazy val http4s = project
.settings(
name := "meters4s-http4s",
commonSettings,
publishSettings,
libraryDependencies ++= commonDependencies ++ Seq(
"org.http4s" %% "http4s-core" % http4sVersion,
"org.http4s" %% "http4s-dsl" % http4sVersion % Test,
"org.http4s" %% "http4s-server" % http4sVersion % Test,
"org.http4s" %% "http4s-client" % http4sVersion % Test,
)
)
.dependsOn(core)

lazy val docs = project
.settings(
commonSettings,
Expand Down
39 changes: 0 additions & 39 deletions build/tag.sh

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ovoenergy.meters4s
package meters4s

import cats.effect.Sync
import cats.implicits._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ovoenergy.meters4s
package meters4s

import scala.concurrent.duration._

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ovoenergy.meters4s
package meters4s

import io.micrometer.core.instrument.simple.SimpleMeterRegistry
import cats.effect.IO
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.ovoenergy.meters4s.datadog
package meters4s.datadog

import cats.effect.{Async, Resource, Sync}
import cats.implicits._
import com.ovoenergy.meters4s.{MetricsConfig, Reporter}
import meters4s.{MetricsConfig, Reporter}
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.datadog.{
DatadogMeterRegistry,
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ For comprehensive API documentation check [the scaladoc](https://ovotech.github.
A simple usage example for incrementing a counter, backed by a Micrometer `SimpleMeterRegistry`:

```scala mdoc:silent
import com.ovoenergy.meters4s.{Reporter, MetricsConfig}
import meters4s.{Reporter, MetricsConfig}
import cats.effect.IO
import scala.concurrent.ExecutionContext.Implicits.global

Expand All @@ -61,8 +61,8 @@ for {
### With Datadog

```scala mdoc:silent
import com.ovoenergy.meters4s.{MetricsConfig, Reporter}
import com.ovoenergy.meters4s.datadog.{DataDog, DataDogConfig}
import meters4s.{MetricsConfig, Reporter}
import meters4s.datadog.{DataDog, DataDogConfig}
import cats.effect.IO

val datadog = DataDog.createReporter[IO](DataDogConfig(apiKey = "1234"), MetricsConfig())
Expand Down
129 changes: 129 additions & 0 deletions http4s/src/main/scala/Meters4s.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package meters4s.http4s

import scala.concurrent.duration._

import cats.effect._
import cats.syntax.all._

import org.http4s.metrics.TerminationType._
import org.http4s.metrics.{MetricsOps, TerminationType}
import org.http4s.{Method, Status}
import meters4s.Reporter

object Meters4s {

private val TagsReg = """.*?\[([^\]]*)\]""".r
private val TagReg = """([^:]*)\s*:\s*(.*)""".r

def apply[F[_]: Async](
reporter: Reporter[F],
percentiles: Set[Double] = Set.empty
): MetricsOps[F] =
new MetricsOps[F] {

private def namespace(classifier: Option[String]): String = {
classifier
.map(_.takeWhile(_ != '[').trim)
.filter(_.nonEmpty)
.getOrElse("default")
}

private def name(classifier: Option[String], key: String): String =
s"${namespace(classifier)}.$key"

private def tags(classifier: Option[String]): Map[String, String] = {
classifier
.collect {
case TagsReg(tagsString) if tagsString.trim.nonEmpty =>
tagsString
.split(",")
.collect {
case TagReg(key, value) =>
Map(key -> value)
}
.reduce(_ ++ _)
}
.getOrElse(Map.empty)

}

def increaseActiveRequests(classifier: Option[String]): F[Unit] =
reporter
.gauge(name(classifier, "active-requests"), tags(classifier))
.flatMap(_.increment)

def decreaseActiveRequests(classifier: Option[String]): F[Unit] =
reporter
.gauge(name(classifier, "active-requests"), tags(classifier))
.flatMap(_.decrement)

def recordHeadersTime(
method: Method,
elapsed: Long,
classifier: Option[String]
): F[Unit] =
reporter
.timer(
name(classifier, "response-headers-time"),
tags(classifier) ++ methodTags(method),
percentiles
)
.flatMap(_.record(elapsed.nanos))

def recordAbnormalTermination(
elapsed: Long,
terminationType: TerminationType,
classifier: Option[String]
): F[Unit] = {
val terminationTags = terminationType match {
case Abnormal(_) => "termination" -> "abnormal"
case Error(_) => "termination" -> "error"
case Canceled => "termination" -> "cancelled"
case Timeout => "termination" -> "timeout"
}

recordResponseTime(
classifier,
tags(classifier) ++ Map(terminationTags),
elapsed
)
}
def recordTotalTime(
method: Method,
status: Status,
elapsed: Long,
classifier: Option[String]
): F[Unit] = {
val statusTags = status.responseClass match {
case Status.Informational => "status-code" -> "1xx"
case Status.Successful => "status-code" -> "2xx"
case Status.Redirection => "status-code" -> "3xx"
case Status.ClientError => "status-code" -> "4xx"
case Status.ServerError => "status-code" -> "5xx"
}
val allTags = tags(classifier) ++
Map("termination" -> "normal", statusTags) ++
methodTags(method)

recordResponseTime(
classifier,
allTags,
elapsed
)
}

private def recordResponseTime(
classifier: Option[String],
tags: Map[String, String],
elapsed: Long
): F[Unit] =
reporter
.timer(name(classifier, "response-time"), tags, percentiles)
.flatMap(_.record(elapsed.nanos))

private def methodTags(method: Method): Map[String, String] = Map(
"method" -> method.name.toLowerCase
)

}
}
Loading

0 comments on commit 52c8782

Please sign in to comment.