Logging
Foundry Connect writes its application log as structured JSON, one object per line. This page describes what the log contains, how to configure it, and what it deliberately does not do.
The log line
Every line is a single JSON object:
{"level":"info","time":1786619775084,"service":"web","app_service":"web","component":"business/run/run.ts","correlation_id":"3f7a1c2e-8b04-4d1a-9f65-2c0d5e7a1b93","user_id":42,"runId":12195,"attempt":1,"msg":"run launched"}
| Field | Meaning |
|---|---|
level |
debug, info, audit, warn, or error |
time |
milliseconds since the epoch, UTC |
service, app_service |
the service that emitted the line, from APP_NAME, which defaults to web |
component |
the source module, so a line can be traced to the code that wrote it |
correlation_id |
ties every line produced by one API call together. See below |
user_id |
the acting user, when the line was emitted during an authenticated request |
msg |
the human readable message. It is a fixed string, so lines can be grouped |
Any remaining fields are context the call site supplied, such as runId and attempt above.
Values are structured fields rather than text interpolated into the message, so they can be
filtered on directly.
Correlation id
Each incoming API request is assigned a correlation_id, and every line emitted while handling
that request carries it, including work that continues asynchronously. To follow one request end to
end, filter on that single value.
If the request already carries an x-request-id header, that value is reused, so a proxy such as
nginx can originate the identifier at the edge and one id then covers every hop. The response
always echoes the id back in its own x-request-id header.
An inbound value is accepted only when it is at most 128 characters and made of letters, digits, dots, underscores, or hyphens. Anything else is ignored and Foundry Connect generates its own id. This is deliberate: the header is supplied by the caller, and an unbounded or specially crafted value would otherwise appear on every line of that request.
Configuration
| Setting | Default | Purpose |
|---|---|---|
LOG_LEVEL |
debug |
Lowest level that is written. Accepts debug, info, audit, error |
LOG_FORMAT |
json in production, pretty elsewhere |
json for machine readable output, pretty for human readable output during development |
LOG_DIR |
/var/log/viafoundry |
Directory the log file is written to |
LOG_FILE_NAME |
viafoundry |
Base name of the log file |
LOG_AUTO_ROTATE |
true |
Roll the file daily and compress what rolls off |
LOG_MAX_FILES_DAYS |
14d |
How long rotated files are kept |
LOG_LEVEL accepts four values and warn is not among them, even though lines are emitted at
warn. The levels rank debug, info, audit, warn, error, and setting LOG_LEVEL keeps
everything at that rank and above. Setting it to audit therefore still records warnings and
errors, while dropping debug and info.
The default of debug is verbose for a production deployment. Set LOG_LEVEL=info or higher there
unless you are actively diagnosing something.
The LOG_DIR and LOG_FILE_NAME defaults still carry the product's former name. That is
deliberate and must not be tidied up: those paths are the writable bind mount target in every
compose file, no deployment sets them in its own environment file, and a renamed default directory
is not writable by the non-root runtime user, which crash loops the backend at startup. A new
install that wants a different path can set LOG_DIR explicitly.
Output goes to standard output and, in production, to a file in LOG_DIR as well. Container
deployments usually collect standard output; the file exists for on-prem operators who read it
directly on the host.
With LOG_AUTO_ROTATE enabled the file is <LOG_FILE_NAME>-YYYY-MM-DD.log and older days are
compressed to .log.gz. With it disabled the file is <LOG_FILE_NAME>.log and grows without bound,
so an external rotation mechanism becomes your responsibility.
What this does not do
State these plainly when planning around them.
- There is no central log store. Lines are written to standard output and to the local file. Shipping them somewhere searchable, and any retention policy beyond the file rotation above, is the operating organization's responsibility.
- This is not an audit trail of record changes. The log records what the application did, not a
tamper evident history of who changed which record and when. Lines at the
auditlevel are operational events in the same stream as everything else, subject to the same rotation, and they can be lost when a file rolls off. Do not rely on them as a record of truth. - Retention is not enforced by the product.
LOG_MAX_FILES_DAYSgoverns rotated files on one host. Nothing verifies that a policy is met, and nothing prevents a line being deleted. - Frontend errors are not included. Only backend activity reaches this log today.