diff --git a/example/7-debug/README.md b/example/7-debug/README.md
index a4457c7..eb9f563 100644
--- a/example/7-debug/README.md
+++ b/example/7-debug/README.md
@@ -82,9 +82,10 @@ As you can see, the report includes:
The debugger is disabled by default to avoid leaking information by accident in
a production environment. Whether the debugger is enabled or disabled, Dream
-still writes error messages to the log — the debugger is only about also
-sending them as *reponses*, which can be easier to work with, especially for
-collaborators who are not currently looking at the log.
+still writes error messages to the server-side log — the debugger is only
+about also sending them to the client as *reponses*, which can be easier to
+work with, especially for collaborators who are not currently looking at the
+log.
@@ -101,8 +102,8 @@ Both the debugger's output and the non-debug error page are fully customizable
- [**`8-error`**](../8-error/#files) handles all errors in one place, also
customizing the debugger.
-- [**`9-logging`**](../9=logging/#files) writes messages to the same Dream log
- at various levels, and creates a sub-log.
+- [**`9-log`**](../9-log/#files) writes messages to the same Dream log at
+ various levels, and creates a sub-log.
diff --git a/example/8-error/README.md b/example/8-error/README.md
index 806046a..0dd18cc 100644
--- a/example/8-error/README.md
+++ b/example/8-error/README.md
@@ -85,8 +85,7 @@ to the point of intercepting strings generated by its HTTP dependencies.
**Next steps:**
-- [**`9-logging`**](../9-logging/#files) shows how to write messages to
- Dream's log.
+- [**`9-log`**](../9-log/#files) shows how to write messages to Dream's log.
- [**`a-promise`**](../a-promise/#files) properly introduces Lwt, the promise
library used by Dream.
diff --git a/example/9-log/README.md b/example/9-log/README.md
new file mode 100644
index 0000000..a94fcdf
--- /dev/null
+++ b/example/9-log/README.md
@@ -0,0 +1,81 @@
+# `9-log`
+
+
+
+This app writes custom messages to Dream's log:
+
+```ocaml
+let () =
+ Dream.run
+ @@ Dream.logger
+ @@ Dream.router [
+
+ Dream.get "/"
+ (fun request ->
+ Dream.log "Sending greeting to %s!" (Dream.client request);
+ Dream.respond "Good morning, world!");
+
+ Dream.get "/fail"
+ (fun _ ->
+ Dream.warning (fun log -> log "Raising an exception!");
+ raise (Failure "The web app failed!"));
+
+ ]
+ @@ Dream.not_found
+```
+
+
$ dune exec --root . ./log.exe
+
+
+
+If you visit [http://localhost:8080](http://localhost:8080) and then
+[http://localhost:8080/fail](http://localhost:8080/fail), you will find these
+messages in the log, between the other messages:
+
+```
+26.03.21 21:25:17.383 REQ 1 Sending greeting to 127.0.0.1:64099!
+26.03.21 21:25:19.464 WARN REQ 2 Raising an exception!
+```
+
+As you can see, the functions take
+[`Printf`-style format strings](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html),
+so you can quickly print values of various types to the log.
+
+
+
+`Dream.warning` is a bit strange. The reason it takes a callback, which waits
+for a `log` argument, is because if the log threshold is higher than
+`` `Warning``, the callback is never called, so the application doesn't spend
+any time formatting a string that it will not print. This is the style of the
+[Logs](https://erratique.ch/software/logs) library. Try inserting this code
+right before `Dream.run` to see the message suppressed:
+
+```ocaml
+Dream.initialize_log ~level:`Error ();
+```
+
+
+
+You can create named sub-logs for different parts of your application with
+`Dream.sub_log`:
+
+```ocaml
+let my_log =
+ Dream.sub_log "my.log"
+
+let () =
+ sub_log.warning (fun log -> log "Hmmm...")
+```
+
+
+
+**Next steps:**
+
+- [**`a-promise`**](../a-promise/#files) introduces Lwt, the promise library
+ used by Dream.
+- [**`b-session`**](../b-session/#files) finally back to web development proper
+ — session management.
+
+
+
+[Up to the tutorial index](../#readme)
diff --git a/example/9-log/dune b/example/9-log/dune
new file mode 100644
index 0000000..6d0a528
--- /dev/null
+++ b/example/9-log/dune
@@ -0,0 +1,3 @@
+(executable
+ (name log)
+ (libraries dream))
diff --git a/example/9-log/dune-project b/example/9-log/dune-project
new file mode 100644
index 0000000..929c696
--- /dev/null
+++ b/example/9-log/dune-project
@@ -0,0 +1 @@
+(lang dune 2.0)
diff --git a/example/9-log/log.ml b/example/9-log/log.ml
new file mode 100644
index 0000000..1c6c9f9
--- /dev/null
+++ b/example/9-log/log.ml
@@ -0,0 +1,17 @@
+let () =
+ Dream.run
+ @@ Dream.logger
+ @@ Dream.router [
+
+ Dream.get "/"
+ (fun request ->
+ Dream.log "Sending greeting to %s!" (Dream.client request);
+ Dream.respond "Good morning, world!");
+
+ Dream.get "/fail"
+ (fun _ ->
+ Dream.warning (fun log -> log "Raising an exception!");
+ raise (Failure "The web app failed!"));
+
+ ]
+ @@ Dream.not_found
diff --git a/example/README.md b/example/README.md
index 56ba920..70212a7 100644
--- a/example/README.md
+++ b/example/README.md
@@ -16,11 +16,13 @@ list below and jump to whatever interests you!
- [**`5-echo`**](5-echo/#files) — reads request bodies.
- [**`6-template`**](6-template/#files) — renders responses
from templates and guards against XSS.
-- [**`7-debug`**](7-debug) — includes detailed information
+- [**`7-debug`**](7-debug/#files) — includes detailed
+ information
about errors in responses.
-- [**`8-error`**](8-error) — customize all error responses in
- one place.
-- [**`9-logging`**](9-logging)
+- [**`8-error`**](8-error/#files) — customize all error
+ responses in one place.
+- [**`9-log`**](9-log/#files) — writing messages to Dream's
+ log.
- [**`a-promise`**](a-promise)
- [**`b-session`**](a-session)
- [**`c-cookie`**](b-cookie)