All Galleon subsystems write a single structured log stream to stdout — JSON format, one record per line. Rotation and delivery to a log storage system are the deployment platform’s concern.

Configuration

The logging configuration block:

KeyDefaultPurpose
levelinfoThe Galleon log threshold: debug, info, warn, error
formatjsonjson or text
registry_levelwarnA separate threshold for the records about container image operations

The audit events of container image operations (manifest, blob, and tag uploads and deletions) are always written, regardless of registry_level.

Access log

Every request writes one msg="http request" record with the fields: source (the subsystem), method, path, status, size, user, client_ip, duration, the database time and query count, and the trailing request_id. user carries the authenticated account; a request without successful authentication shows - there.

Galleon returns the same identifier to the client in the X-Request-ID header. It finds every record of one request in the log. client_ip behind a reverse proxy is correct only with trust configured (Reverse proxy).

The errors of the HTTP server itself are logged with the server.http source at the warn level. When Galleon serves HTTPS itself, failed TLS handshakes land in that source (TLS).

The fields that appear only on problem traffic:

  • sqlstate — the request hit a PostgreSQL error;
  • served_stale=true — a proxy repository served a stale copy because the external registry did not respond;
  • deny_reason — an authentication or access denial; the value dictionary is below.

The attempted_user field is the login the client presented: the credentials were rejected, or not checked because the request was refused earlier. The ? value means a non-Basic authorization or an empty login. Look for failed sign-ins by this field: user shows - on such records. The field appears on normal traffic as well, for example when monitoring polls an open endpoint with credentials.

Denial reasons

The deny_reason field explains every denial without changing the client response (response codes do not reveal the existence of others’ repositories):

ValueReason
no_credentialsA request without credentials where they are mandatory
bad_credentialsCredentials were presented and rejected
anonymous_disabledAnonymous access is off
anonymous_forbiddenThe anonymous role lacks a privilege
forbiddenThe user lacks a privilege on the repository
admin_requiredAn administrative privilege is missing
anon_no_adminAn anonymous container image request that requires administrative rights
password_change_requiredBlocked until the forced password change
repository_not_foundThe target repository was not found; also stamped on a 404 answered before the credential check
repository_name_invalidThe repository name could not be parsed, so no lookup was made
wrong_formatThe repository exists but has a different format
monitoring_ip_deniedAn address outside monitoring_allowlist
upstream_manually_blockedRequests to the external registry are frozen manually
upstream_auto_blockedRequests are paused by auto-blocking

A 401/403 record without deny_reason indicates an infrastructure failure: check the err and sqlstate fields. A 404 on a repository carries a reason as well; only the answers of a repository taken out of service and the refusals of a malformed request go without one.

Audit events

Administrative and domain events are written at the info level:

  • artifact publications — msg="<FORMAT> artifact published" records with the coordinates and repository, one per artifact;
  • container image operations — manifest, blob, and tag uploads and deletions;
  • repository cache invalidation (source=audit.repository);
  • bootstrap administrator creation, creation of the starter set of repositories, garbage collection cycle summaries, external registry connection state changes.

Secrets never reach the log at any level: authorization header values, passwords, tokens, and body contents are not logged. Control characters and invalid UTF-8 in client-supplied values (the path, headers, file names) are replaced with ? in the log.