Commits
Tarun Sukhani authored e8e29024633
feat: structured JSON logging via Log4j JsonTemplateLayout with ECS template (PF-9) Add an application.log.format toggle (text default, json opt-in) that swaps the bundled framework log config between the classic PatternLayout (framework/src/log4j.properties) and a new framework/src/log4j-json.properties which routes the console appender through Log4j 2's JsonTemplateLayout with the bundled EcsLayout.json template (classpath:EcsLayout.json, ships inside log4j-layout-template-json-2.25.3.jar). ECS was chosen over GCP/Gelf/JSON templates because it's the broadest schema across log aggregators — Loki/Datadog/Splunk/Elastic all parse it natively without custom field mapping. The toggle is wired in Logger.LoggerInit's resource lookup; user config (application.log.path) still wins, only the framework default name flips. Per-request context flows through Log4j 2's ThreadContext (MDC): ActionInvoker.invoke() pushes request_id (carried via request.args under __REQUEST_ID if upstream set it, else a fresh UUID), http_method, http_path, client_ip at entry, plus action_name once the route is resolved, and clears the map in the finally block so subsequent virtual-thread reuses don't inherit stale context. The EcsLayout.json shipped in the layout jar flattens MDC entries to top-level keys (stringified=true), so request_id shows up as `"request_id": "..."` rather than nested. The new dep (org.apache.logging.log4j -> log4j-layout-template-json 2.25.3) matches the existing log4j-api/log4j-core version and was added to framework/dependencies.yml + framework/lib/ (the project commits resolved jars; lib/ IS the runtime classpath, no resolver runs at boot). ant resolve needed a one-time bootstrap of the jar into lib/ because the local playCoreDependencies resolver pins each declared module to itself — once the file existed, resolve verified it cleanly with no other churn. LoggerJsonFormatTest exercises the layout in isolation: it builds a JsonTemplateLayout against the EcsLayout.json template, formats a synthetic LogEvent, parses with Gson, and asserts the ECS core fields plus MDC flattening. Format-toggle wiring is also covered by constructing Logger.LoggerInit twice and asserting the resolved resource URL flips between log4j.properties and log4j-json.properties. No port-binding required because this is pure in-process behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>