Self-hosted platform for standing up and running software factories on AWS: complete DevSecOps environments with secure delivery pipelines, supply-chain controls, compliance evidence and multi-tenant governance — in minutes, not months.
Application logs from every Pipeline Builder service, searchable in the dashboard at Deliver → Logs (/dashboard/logs).
logs:export) from viewing (observability:read), because bulk egress leaves the building and outlives a revoked session./dashboard/audit records who did what; these are the services’ own log lines.This page is the per-organization window onto the platform’s own application logs. A member sees only the lines their organization produced; a system administrator can additionally read the shared infrastructure tenant and span several organizations at once.
It is for two audiences: developers debugging their own pipelines and plugins, and operators standing up or tuning the logging stack. If you want the audit record of an action rather than a service’s log line, see Audit Events.
orgId from the tenant scope (setLogContextProvider, wired once in api-server’s tenant-context.ts).[REDACTED] before the line is stored — and again on the way out.orgId to structured metadata and uses its tenant stage to send the line to that organization’s Loki tenant.orgId goes to _infra. Anything written outside a request scope — service startup, background workers, nginx, Postgres, Loki itself — lands in the infrastructure tenant, visible only to system administrators. This is fail-closed by construction.Infrastructure noise is not yours. And because a pod is shared — one platform replica serves every organization — “the whole log file for this container” is not something an organization can be shown. The raw view and the download give you your lines from that stream, and say so in the file.
Masking only the display would leave the real value in storage and still matchable. Someone could search for a guess and learn from whether it hit, confirming the secret even though the line renders as [REDACTED]. Masking at write time closes that, and is also why a search term that looks like a credential is refused outright.
Masking is a net under the rule that services should not log secrets in the first place — not a licence to log them.
| Setting | Where | Note |
|---|---|---|
LOKI_URL |
platform env | Defaults to http://loki:3100 |
LOKI_BASE_SELECTOR |
platform env | Anchor matcher when no label is constrained; defaults to service_name=~".+" |
All four live in each target’s config/loki/loki-config.yml.
| Setting | Why it is required |
|---|---|
auth_enabled: true |
Turns on per-organization tenancy. Every Loki client must then send X-Scope-OrgID, Grafana included |
multi_tenant_queries_enabled: true |
Lets an administrator read several tenants in one query |
allow_structured_metadata: true |
Required, or Loki rejects the orgId metadata promtail attaches |
deletion_mode: filter-and-delete |
Enables per-tenant deletion when an organization is removed |
retention_period: 168h |
7 days, platform-wide |
auth_enabled: true in the target’s loki-config.yml, along with the other three Loki settings above. Tenancy does not work without it.X-Scope-OrgID is refused — including Grafana.orgId reaches promtail. Tenancy depends on it: the logger stamps it, promtail promotes it to structured metadata and routes on it. Lines missing it silently become _infra lines.Regenerate the masking stages rather than hand-editing them:
node scripts/gen-promtail-masking.mjs # print the block
node scripts/gen-promtail-masking.mjs --check # CI: fail if a config drifted
They are generated from packages/api-core/src/utils/sensitive-patterns.ts, the single source shared with the logger and the read path.
logs:export deliberately. Viewing rides observability:read, which the built-in Member role already has; export is granted to admins and owners by default so an organization can let members read logs on screen while withholding bulk egress.If Loki is unreachable — a LEAN deployment omits it — the pages render an empty state with a banner rather than an error.
| You are | You see |
|---|---|
| A member of an organization | Only lines your organization produced |
| A system administrator | The _infra tenant by default; any organization, or several, by selecting them |
level:error service_name:platform "connection refused" -healthz /timed? out/
| Form | Meaning |
|---|---|
field:value |
Exact match on an allow-listed field |
"quoted phrase" or a bare word |
The line contains this text |
-term |
The line does not contain this text |
/regex/ |
The line matches this regular expression |
Allow-listed fields: service_name, service, level, pod, container, event, eventCategory, actor, pluginName, orgId, trace_id, requestId. An unrecognized field is an error rather than a silently ignored filter.
Regular expressions are safe to use freely: Loki evaluates them with RE2, which is linear-time and has no catastrophic-backtracking failure mode.
Presets (15m / 1h / 6h / 24h / 7d), or an absolute range. Clicking a bar in the volume histogram zooms to that bucket. Because retention is 7 days, a wider request is narrowed to that window with a banner.
Expand a row (the › chevron) for its parsed fields, stream labels and structured metadata, plus:
trace_id, so you can jump straight to the distributed traceView as text renders the current selection as a plain-text extract.
Two formats: .log (plain text) or .jsonl (one JSON object per line, labels preserved).
The download runs the same compiled query, tenant scope and masking as the search on screen — it is not a separate path, so you get exactly what you can see. Each file opens with a preamble recording the organization, filter, window and masking notice.
| Property | Value |
|---|---|
| Permission | logs:export (separate from viewing) |
| Cap | 100 MB or 60 seconds, whichever comes first |
| Truncation | A truncated file says so on its last line |
| Audit | Recorded as observability.logs.export |
| Impersonation | Refused during a read-only impersonation session |
JWTs, Bearer tokens, AWS access keys, Stripe / GitHub / Slack tokens, credentials embedded in connection strings and URLs, ?token=-style query parameters, inline secret= assignments, private-key headers, and AWS account identifiers.
observability:read and logs:exportLOKI_URL, LOKI_BASE_SELECTOR