Skip to content

Commit

Permalink
Merge pull request #1 from tpolecat/docs
Browse files Browse the repository at this point in the history
initial docs
  • Loading branch information
tpolecat authored Feb 9, 2021
2 parents 3ac632a + f68ba78 commit f54b455
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 1 deletion.
33 changes: 32 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,35 @@ lazy val examples = project
"org.http4s" %% "http4s-ember-server" % "0.21.15",
"org.slf4j" % "slf4j-simple" % "1.7.30",
).filterNot(_ => isDotty.value)
)
)

lazy val docs = project
.in(file("modules/docs"))
.dependsOn(http4s)
.enablePlugins(AutomateHeaderPlugin)
.enablePlugins(ParadoxPlugin)
.enablePlugins(ParadoxSitePlugin)
.enablePlugins(GhpagesPlugin)
.enablePlugins(MdocPlugin)
.settings(commonSettings)
.settings(
scalacOptions := Nil,
git.remoteRepo := "[email protected]:tpolecat/natchez.git",
ghpagesNoJekyll := true,
publish / skip := true,
paradoxTheme := Some(builtinParadoxTheme("generic")),
version := version.value.takeWhile(_ != '+'), // strip off the +3-f22dca22+20191110-1520-SNAPSHOT business
paradoxProperties ++= Map(
"scala-versions" -> (crossScalaVersions in http4s).value.map(CrossVersion.partialVersion).flatten.distinct.map { case (a, b) => s"$a.$b"} .mkString("/"),
"org" -> organization.value,
"scala.binary.version" -> s"2.${CrossVersion.partialVersion(scalaVersion.value).get._2}",
"core-dep" -> s"${(http4s / name).value}_2.${CrossVersion.partialVersion(scalaVersion.value).get._2}",
"version" -> version.value,
"scaladoc.natchez.base_url" -> s"https://static.javadoc.io/org.tpolecat/natchez-core_2.13/${version.value}",
),
mdocIn := (baseDirectory.value) / "src" / "main" / "paradox",
Compile / paradox / sourceDirectory := mdocOut.value,
makeSite := makeSite.dependsOn(mdoc.toTask("")).value,
mdocExtraArguments := Seq("--no-link-hygiene"), // paradox handles this
)

3 changes: 3 additions & 0 deletions modules/docs/src/main/paradox/_template/js/link_fix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function sourceUrlFix(sourceUrl) {
$("#source-link").attr("href", sourceUrl.replace("target/mdoc", "src/main/paradox"))
}
9 changes: 9 additions & 0 deletions modules/docs/src/main/paradox/_template/source.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script type="text/javascript" src="$page.base$js/link_fix.js"></script>

$if(page.source_url)$
<div class="source-github">
The source code for this page can be found <a id="source-link" href="$page.source_url$">here</a>.
</div>
$endif$

<script type="text/javascript">jQuery(function(){sourceUrlFix('$page.source_url$')});</script>
92 changes: 92 additions & 0 deletions modules/docs/src/main/paradox/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Natchez-Http4s

This is a support library for using [Natchez]() with [Http4s](). It provides the following things:

1. A mechanism to discharge a `Trace[F]` constraint on `HttpRoutes[F]`, which constructs the required ambient span from incoming headers when possible, otherwise creating a root span.
1. A middleware which adds standard request/response header information to the top-level span associated with a request, as well as extended fields for failure cases.

See below, then check out the `examples/` module in the repo for a working example with a real tracing back-end.

## Tracing HttpRoutes

Here is the basic pattern.

- Construct `HttpRoutes` in abstract `F` with a `Trace` constraint.
- Lift it into our `EntryPoint`'s `F`, which has _no_ `Trace` constraint.

```scala mdoc
// Nothing new here
import cats.effect.Bracket
import natchez.{ EntryPoint, Trace }
import org.http4s.HttpRoutes

// This import provides `liftT`, used below.
import natchez.http4s.implicits._

// Our routes constructor is parametric in its effect, which has (at least) a
// `Trace` constraint. This means all our handlers will be in the same F and
// will have tracing available.
def mkTracedRoutes[F[_]: Trace]: HttpRoutes[F] =
???

// Given an EntryPoint in F, with (at least) `Bracket[F, Throwable]` but
// WITHOUT a Trace constraint, we can lift `mkTracedRoutes` into F.
def mkRoutes[F[_]](ep: EntryPoint[F])(
implicit ev: Bracket[F, Throwable]
): HttpRoutes[F] =
ep.liftT(mkTracedRoutes)
```

The trick here is that `liftT` takes an `HttpRoutes[Kleisl[F, Span[F], *]]`, but this type is usually inferred so we never know or care that we're using `Kleisli`. If you do need to provide an explicit type argument, that's what it is.

## Tracing HttpRoutes Resources

We also provide `liftR`, which is analogous to `liftT` but works for `Resource[F, HttpRoutes[F]]`.

```scala mdoc
import cats.Defer
import cats.effect.Resource

def mkTracedRoutesResource[F[_]: Trace]: Resource[F, HttpRoutes[F]] =
???

// Note that liftR also requires Defer[F]
def mkRoutesResource[F[_]: Defer](ep: EntryPoint[F])(
implicit ev: Bracket[F, Throwable]
): Resource[F, HttpRoutes[F]] =
ep.liftR(mkTracedRoutesResource)
```

## Middleware

`NatchezMiddleware` adds the following "standard" fields to the top-level span associated with each request.

| Field | Type | Description |
|--------------------|------------|---------------------------------------|
| `http.method` | String | `"GET"`, `"PUT"`, etc. |
| `http.url` | String | request URI |
| `http.status_code` | String (!) | `"200"`, `"403"`, etc. |
| `error` | Boolean | `true`, only present in case of error |

In addition, the following Natchez-only fields are added.

| Field | Type | Description |
|--------------------|--------|----------------------------------------------|
| `error.message` | String | Exception message |
| `error.stacktrace` | String | Exception stack trace as a multi-line string |

Usage is straightforward.

```scala mdoc
import natchez.http4s.NatchezMiddleware

def mkRoutes2[F[_]](ep: EntryPoint[F])(
implicit ev: Bracket[F, Throwable]
): HttpRoutes[F] =
ep.liftT(NatchezMiddleware(mkTracedRoutes)) // type arguments are inferred as above
```

## Limitations

Because we're instantiating `F` to `Kleisl[F, Span[F], *]` in our `HttpRoutes`, any constraints we place on `F` must be fulfillable by `Kleisli`. Things like `Monad` and `Deferred` and so on are fine, but effects that require the ability to extract a value (specifically `Effect` and `ConcurrentEffect`) are unavailable and will lead to a type error. So be aware of this constraint.

17 changes: 17 additions & 0 deletions modules/examples/src/main/scala-2/Example1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ import org.http4s.server.Server
import org.http4s.implicits._
import natchez.http4s.NatchezMiddleware

/**
* Start up Jaeger thus:
*
* docker run -d --name jaeger \
* -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
* -p 5775:5775/udp \
* -p 6831:6831/udp \
* -p 6832:6832/udp \
* -p 5778:5778 \
* -p 16686:16686 \
* -p 14268:14268 \
* -p 9411:9411 \
* jaegertracing/all-in-one:1.8
*
* Run this example and do some requests. Go to http://localhost:16686 and select `Http4sExample`
* and search for traces.
*/
object Http4sExample extends IOApp {

// A dumb subroutine that does some tracing
Expand Down
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3")
addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.16")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.1")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.2")
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.16")

0 comments on commit f54b455

Please sign in to comment.