Skip to content

feat(logging): [1/4] structured-logging foundation — channels + context processors + auth subscriber#475

Merged
turegjorup merged 5 commits into
release/3.0.0from
feature/structured-logging-foundation
Jun 9, 2026
Merged

feat(logging): [1/4] structured-logging foundation — channels + context processors + auth subscriber#475
turegjorup merged 5 commits into
release/3.0.0from
feature/structured-logging-foundation

Conversation

@turegjorup

@turegjorup turegjorup commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

PR 1 of 4 — Structured logging

First of a four-PR series implementing structured, channel-split, context-rich logging (see docs/adr/011-structured-logging.md). Base branch: release/3.0.0.

What this PR does

  • Channels — declares per-domain Monolog channels (auth, screen, media, feed, interactive, cache) alongside outbound_http (the renamed app_http outbound-HTTP-client channel, see below).
  • Outbound-HTTP channel rename + native client silencedapp_httpoutbound_http; a NullLogger decorates Symfony's native http_client logger so LoggingHttpClient is the single, OTel-shaped source of outbound-HTTP logs (no duplicate request logging).
  • Per-channel prod handlers — one always-on stderr handler per domain channel. A Monolog handler carries a single level, so per-channel thresholds need per-channel handlers. Each level is LOG_LEVEL_<CHANNEL> falling back to a global LOG_LEVEL (via the app.log_level parameter and the empty-safe default: env processor). info reproduces today's output, so this is additive.
  • Configurable destination — all prod handlers (incl. nested/outbound_http) write to LOG_PATH (default php://stderr). Suits container stderr capture; bare-metal nginx + php-fpm operators can point it at a file (they own rotation/permissions). New .env: LOG_PATH, LOG_LEVEL, LOG_LEVEL_<CHANNEL> (empty = inherit).
  • RequestContextProcessor — enriches every record with request_id, route, method, and identity (user_id XOR screen_id, tenant_id). No request-id format validation. Identity-resolution failures (no active tenant, unhydrated screen token, …) are caught so enrichment never breaks the request it is annotating.
  • TraceContextProcessor — parses a W3C traceparent header into trace_id / span_id.
  • RequestIdSubscriber — adopts the inbound X-Request-Id or mints one, echoes it on the response.
  • AuthLoggingSubscriber — logs login/logout and JWT invalid/expired/not-found on the auth channel. Logging only — never token strings or credentials, and each handler is guarded so a logging failure can never break authentication.

Field names here are interim (request_id, route, method, user_id, screen_id, tenant_id). The rename to OpenTelemetry semantic conventions lands in PR 2.

Verification

  • composer code-analysis — green (PHPStan level 6)
  • composer coding-standards-check + env-coverage — green
  • composer test — green (18 tests across both processors, the request-id subscriber, and the auth subscriber)
  • cache:clear --env=prod — per-channel handlers + LOG_LEVEL fallback resolve

🤖 Generated with Claude Code

@turegjorup
turegjorup force-pushed the feature/structured-logging-foundation branch 2 times, most recently from c6652e7 to 7661b7d Compare June 3, 2026 08:11
@turegjorup turegjorup self-assigned this Jun 3, 2026
@turegjorup
turegjorup force-pushed the feature/structured-logging-foundation branch 4 times, most recently from e800c87 to fbf4f60 Compare June 3, 2026 08:58
Comment thread .env
HTTP_CLIENT_LOG_LEVEL=error
###< Http Client ###

###> Logging ###

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables should be documented in README.md under Configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readme updated


- Contributors must attach context via the PSR-3 context array (not string interpolation)
and must never swallow exceptions silently; the PHPStan gate fails the build otherwise.
The conventions are documented in `docs/logging.md`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is docs/logging.md ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in #476

@turegjorup
turegjorup force-pushed the feature/structured-logging-foundation branch 4 times, most recently from 4e6beca to 0a35565 Compare June 3, 2026 10:58
…criber

Introduce the structured-logging foundation (ADR 011):

- Declare per-domain Monolog channels (auth, screen, media, feed,
  interactive, cache) alongside app_http.
- In prod, add one always-on stderr handler per domain channel, each
  thresholded by a LOG_LEVEL_<CHANNEL> env var that falls back to a global
  LOG_LEVEL (app.log_level parameter); a single Monolog handler carries one
  level, so per-channel thresholds need per-channel handlers.
- Make the output destination configurable via LOG_PATH (default
  php://stderr); nested and app_http honour it too. Suits container stderr
  capture; bare-metal nginx + php-fpm operators can point it at a file.
- RequestContextProcessor enriches every record with request id, route,
  method and identity (user_id XOR screen_id, tenant_id). Field names are
  the interim PR-1 names; the OTel SemConv rename follows in a later PR.
- TraceContextProcessor parses a W3C traceparent header into trace_id /
  span_id when present.
- RequestIdSubscriber adopts the inbound X-Request-Id or mints one and
  echoes it on the response, with no format validation.
- AuthLoggingSubscriber logs login/logout and JWT-failure outcomes on the
  auth channel (logging only; never token strings or credentials).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@turegjorup
turegjorup force-pushed the feature/structured-logging-foundation branch from 0a35565 to 35ed9dc Compare June 3, 2026 11:58
…t-id subscriber

Apply the "logging must never break the request it annotates" principle
consistently:

- RequestContextProcessor: widen the try/catch from getActiveTenant() to the
  whole identity block, so screen_id (getScreen()->getId() on an unhydrated
  ScreenUser throws an uninitialized-typed-property Error) and user_id are
  guarded too. Fields set before a throw are kept.
- AuthLoggingSubscriber: run each handler's logging through a guard() so a
  failure (context building or write) drops the line instead of aborting
  authentication, making the class's "only logs, never alters auth" contract
  real.

Tests:
- New RequestIdSubscriberTest: adopt/mint/empty-header/no-overwrite/sub-request.
- AuthLoggingSubscriberTest: add onLoginSuccess and onJwtFailure (all four
  match arms: invalid/expired/not_found/unknown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ctured-logging-foundation

# Conflicts:
#	CHANGELOG.md
#	config/services.yaml
@turegjorup turegjorup changed the title feat(logging): structured-logging foundation — channels + context processors + auth subscriber feat(logging): [1/4] structured-logging foundation — channels + context processors + auth subscriber Jun 8, 2026
turegjorup and others added 2 commits June 8, 2026 22:46
…nnel

Rename AuthLoggingSubscriber's injected logger $logger -> $authLogger so the
constructor self-documents which channel it writes to. The explicit
@monolog.logger.auth binding stays (renamed to the matching key) and remains
the authoritative, build-verified wiring; a services.yaml comment records why
explicit binding is preferred over MonologBundle channel autowiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OTel semconv deprecated the enduser.* attribute group in favour of user.id.
Align the ADR's identity description; the field rename itself lands with the
processor changes on the semconv branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@turegjorup
turegjorup merged commit 4b1094f into release/3.0.0 Jun 9, 2026
25 checks passed
@turegjorup
turegjorup deleted the feature/structured-logging-foundation branch June 9, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants