Logs
What the server writes, as JSON, and where to read it.
Describes Ronne AI Marketplace 0.4.0.
Logs
Ronne writes one line per event to its standard output, as JSON: when it happened, how serious, what happened, and the request it belongs to. Where that output goes depends on how Ronne runs:
| Ronne runs as | Its logs |
|---|---|
| Docker | docker compose logs web (Docker's logging driver) |
| A Linux service | sudo rmk-server service logs, or journalctl -u rmk-server |
| A macOS service | sudo rmk-server service logs, or /Library/Logs/rmk-server/server.log |
| npm or a package with Node.js inside, no service | the terminal running rmk-server start |
| A clone | the terminal running pnpm start, or pnpm dev (as text) |
A line looks like this:
{"time":"2026-10-14T14:03:22.417Z","level":"info","msg":"Released","domain":"submissions","event":"version.released","requestId":"01K7…","userId":"01J9…","count":3}- Always:
time(UTC),levelandmsg, a short sentence that never changes. - When they apply:
event, a stable name to search and alert on;domain, the part of Ronne that wrote it;requestId, the request it belongs to;userId, who made it (an id, never an email or a name);err, an error's name, message and code, with its stack aterror; with tracing on,traceIdandspanId, the span it was written in (Monitoring). - The event's own fields: numbers, counts and short reasons, such as
statusorcount. - Better Auth's own lines (sign-in's library):
msgisBetter Auth, withsourceand its text indetail.
LOG_LEVEL (debug, info, warn or error; info by default) sets the least serious lines written: warn is what someone should look at, error a failure, and debug adds detail nobody needs day to day. An unknown value writes one warning and uses info. LOG_FORMAT is json, or text for a person reading a terminal (the default with pnpm dev). Set either in the settings file or the environment (with Docker, in the .env file next to compose.yaml, then docker compose up -d), and restart Ronne.
The events:
| Event | Level | When | Fields |
|---|---|---|---|
server.started | info | The server started (before setup, setup: required in place of dialect) | version, dialect, setup, runtime, port |
server.migrations_applied | info | Database changes were applied at start | count, latest |
http.request | info (warn for 429, error for 5xx) | An API request was answered | method, route, status, ms, userId |
http.error | error | An unexpected error | err, route |
auth.signed_in | info | Someone signed in | userId |
auth.sign_in_failed | warn | A sign-in failed (via is cli for rmk login; reason is two_factor_required when rmk login sent the right password of an account with two-factor) | reason, via |
auth.rate_limited | warn | Too many sign-ins or token requests | limit |
auth.two_factor_started | info | Someone started setting up two-factor sign-in | userId |
auth.two_factor_enabled | info | Two-factor sign-in was turned on | userId |
auth.two_factor_disabled | info | Two-factor sign-in was turned off | userId |
auth.two_factor_failed | warn | A two-factor code was refused at sign-in | reason (wrong_code, used, expired, too_many, locked, refused, unreadable) |
token.exchanged | info | rmk login, or a browser approval, got an access token | userId |
token.rejected | warn | An access token was refused | reason (unknown, revoked, expired, disabled) |
version.released | info | Versions were released (one line when releasing many at once) | count, userId |
version.yanked | info | A version was yanked | userId |
draft.uploaded | info | A draft was uploaded | files, bytes |
draft.upload_refused | warn | A draft upload was refused | reason |
upload.rate_limited | warn | Too many uploads or submits | limit |
usage.rate_limited | warn | Too many usage reports | limit |
submission.checks_failed | warn | Submit's checks couldn't run | err |
feed.built | info (debug from the cache) | A plugin feed was built, or served from the cache (cached, without plugins or ms) | tool, plugins, ms, bytes, cached |
feed.size_warning | warn | A feed is near its size limit | tool, percent |
feed.plugin_skipped | warn | A plugin was left out of a feed | tool, reason |
db.unavailable | error | The health check couldn't reach the database | err |
storage.unavailable | error | The health check found storage not writable | err |
telemetry.started | info | OpenTelemetry started (Monitoring) | traces, metrics |
telemetry.endpoint_invalid | warn | The OTLP endpoint isn't an http(s) URL; telemetry is off | |
telemetry.failed | error | OpenTelemetry couldn't start; the server starts without it | err |
telemetry.verbose_off | warn | NEXT_OTEL_VERBOSE is turned off: verbose spans would carry private names | |
telemetry.export_failed | warn | The collector couldn't be reached (at most once a minute) | signal |
job.started | info | A scheduled job started (Privacy: the retention purge) | job |
job.finished | info | A scheduled job finished, with what it did | job, ms, ipsCleared, emailsCleared, sessionsDeleted, grantsDeleted |
job.skipped | debug | Another run holds the job's lock | job, reason |
job.failed | error | A scheduled job failed; the next runs on schedule | job, ms, err |
log.level_invalid | warn | LOG_LEVEL isn't a level; info is used |
Never in a line: passwords, access tokens, cookies, AUTH_SECRET, a database's password, emails, people's IP addresses, or the names of items, scopes and workspaces (a private workspace's names stay private). Ronne doesn't write them, and the logger also takes out what could slip in, error messages included: fields named like a secret, access tokens, passwords in addresses, emails, the paths of stored files, and the value a database quotes in a duplicate-key error. Next.js also prints an unexpected error itself, as plain text next to Ronne's line: that text is Next.js's own, outside Ronne's logger. So is the error Better Auth (sign-in's library) prints when the database itself fails mid-request.
Reporting a problem: every page and API response carries an X-Request-Id header, and the API's message for an unexpected error names it. Quote it, and whoever runs the instance finds that request's lines by their requestId. Behind a proxy that sets its own X-Request-Id, Ronne keeps it when TRUST_PROXY=true, so one ID follows the request through.