Core API
The API writes structured JSON to stdout with pino. One line is one event. There are two loggers in the process.@perform/logger (pino)
createLogger(serviceName) in backend/packages/logger/src/index.ts returns a pino logger with:
- a
servicefield on every line (core-apifor the app logger,httpfor the access logger), - ISO timestamps,
- a redaction list whose matching fields are removed from the output.
err key so pino serialises the stack. The logger is available to every module as ctx.logger.
pino writes numeric levels. 30 is info, 40 is warn, 50 is error. No level is configured, so the default applies and debug lines are not written.
The HTTP access log
httpLogger is a pino-http middleware mounted in app.ts after the body parsers.
Successful requests are logged at
debug, which the default level drops. In practice the access log shows only failed requests. That keeps volume low, and it means you cannot count traffic from the logs.
Because the serialiser copies only three headers, authorization headers, cookies and API keys never reach the log through the access logger.
Redaction
These paths are removed from any log object that contains them:{ apiKey } at the top level, is written in clear. Do not log request bodies, tokens, connection strings or message contents. Log ids.
@repo/logs (consola)
The packages ported from the web repository (@repo/auth, @repo/api, @repo/payments, @repo/mail, @repo/database) log through a consola logger from @repo/logs. Its output is human-readable text, not JSON, and it has no redaction list. The Hono app also logs one line per /api request through this logger.
Expect a mix of JSON lines and plain text lines in the same stream. A log pipeline that parses JSON must tolerate non-JSON lines.
Prisma logging
The Prisma client forwards itswarn and error events to the logger it was created with. Transient connection errors and expected unique constraint violations are downgraded to debug, so they do not appear at the default level. Query logging exists behind the logQueries option of createPrismaClient and is off.
Where the output goes
pm2 does not rotate these files by itself. Install and configure log rotation on the host. An unrotated log can fill the disk, and a full disk makes uploads and extraction fail during a deploy.
The repository contains no log shipping configuration. If logs are sent to a central store, that is set up on the host.
Reading logs
Rules for backend code
console.logis a lint error inapps/and the@perform/*packages. Onlyconsole.warnandconsole.errorare allowed, and the logger is preferred.- Put identifiers in the fields object (
studioId,clientId,jobId), not in the message string, so lines can be filtered. - Keep the message constant.
'whatsapp send failed'can be searched. A message with an id interpolated cannot. - Never write an empty
catch. Log atwarnorerrorwith theerrfield, or rethrow. - Workers should log the queue name and the job id.
Web app
The web app logs to stdout and stderr of the Next.js server process.
Shared packages use the
consola logger from @repo/logs. There is no structured access log and no request id generation in the web app. The API generates the id.
When a server action fails because the API refused the request, the server-side client throws a CoreApiError carrying status, code and optional details from the API’s envelope. Log those fields. They identify the failing rule far better than the message.
When the workspace script starts the backend for you, the API’s output goes to a log file under /tmp instead of your terminal. tools/ensure-core-api.sh prints the last 40 lines of it if the API fails to start.
Mobile app
The mobile app has no remote logging or crash reporting service in its dependencies. During development, logs appear in the Metro terminal and the device logs.src/lib/api/config.ts prints the resolved API base URL once at startup in development builds, which is the first thing to check when the app cannot reach the API.
For a production problem, the evidence is on the server: the trainee lane’s failed requests in the API log, and push delivery receipts processed by the push receipt worker.
What is not in place
Stated plainly so nobody assumes otherwise:- No distributed tracing.
initTelemetryis an empty function. - No error tracking service in any of the three repositories.
- No log shipping configuration in the repositories.
- No access log for successful requests at the default level.