diff --git a/build.sbt b/build.sbt
index 5e5e217..250bbb3 100644
--- a/build.sbt
+++ b/build.sbt
@@ -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)
- )
\ No newline at end of file
+ )
+
+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 := "git@github.com: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
+ )
+
diff --git a/modules/docs/src/main/paradox/_template/js/link_fix.js b/modules/docs/src/main/paradox/_template/js/link_fix.js
new file mode 100644
index 0000000..6d9604b
--- /dev/null
+++ b/modules/docs/src/main/paradox/_template/js/link_fix.js
@@ -0,0 +1,3 @@
+function sourceUrlFix(sourceUrl) {
+ $("#source-link").attr("href", sourceUrl.replace("target/mdoc", "src/main/paradox"))
+}
diff --git a/modules/docs/src/main/paradox/_template/source.st b/modules/docs/src/main/paradox/_template/source.st
new file mode 100644
index 0000000..69b44f2
--- /dev/null
+++ b/modules/docs/src/main/paradox/_template/source.st
@@ -0,0 +1,9 @@
+
+
+$if(page.source_url)$
+
+The source code for this page can be found
here.
+
+$endif$
+
+
diff --git a/modules/docs/src/main/paradox/index.md b/modules/docs/src/main/paradox/index.md
new file mode 100644
index 0000000..17808f6
--- /dev/null
+++ b/modules/docs/src/main/paradox/index.md
@@ -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.
+
diff --git a/modules/examples/src/main/scala-2/Example1.scala b/modules/examples/src/main/scala-2/Example1.scala
index 2ca9dfc..07f7fd6 100644
--- a/modules/examples/src/main/scala-2/Example1.scala
+++ b/modules/examples/src/main/scala-2/Example1.scala
@@ -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
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 69ef488..a2434da 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -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")
\ No newline at end of file