fix(deploy): wire SENTRY_DSN and enable ECS JSON logging for prod (#641) #643

Merged
marcel merged 1 commits from fix/issue-641-sentry-dsn-json-logging into main 2026-05-20 09:39:20 +02:00
Owner

Closes #641

Summary

  • docker-compose.prod.yml: Add SENTRY_DSN: ${SENTRY_DSN:-} to backend environment — the variable was already written to .env.staging by nightly.yml (line 81) but never forwarded into the container, so Sentry.captureException() was silently a no-op
  • docker-compose.prod.yml: Add LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs to backend environment — enables Spring Boot 4.0 ECS JSON structured logging so Loki gets single-entry JSON log lines with log.level instead of 50 separate unlinked stack trace lines
  • loki-logs.json: Update Grafana Loki dashboard query from | logfmt to | json to match the new ECS format

Action required before this lands

Set the SENTRY_DSN secret in Gitea repository settings → Actions → Secrets with value:
https://686ec2daa9bb45dc8e264e1e2727c8a4@glitchtip.archiv.raddatz.cloud/2

Test plan

  • Trigger a nightly deploy after setting the secret
  • Confirm a known error (e.g. the document page 500 from #642) appears in GlitchTip
  • In Grafana → Logs / App, select app = backend — confirm logs show as single JSON entries with detected_level visible
  • Confirm stack traces appear as one log entry (not 50+ fragmented lines)
Closes #641 ## Summary - **`docker-compose.prod.yml`**: Add `SENTRY_DSN: ${SENTRY_DSN:-}` to backend environment — the variable was already written to `.env.staging` by `nightly.yml` (line 81) but never forwarded into the container, so `Sentry.captureException()` was silently a no-op - **`docker-compose.prod.yml`**: Add `LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs` to backend environment — enables Spring Boot 4.0 ECS JSON structured logging so Loki gets single-entry JSON log lines with `log.level` instead of 50 separate unlinked stack trace lines - **`loki-logs.json`**: Update Grafana Loki dashboard query from `| logfmt` to `| json` to match the new ECS format ## Action required before this lands Set the `SENTRY_DSN` secret in Gitea repository settings → Actions → Secrets with value: `https://686ec2daa9bb45dc8e264e1e2727c8a4@glitchtip.archiv.raddatz.cloud/2` ## Test plan - [ ] Trigger a nightly deploy after setting the secret - [ ] Confirm a known error (e.g. the document page 500 from #642) appears in GlitchTip - [ ] In Grafana → Logs / App, select `app = backend` — confirm logs show as single JSON entries with `detected_level` visible - [ ] Confirm stack traces appear as one log entry (not 50+ fragmented lines)
marcel added 1 commit 2026-05-20 08:17:01 +02:00
fix(deploy): wire SENTRY_DSN and enable ECS JSON logging for prod (#641)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m27s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m22s
CI / fail2ban Regex (pull_request) Successful in 1m19s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
CI / Unit & Component Tests (push) Successful in 3m21s
CI / OCR Service Tests (push) Successful in 18s
CI / Backend Unit Tests (push) Successful in 3m33s
CI / fail2ban Regex (push) Successful in 43s
CI / Semgrep Security Scan (push) Successful in 20s
CI / Compose Bucket Idempotency (push) Successful in 59s
e89a90ff66
Pass SENTRY_DSN env var through to the backend container so the Sentry SDK
actually ships exceptions to GlitchTip — the variable was written to
.env.staging by nightly.yml but never forwarded into the container.

Enable Spring Boot 4.0 ECS structured logging (LOGGING_STRUCTURED_FORMAT_CONSOLE=ecs)
so Loki receives single-entry JSON log lines with parsed log.level, enabling
detected_level filtering in Grafana instead of 50-line unlinked stack trace blobs.

Update Grafana Loki dashboard query from | logfmt to | json to match the new format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

🏛️ Markus Keller — Software Architect

Verdict: Approved

Two independent gaps fixed correctly in one PR:

SENTRY_DSN passthrough (docker-compose.prod.yml)
The root cause is clear: nightly.yml writes the secret to .env.staging, but Docker Compose does not auto-inject env-file variables into containers — they must be explicitly declared under environment:. The ${SENTRY_DSN:-} empty-default pattern is correct: the SDK silently disables itself when the var is empty, so production starts cleanly even before the secret is set.

ECS structured logging (docker-compose.prod.yml + loki-logs.json)
LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs is the right lever — it's the Spring Boot 3.4+ property for ECS JSON output. Pairing it with the | json parser fix in the Grafana dashboard is necessary and correct; | logfmt was silently parsing nothing against Spring Boot's plain-text format.

No layer boundary concerns. Both fixes are strictly in the infrastructure config layer; no application logic is touched. The change is minimal and precisely scoped to the stated problem.

One note for future PRs: the Grafana dashboard JSON lives in the repo, which is good for reproducibility. If the log schema evolves (new fields added to ECS output), the dashboard queries may need co-updating — worth keeping in mind as the observability setup matures.

## 🏛️ Markus Keller — Software Architect **Verdict: ✅ Approved** Two independent gaps fixed correctly in one PR: **`SENTRY_DSN` passthrough** (`docker-compose.prod.yml`) The root cause is clear: `nightly.yml` writes the secret to `.env.staging`, but Docker Compose does not auto-inject env-file variables into containers — they must be explicitly declared under `environment:`. The `${SENTRY_DSN:-}` empty-default pattern is correct: the SDK silently disables itself when the var is empty, so production starts cleanly even before the secret is set. **ECS structured logging** (`docker-compose.prod.yml` + `loki-logs.json`) `LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs` is the right lever — it's the Spring Boot 3.4+ property for ECS JSON output. Pairing it with the `| json` parser fix in the Grafana dashboard is necessary and correct; `| logfmt` was silently parsing nothing against Spring Boot's plain-text format. No layer boundary concerns. Both fixes are strictly in the infrastructure config layer; no application logic is touched. The change is minimal and precisely scoped to the stated problem. **One note for future PRs:** the Grafana dashboard JSON lives in the repo, which is good for reproducibility. If the log schema evolves (new fields added to ECS output), the dashboard queries may need co-updating — worth keeping in mind as the observability setup matures.
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: Approved

Pure infra config — no application code changes. The fixes are correct and complete.

docker-compose.prod.yml

  • SENTRY_DSN: ${SENTRY_DSN:-} properly threads the Gitea secret through compose substitution into the container. The application.yaml binding sentry.dsn: ${SENTRY_DSN:} then picks it up via Spring's env-var interpolation.
  • LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs is the correct Spring Boot env var. This is a first-class Spring Boot 3.4+ feature, not a workaround — it activates the bundled ECS encoder on the console appender.

loki-logs.json

  • | json is required for ECS output (which is structured JSON). The old | logfmt parser was a silent no-op against Spring Boot's %d{…} %-5level … text pattern.
  • The graph panel query (line 127) uses count_over_time without a parser — that's fine; it doesn't need to parse fields.

Nothing to block on. Ship it.

## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ✅ Approved** Pure infra config — no application code changes. The fixes are correct and complete. **`docker-compose.prod.yml`** - `SENTRY_DSN: ${SENTRY_DSN:-}` properly threads the Gitea secret through compose substitution into the container. The `application.yaml` binding `sentry.dsn: ${SENTRY_DSN:}` then picks it up via Spring's env-var interpolation. - `LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs` is the correct Spring Boot env var. This is a first-class Spring Boot 3.4+ feature, not a workaround — it activates the bundled ECS encoder on the console appender. **`loki-logs.json`** - `| json` is required for ECS output (which is structured JSON). The old `| logfmt` parser was a silent no-op against Spring Boot's `%d{…} %-5level …` text pattern. - The graph panel query (line 127) uses `count_over_time` without a parser — that's fine; it doesn't need to parse fields. Nothing to block on. Ship it.
Author
Owner

🛠️ Tobias Wendt — DevOps / Infrastructure

Verdict: Approved

This directly touches the production deployment config, so I reviewed it closely.

What's correct:

  • SENTRY_DSN: ${SENTRY_DSN:-} uses an empty default (:-) — the backend starts cleanly whether or not the secret is set. No compose startup abort. Good defensive default.
  • The secret never touches the image layer; it's injected at runtime via the environment: block. Safe.
  • LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs is additive — it only changes the console log format, not log content or level. No regression risk.
  • Dashboard | json update: necessary. Without it, Loki returns zero parsed fields, making the dashboard useless even though logs are being stored correctly.

Operational note:
The PR description correctly documents the required follow-up: set the SENTRY_DSN Gitea secret before the next nightly deploy. This is the correct workflow — the compose file just wires the variable; the actual secret value lives in Gitea. The suggested deploy order (merge #643 → nightly → verify Sentry active → merge #644 to validate error disappears) is sound.

Minor observation:
MANAGEMENT_TRACING_SAMPLING_PROBABILITY already has a ${…:-0.1} default pattern — the SENTRY_DSN addition follows the same convention consistently. Good.

## 🛠️ Tobias Wendt — DevOps / Infrastructure **Verdict: ✅ Approved** This directly touches the production deployment config, so I reviewed it closely. **What's correct:** - `SENTRY_DSN: ${SENTRY_DSN:-}` uses an empty default (`:-`) — the backend starts cleanly whether or not the secret is set. No compose startup abort. Good defensive default. - The secret never touches the image layer; it's injected at runtime via the `environment:` block. Safe. - `LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs` is additive — it only changes the console log format, not log content or level. No regression risk. - Dashboard `| json` update: necessary. Without it, Loki returns zero parsed fields, making the dashboard useless even though logs are being stored correctly. **Operational note:** The PR description correctly documents the required follow-up: set the `SENTRY_DSN` Gitea secret before the next nightly deploy. This is the correct workflow — the compose file just wires the variable; the actual secret value lives in Gitea. The suggested deploy order (merge #643 → nightly → verify Sentry active → merge #644 to validate error disappears) is sound. **Minor observation:** `MANAGEMENT_TRACING_SAMPLING_PROBABILITY` already has a `${…:-0.1}` default pattern — the `SENTRY_DSN` addition follows the same convention consistently. Good.
Author
Owner

📋 Elicit — Requirements Engineer

Verdict: Approved

Checking against issue #641 requirements:

Requirement Addressed?
Sentry DSN forwarded to backend container SENTRY_DSN: ${SENTRY_DSN:-} added to backend environment:
GlitchTip receives backend errors Unblocked — DSN now reaches the Sentry SDK
Grafana Loki dashboard shows useful log entries `
No new secrets hardcoded Uses ${…:-} substitution
Staging deploy not broken when secret absent Empty fallback prevents compose abort

The fix is minimal and precisely scoped to the stated issues. No out-of-scope changes were introduced.

One open action (outside scope of this PR, documented in PR description): the SENTRY_DSN Gitea repository secret must be set to the GlitchTip project DSN before the next nightly deploy for Sentry capture to activate.

## 📋 Elicit — Requirements Engineer **Verdict: ✅ Approved** Checking against issue #641 requirements: | Requirement | Addressed? | |---|---| | Sentry DSN forwarded to backend container | ✅ `SENTRY_DSN: ${SENTRY_DSN:-}` added to backend `environment:` | | GlitchTip receives backend errors | ✅ Unblocked — DSN now reaches the Sentry SDK | | Grafana Loki dashboard shows useful log entries | ✅ `| logfmt` → `| json`, log format changed to ECS JSON | | No new secrets hardcoded | ✅ Uses `${…:-}` substitution | | Staging deploy not broken when secret absent | ✅ Empty fallback prevents compose abort | The fix is minimal and precisely scoped to the stated issues. No out-of-scope changes were introduced. **One open action** (outside scope of this PR, documented in PR description): the `SENTRY_DSN` Gitea repository secret must be set to the GlitchTip project DSN before the next nightly deploy for Sentry capture to activate.
Author
Owner

🔒 Nora "NullX" Steiner — Security Expert

Verdict: Approved

Reviewed with focus on secret handling and data exposure risk.

SENTRY_DSN: ${SENTRY_DSN:-}

  • DSN contains an authentication key (format: https://<key>@host/<project>). It is injected via environment variable, not hardcoded — correct.
  • The :- empty fallback is safe: the Sentry SDK disables itself silently, rather than crashing or leaking.
  • The DSN value is not echoed to logs. Spring Boot ECS logging does not dump environment variables by default.
  • In the Sentry/GlitchTip threat model: a leaked DSN allows an attacker to submit fake error reports (low impact) but not read existing data. Still correct to protect it as a secret.

LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs

  • Changes log format to structured JSON. No new fields are added beyond what was logged before. ECS format does not include sensitive application data (passwords, tokens) unless they were already being logged — which would be a pre-existing issue, not introduced here.
  • No concern with multi-line stack traces leaking into unexpected sinks; Promtail handles them as individual log lines.

loki-logs.json

  • Dashboard change is cosmetic/query-level. No new data sources, no new access paths. The Grafana dashboard is internal-only (no public exposure in the compose config). No concern.

No security blockers.

## 🔒 Nora "NullX" Steiner — Security Expert **Verdict: ✅ Approved** Reviewed with focus on secret handling and data exposure risk. **`SENTRY_DSN: ${SENTRY_DSN:-}`** - DSN contains an authentication key (format: `https://<key>@host/<project>`). It is injected via environment variable, not hardcoded — correct. - The `:-` empty fallback is safe: the Sentry SDK disables itself silently, rather than crashing or leaking. - The DSN value is not echoed to logs. Spring Boot ECS logging does not dump environment variables by default. - In the Sentry/GlitchTip threat model: a leaked DSN allows an attacker to submit fake error reports (low impact) but not read existing data. Still correct to protect it as a secret. **`LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs`** - Changes log format to structured JSON. No new fields are added beyond what was logged before. ECS format does not include sensitive application data (passwords, tokens) unless they were already being logged — which would be a pre-existing issue, not introduced here. - No concern with multi-line stack traces leaking into unexpected sinks; Promtail handles them as individual log lines. **`loki-logs.json`** - Dashboard change is cosmetic/query-level. No new data sources, no new access paths. The Grafana dashboard is internal-only (no public exposure in the compose config). No concern. No security blockers.
Author
Owner

🧪 Sara Holt — QA / Tester

Verdict: Approved

Infrastructure config changes — no unit/integration tests applicable. Validation is deployment-time.

Test plan for after merge + nightly deploy:

  1. Verify SENTRY_DSN reaches the container

    docker inspect archiv-staging-backend-1 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep SENTRY
    

    Expected: SENTRY_DSN=https://...@glitchtip.archiv.raddatz.cloud/2

  2. Verify ECS JSON logging is active

    docker logs archiv-staging-backend-1 --tail 5
    

    Expected: lines starting with { (JSON), not 2026-05-20 … (plain text)

  3. Verify Loki ingests structured logs

    • Open Grafana → Explore → Loki datasource
    • Query: {job="archiv-staging-backend-1"} | json | level="INFO"
    • Expected: results with parsed level, message, @timestamp fields
  4. Verify GlitchTip receives errors (after PR #644 is also merged and the 500 is confirmed fixed — but test the pipeline with a known error first)

    • Trigger a controlled 500 in staging
    • Check GlitchTip https://glitchtip.archiv.raddatz.cloud for the new event

Note: The SENTRY_DSN Gitea secret must be set before step 1 will pass.

## 🧪 Sara Holt — QA / Tester **Verdict: ✅ Approved** Infrastructure config changes — no unit/integration tests applicable. Validation is deployment-time. **Test plan for after merge + nightly deploy:** 1. **Verify SENTRY_DSN reaches the container** ```bash docker inspect archiv-staging-backend-1 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep SENTRY ``` Expected: `SENTRY_DSN=https://...@glitchtip.archiv.raddatz.cloud/2` 2. **Verify ECS JSON logging is active** ```bash docker logs archiv-staging-backend-1 --tail 5 ``` Expected: lines starting with `{` (JSON), not `2026-05-20 …` (plain text) 3. **Verify Loki ingests structured logs** - Open Grafana → Explore → Loki datasource - Query: `{job="archiv-staging-backend-1"} | json | level="INFO"` - Expected: results with parsed `level`, `message`, `@timestamp` fields 4. **Verify GlitchTip receives errors** (after PR #644 is also merged and the 500 is confirmed fixed — but test the pipeline with a known error first) - Trigger a controlled 500 in staging - Check GlitchTip `https://glitchtip.archiv.raddatz.cloud` for the new event **Note:** The `SENTRY_DSN` Gitea secret must be set before step 1 will pass.
Author
Owner

🎨 Leonie Voss — UI / UX Expert

Verdict: Approved

This is a pure infrastructure and observability config change — no frontend code, no UI components, no user-facing behavior is affected. Users will not see any change.

The indirect benefit is that once the Grafana dashboard is functional and GlitchTip receives errors, developers get faster feedback loops when UI-breaking backend errors occur. That's a positive for the overall development experience.

Nothing to flag from a UI perspective. LGTM.

## 🎨 Leonie Voss — UI / UX Expert **Verdict: ✅ Approved** This is a pure infrastructure and observability config change — no frontend code, no UI components, no user-facing behavior is affected. Users will not see any change. The indirect benefit is that once the Grafana dashboard is functional and GlitchTip receives errors, developers get faster feedback loops when UI-breaking backend errors occur. That's a positive for the overall development experience. Nothing to flag from a UI perspective. LGTM.
marcel merged commit e89a90ff66 into main 2026-05-20 09:39:20 +02:00
marcel deleted branch fix/issue-641-sentry-dsn-json-logging 2026-05-20 09:39:20 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#643