tail -100 logs/app.log | grep -A 10 "ERROR"
Start from the innermost layer (core/database) and work outward to HTTP. Don’t debug through the full stack when you can isolate the issue.
tail -100 logs/app.log | grep -A 10 "ERROR"
Errors are logged with stack traces. Look for the first non-Wagoe frame in the trace.
;; Output appears in REPL/server stdout, not log files
(println "DEBUG:" {:field value :other other-value})
Remove all println statements before committing.
Bypass the HTTP layer and call services directly:
(def user-svc (get integrant.repl.state/system :wagoe/user-service))
(ports/find-user-by-email user-svc "alice@example.com")
Add temporary logging in handlers:
(println "DEBUG request:"
{:method (:request-method request)
:params (:params request)
:headers (select-keys (:headers request) ["hx-request" "authorization"])})
(def ds (get-in integrant.repl.state/system [:wagoe/db-context :datasource]))
(require '[next.jdbc :as jdbc])
(jdbc/execute! ds ["SELECT * FROM users WHERE email = ?" "alice@example.com"])
nil where a field value is expectedCause: snake_case / kebab-case mismatch.
Check: is the field :password_hash (snake) where the code expects :password-hash (kebab)?
Fix: always use cc/snake-case→kebab-case-map at the persistence boundary.
Cause: An ex-info without :type, or an unwrapped Java exception.
Fix: wrap in try-catch with a typed error:
(try
(UUID/fromString id-string)
(catch IllegalArgumentException _
(throw (ex-info "Invalid UUID" {:type :validation-error :value id-string}))))
In dev, an error over HTTP answers with a dev block:
{"error": "validation-error",
"message": "Request validation failed",
"details": {"email": ["missing required key"]},
"dev": {"code": "BND-201",
"category": "validation",
"docs-url": "bb guide error BND-201"}}
Three things have to be true for that block to appear, and each is deliberate:
:wagoe/dev-error-enricher {} is in the :active section of the config —
wagoe new writes it into the dev config and nowhere else;
the profile the app booted with is dev-like (:dev, :test) — the profile,
not an environment variable the deployment may never have been given;
wagoe-devtools is on the classpath, and it lives in the :repl alias — so
clojure -M:run, the uberjar and the Docker image cannot produce it.
Production gets the same response without the dev block, and its details
name only the fields that were wrong. The full messages describe the schema —
"should be either \"admin\" or \"auditor\"" hands a caller every enum member
it never knew about — so they are dev-only too. A 5xx says "Internal Server
Error" and nothing else; the message, the class and the ex-data go to the
log, keyed by correlation ID. Delete the config key to see the production shape
while developing.
One thing this response does not go through: a coercion failure is answered before the interceptor stack runs, so it carries a correlation ID but not the security headers, and it is not counted by request metrics or the rate limiter.
Cause: Schema and database migration are out of sync.
Fix: check that the field exists in both schema.clj, the migration SQL, and shell/persistence.clj.
Fix:
JWT_SECRET="dev-secret-at-least-32-characters-long" clojure -M:test :user
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |