No logging or observability anywhere in the backend #89
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: MEDIUM
The problem
There is no
loggingimport anywhere underbackend/app— confirmed by grep. The onlydiagnostics are
echo=TrueSQL dumps in development, which are themselves a data-disclosureproblem (they log comment bodies and notes).
When the OAuth callback fails, ingest rejects a file, or the worker dies, there is nothing to read.
The
AuditEventtable is a product record of who changed what — it is not an operations logand should not be pressed into that role.
Fix
loggingwith a module-level logger per package.CIRCA_LOG_LEVEL; dropecho=Truein favour of a separateCIRCA_SQL_ECHOthat defaults off.Done when
References
backend/app/main.py,backend/app/db/session.py:16Related: #50 (deployment) will consume this.
Done in
960e6f0.app/logging_config.py,RequestLogMiddleware, andbackend/tests/test_logging.py(20).What it writes
One line per request: method, path, status, duration, user id, request id. The id also comes back as
X-Request-Id, so a reported problem can be found in the log without asking the person what time it was — and it is set as a contextvar, so a warning from deep inside the image sandbox carries the id of the upload that caused it without every function in the chain being handed one.JSON by default. Not a style preference: a message containing a newline — an exception, an Authlib error payload — silently becomes two entries in a line-oriented text format, and the second one looks like a fresh event with no context.
CIRCA_LOG_FORMAT=textfor a terminal;CIRCA_LOG_LEVELfor the level.4xx logs at INFO, 5xx at ERROR. A refused login is ordinary traffic, and shouting about it trains people to ignore the channel that is supposed to mean something.
The worker now installs the same handler instead of its own
basicConfigformat — they were writing differently-shaped lines to the same place, so anything reading them needed two parsers and a job failure could not be lined up with the request that enqueued it.What it must never write, which is the actual design
status=disputedanswers that/api/photosdoes not — and logging the path alone means a future search parameter cannot quietly start recording what people look for.LOGGED_FIELDSis a closed list andtest_a_request_line_carries_only_the_declared_fieldsholds it closed. The failure worth guarding is not somebody logging a note on purpose — it is somebody addingrequest.query_paramsor a body because it was convenient, in a change nobody reads as being about privacy. Two more tests write a real note and a real comment through the API and assert the text appears in no record at all.The ledger stays a product record
test_a_request_writes_no_audit_eventasserts requests add nothing toAuditEvent. It is who-changed-what about a photograph, read by reviewers, and immutable at the database level (#67) — so a stack trace in there would be permanent and unremovable, which is the strongest possible reason not to start.On the SQL echo
Already done in #14, which is where the environment default moved. The setting is
CIRCA_SQLITE_ECHOrather than theCIRCA_SQL_ECHOthis issue names — same thing, matching theCIRCA_SQLITE_*family already in config. Off by default and not implied by development, because the echoed statements contain exactly the note and comment bodies above.Done when
One thing found on the way:
configure_loggingis idempotent and refuses to stack handlers, becausecreate_app()calls it and the test suite builds hundreds of applications in one process — a handler per app would multiply every line by the number ever constructed.1079 passed, 8 skipped; ruff clean. README and
.env.exampleupdated.