Skip to content

Commit 0954a76

Browse files
authored
Merge pull request #1441 from makeabilitylab/1440-logs-url-docs
Stop documenting the web /logs/ URL — it no longer exists (#1440)
2 parents 4d6085a + 71b30fe commit 0954a76

5 files changed

Lines changed: 67 additions & 50 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ A superuser is required to use `/admin` and add content; create one with `python
5151
- **Push to `master`** → auto-deploys to `makeabilitylab-test.cs.washington.edu` via webhook.
5252
- **Push a SemVer tag (e.g. `git tag 2.3.2 && git push --tags`)** → deploys to production `makeabilitylab.cs.washington.edu`.
5353
- Bump `ML_WEBSITE_VERSION` and `ML_WEBSITE_VERSION_DESCRIPTION` in `makeabilitylab/settings.py` when cutting a release.
54-
- Build logs: `<host>/logs/buildlog.txt`. Application logs: `<host>/logs/debug.log`. See `docs/DEPLOYMENT.md` for SSH paths on `recycle.cs.washington.edu`.
54+
- Application logs: read `debug.log` over SSH on `makelab1`/`makelab2`/`recycle` under `/cse/web/research/makelab/www[-test]/`. Build logs only reach you via the deploy email. **The web `/logs/` URL is gone — every path under it 404s on both hosts.** Confirm what a server is running with `/version.json` (`git_sha`, not `built_at`). See `docs/DEPLOYMENT.md`.
5555

5656
### Server access model (important — shapes how anything ships to prod/test)
5757

@@ -130,7 +130,7 @@ the existing viewset/serializer pattern and keep `v1` fields additive-only
130130
- **Prod/test `config.ini` has only a `[Django]` section — no `[Postgres]` section.** Per `settings.py`, a missing `[Postgres]` section means Django uses the fallback `DATABASES` default (`HOST='db'`) — i.e. the dockerized `db` service of the active compose file. A `[Postgres]` section, if added, would override it. So the DB is the in-stack `db` container in **every** environment (no external Postgres); on the servers that's the `db` service in `docker-compose.yml`.
131131
- `DEBUG` resolution order: `DJANGO_ENV=PROD` forces False → `config.ini [Django] DEBUG``DJANGO_ENV=DEBUG` forces True → default False.
132132
- `TIME_ZONE = 'America/Los_Angeles'`. `ML_WEBSITE_VERSION` in settings is shown in the admin header and used in release tagging.
133-
- **Logging (#1283):** `debug.log` lives at `LOG_DIR/debug.log`, where `LOG_DIR` is `$ML_LOG_DIR` or `<BASE_DIR>/media` (`/code/media` in the container). Keep it inside `MEDIA_ROOT` — the web-served `/logs/debug.log` depends on that. `ML_LOG_DIR` is unset everywhere today; it exists for non-`/code` hosts. If the dir isn't writable the file handler degrades to a `NullHandler` rather than crashing `django.setup()`, and since there's no console on the servers that state surfaces via `/version.json` (`log_to_file`) and a superuser-only callout on the admin dashboard.
133+
- **Logging (#1283):** `debug.log` lives at `LOG_DIR/debug.log`, where `LOG_DIR` is `$ML_LOG_DIR` or `<BASE_DIR>/media` (`/code/media` in the container). Keep it inside `MEDIA_ROOT`that's the tree bind-mounted to the shared CSE filesystem, so it's what makes the log readable over SSH at all. `ML_LOG_DIR` is unset everywhere today; it exists for non-`/code` hosts. `MEDIA_ROOT` is web-served, so never log anything sensitive. If the dir isn't writable the file handler degrades to a `NullHandler` rather than crashing `django.setup()`, and since there's no console on the servers that state surfaces via `/version.json` (`log_to_file`) and a superuser-only callout on the admin dashboard.
134134

135135
### Container startup side effects (`docker-entrypoint.sh`)
136136

docs/DEPLOYMENT.md

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,17 @@ git push --tags
7575

7676
### Verifying Deployment
7777

78-
Check the build log to confirm deployment succeeded:
78+
Confirm the deployment succeeded with `/version.json`, which reports the running
79+
build (the `/logs/buildlog.txt` URL this section used to point at now 404s — see
80+
"Log Files" below):
7981

80-
- **Test:** https://makeabilitylab-test.cs.washington.edu/logs/buildlog.txt
81-
- **Production:** https://makeabilitylab.cs.washington.edu/logs/buildlog.txt
82+
```bash
83+
curl -s https://makeabilitylab-test.cs.washington.edu/version.json | python3 -m json.tool
84+
```
85+
86+
Match `git_sha` against the commit you pushed — **not `built_at`**, which has read
87+
fresh while the host served stale code. The build log itself is only available in
88+
the deploy email sent to maintainers on every push.
8289

8390
## Versioning
8491

@@ -112,7 +119,7 @@ View current and past versions on the [Releases page](https://github.com/makeabi
112119
git push --tags
113120
```
114121

115-
5. Verify deployment via the [production build log](https://makeabilitylab.cs.washington.edu/logs/buildlog.txt)
122+
5. Verify the deploy landed via [`/version.json`](https://makeabilitylab.cs.washington.edu/version.json) — check `git_sha`, not `built_at`
116123

117124
## Server Configuration
118125

@@ -175,18 +182,24 @@ You only do this once per property (not per content change):
175182

176183
### Log Files
177184

178-
Not all logs live in the same place. Only the Django application log is on
179-
the shared CSE filesystem (and therefore readable from `recycle`); the build
180-
and web-server logs live on the Docker host (`grabthar` / `docker-test2`),
181-
which we can't SSH into — reach those via the web `/logs/` URL or the deploy
182-
email.
185+
Not all logs live in the same place. Only the Django application log is on the
186+
shared CSE filesystem, and it is the only one we can still read directly. The
187+
build and web-server logs live on the Docker host (`grabthar` / `docker-test2`),
188+
which we can't SSH into.
189+
190+
> **The web `/logs/` URL is gone.** It used to expose these files over HTTP.
191+
> Every path under it now 404s on both prod and test (verified 2026-07-28): the
192+
> response comes back as `Server: gunicorn` with Django's custom 404 template,
193+
> meaning Apache has no `/logs/` alias any more and the request falls through to
194+
> Django. It is not coming back — web access to logs is no longer needed and UW
195+
> CSE IT has trouble maintaining it. Use SSH.
183196
184197
| Log | Description | Where to find it |
185198
|-----|-------------|------------------|
186-
| `debug.log` | Django application logs | **On the shared filesystem** — read via SSH on `recycle` (see below) or the web `/logs/` URL. A rotated `debug.log.1` sits alongside it. |
187-
| `buildlog.txt` | Deployment build output | **Not on the shared filesystem** (so *not* under `www/` on `recycle`). It lives on the Docker host and is emailed to maintainers on every push — that email is the most reliable copy. Also exposed at the web `/logs/` URL. |
188-
| `httpd-access.log` | HTTP request logs | On the Docker host — web `/logs/` URL. |
189-
| `httpd-error.log` | HTTP error logs | On the Docker host — web `/logs/` URL. |
199+
| `debug.log` | Django application logs | **On the shared filesystem** — read via SSH (see below). Rotated `debug.log.1` `.6` sit alongside it. |
200+
| `buildlog.txt` | Deployment build output | **Not on the shared filesystem** (so *not* under `www/`). It lives on the Docker host and is emailed to maintainers on every push — **that email is the only copy you can get.** |
201+
| `httpd-access.log` | HTTP request logs | On the Docker host — **not reachable**; ask UW CSE IT if you need it. |
202+
| `httpd-error.log` | HTTP error logs | On the Docker host — **not reachable**; ask UW CSE IT if you need it. |
190203

191204
#### Where `debug.log` is written, and what happens if that fails (#1283)
192205

@@ -201,15 +214,16 @@ LOG_FILE = $LOG_DIR/debug.log # /code/media/debug.log
201214
out to the shared CSE filesystem — it's what makes `debug.log` readable over SSH at
202215
`/cse/web/research/makelab/www/debug.log` (prod) and `www-test/debug.log` (test).
203216

204-
> **Note:** `https://<host>/logs/debug.log` **404s on both prod and test** as of
205-
> 2026-07-28 (verified with `curl`; it falls through to Django's custom 404). The
206-
> web-URL rows in the table above are stale. SSH is the reliable path — see
207-
> "Reading `debug.log` over SSH" below. Because the log nonetheless lives in a
208-
> web-served tree, we still log at INFO rather than DEBUG when `DEBUG` is off.
217+
Note that `MEDIA_ROOT` is a **web-served** tree, so keep the log conservative:
218+
we log at INFO rather than DEBUG whenever `DEBUG` is off, and nothing personal or
219+
sensitive should ever be written to it.
209220

210221
- **`ML_LOG_DIR`** is an optional environment override for hosts that don't use
211222
`/code`. It is **not set** on prod, test, or local dev, and shouldn't need to
212-
be. If you do set it outside `MEDIA_ROOT`, the web `/logs/` URL stops working.
223+
be. If you point it outside `MEDIA_ROOT`, the log stops being bind-mounted to
224+
the shared filesystem and you lose SSH access to it — which, with no shell on
225+
these servers, means losing all access. Only do that if you also arrange a
226+
mount for the new location.
213227
- **If the log directory can't be created or written**, Django does *not* crash
214228
(it used to: `LOGGING` is evaluated at `django.setup()`, so a bad path killed
215229
startup before a single request). The file handler degrades to a `NullHandler`
@@ -234,8 +248,8 @@ curl -s $HOST/version.json | python3 -m json.tool
234248
```
235249

236250
Match `git_sha`**not `built_at`**, which has shown fresh on a stuck auto-deploy
237-
serving stale code. Then confirm records are really being written (the web `/logs/`
238-
URL 404s, so this has to be SSH):
251+
serving stale code. Then confirm records are really being written (this has to be
252+
SSH; there is no web path to the log):
239253

240254
```bash
241255
ssh makelab1 # or makelab2 / recycle
@@ -244,21 +258,20 @@ tail -5 /cse/web/research/makelab/www-test/debug.log # timestamps after the de
244258
```
245259

246260
The log rotates at 5 MB, so check `debug.log.1` too when hunting a container-start
247-
sequence.
248-
249-
### Accessing Logs via Web
261+
sequence. Rotation is currently unreliable under multiple Gunicorn workers — see
262+
issue #1439 — so a rotated file may be much smaller than 5 MB and may be missing
263+
records.
250264

251-
- **Test:** https://makeabilitylab-test.cs.washington.edu/logs/
252-
- **Production:** https://makeabilitylab.cs.washington.edu/logs/
265+
### Reading `debug.log` over SSH
253266

254-
Only `debug.log` (the Django application log) is reachable this way — it is
255-
the one log mounted out to the shared CSE filesystem. `buildlog.txt` and the
256-
`httpd-*.log` files are **not** here (see the table above).
267+
This is the only way to read the application log. SSH access is read-mostly: you
268+
can read the shared CSE filesystem, but there is no Docker or `manage.py` access
269+
on the hosts that run the stack.
257270

258-
1. SSH into the jump host:
271+
1. SSH to a host with the shared filesystem mounted:
259272

260273
```bash
261-
ssh recycle.cs.washington.edu
274+
ssh makelab1 # or makelab2, or recycle.cs.washington.edu
262275
```
263276

264277
2. Navigate to the log directory:

makeabilitylab/settings.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,10 @@ def _file_log_handler(log_file, level, enabled):
175175

176176

177177
# NOTE: this default must stay in sync with MEDIA_ROOT (defined further down as
178-
# os.path.join(BASE_DIR, 'media')) — the web-served /logs/debug.log URL only works
179-
# because the log lives inside the media root. MEDIA_ROOT isn't defined yet here
178+
# os.path.join(BASE_DIR, 'media')) — media/ is the tree bind-mounted out to the
179+
# shared CSE filesystem, so keeping the log inside it is what makes debug.log
180+
# readable over SSH at all. There is no shell on these hosts, so a log written
181+
# anywhere else is a log nobody can read. MEDIA_ROOT isn't defined yet here
180182
# (LOGGING has to be built before it), hence the duplicated expression;
181183
# test_default_log_file_is_under_media_root pins the two together.
182184
LOG_DIR = os.environ.get('ML_LOG_DIR', os.path.join(BASE_DIR, 'media'))
@@ -217,13 +219,12 @@ def _file_log_handler(log_file, level, enabled):
217219
'handlers': {
218220
# The file handler writes LOG_FILE (media/debug.log by default), which lands
219221
# in the bind-mounted web root — that's what makes it readable over SSH at
220-
# /cse/web/research/makelab/www[-test]/debug.log. (docs/DEPLOYMENT.md also
221-
# describes a /logs/ URL per Jason Howe's design, but that URL 404s on both
222-
# prod and test as of 2026-07-28.) Since the file still sits in a web-served
223-
# tree, stay conservative: log at INFO when DEBUG is off, but keep DEBUG-level
224-
# file logging in local dev where DEBUG is on and nothing is public. If the
225-
# log dir isn't writable (LOG_TO_FILE is False), degrade to a NullHandler so
226-
# startup never dies (issue #1283).
222+
# /cse/web/research/makelab/www[-test]/debug.log, the only way to read it now
223+
# that the /logs/ URL is gone. media/ IS a web-served tree, so stay
224+
# conservative about what lands here: log at INFO when DEBUG is off, but keep
225+
# DEBUG-level file logging in local dev where DEBUG is on and nothing is
226+
# public. If the log dir isn't writable (LOG_TO_FILE is False), degrade to a
227+
# NullHandler so startup never dies (issue #1283).
227228
'file': _file_log_handler(LOG_FILE, 'DEBUG' if DEBUG else 'INFO', LOG_TO_FILE),
228229
'console': {
229230
'level': 'DEBUG',
@@ -453,8 +454,9 @@ def _file_log_handler(log_file, level, enabled):
453454
# See: https://docs.djangoproject.com/en/4.2/ref/settings/#media-url
454455
#
455456
# NOTE: LOG_DIR (defined up with LOGGING, which has to be built before this) hard-codes
456-
# the same expression, because the web-served /logs/debug.log URL only works while the
457-
# log file lives inside the media root. If you move MEDIA_ROOT, move LOG_DIR with it —
457+
# the same expression, because this is the tree bind-mounted to the shared CSE
458+
# filesystem — the log has to live inside it to be readable over SSH, which is the only
459+
# access we have. If you move MEDIA_ROOT, move LOG_DIR with it —
458460
# test_default_log_file_is_under_media_root fails loudly if the two ever diverge.
459461
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
460462
MEDIA_URL = '/media/'

website/tests/test_logging_config.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,15 @@ def test_disabled_degrades_to_nullhandler(self):
8888

8989
class LogFileLocationTests(SimpleTestCase):
9090
def test_default_log_file_is_under_media_root(self):
91-
"""The log must live inside MEDIA_ROOT or the /logs/ URL breaks.
91+
"""The log must live inside MEDIA_ROOT or it becomes unreadable.
9292
9393
``LOG_DIR`` and ``MEDIA_ROOT`` are computed independently in settings.py
9494
(LOGGING has to be built before MEDIA_ROOT is defined), so this pins them
95-
together: the web-served ``/logs/debug.log`` on -test and prod works only
96-
because the log file sits inside the bind-mounted media root. Skipped when
97-
``ML_LOG_DIR`` is set, since an explicit override may point elsewhere.
95+
together: media/ is the tree bind-mounted to the shared CSE filesystem, and
96+
SSH to that filesystem is the *only* way to read debug.log on -test and prod
97+
(there is no shell on those hosts, and the old /logs/ URL is gone). A log
98+
written outside it is a log nobody can read. Skipped when ``ML_LOG_DIR`` is
99+
set, since an explicit override may point elsewhere.
98100
"""
99101
if os.environ.get("ML_LOG_DIR"):
100102
self.skipTest("ML_LOG_DIR override in effect")

website/views/version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
4646
Note that ``log_to_file: true`` only means the log *directory* was writable at
4747
startup. To confirm records are really landing, tail the file over SSH at
48-
``/cse/web/research/makelab/www[-test]/debug.log`` (the ``/logs/`` URL described in
49-
docs/DEPLOYMENT.md 404s on both servers).
48+
``/cse/web/research/makelab/www[-test]/debug.log`` -- there is no web path to the
49+
log (the old ``/logs/`` URL is gone; see docs/DEPLOYMENT.md).
5050
"""
5151

5252
import json

0 commit comments

Comments
 (0)