A skill that checks an application installs the right log module in the right context, and that nothing about the host makes it a bad idea.
Why a skill
Recording is off by default, so "no log" is now indistinguishable from "module not installed" by looking at output. And a module installed the wrong way is silent: install() cannot replace a binding the installer already holds (Ray.Di's Container::merge() keeps existing keys), so $this->install(new DevQueryRepositoryLogModule(...)) next to $this->install(new QueryRepositoryModule()) leaves the null logger in place and records nothing.
What it checks
| Check |
Failure it catches |
The log module wraps the module it configures (module: new QueryRepositoryModule()) or overrides it |
the ordering trap above — recording silently stays off |
Dev context installs DevQueryRepositoryLogModule, prod installs ProdQueryRepositoryLogModule |
a dev-shaped log in production: every session written, no retention policy |
The dev log directory is dedicated, not the app's shared var/log |
LogFileWriter prunes what it finds; a shared directory loses unrelated files |
The prod target is php://stdout or a path the process can write |
a wrapper string is rejected at construction now, but an unwritable path only reports at runtime |
| The host serves one request per process |
on RoadRunner or a Swoole coroutine the sink refuses to arm and recording stays off (#179). A long-lived CLI consumer is the same hazard and is not detected — the skill should ask |
SemanticLoggerInterface is not bound elsewhere without Scope::SINGLETON |
a second logger arming the same sink is never flushed; the sink reports it to error_log(), which nobody reads |
A PsrLogWriter binding is absent where the injector is serialized |
it holds the host's logger; Monolog with closures makes the compiled app graph unserializable |
Acceptance
- Reports per context (dev / prod / test / CLI) what is installed and what the effective destination is — resolved from the bindings, not guessed from file names.
- Proves recording actually works rather than asserting the wiring: run one request, then confirm a session reached the destination.
- Recommends nothing for a host it cannot classify; asks instead.
Notes
DevQueryRepositoryLogModule / ProdQueryRepositoryLogModule docblocks state the constraints; docs/reading-the-log.md has the destination table.
A skill that checks an application installs the right log module in the right context, and that nothing about the host makes it a bad idea.
Why a skill
Recording is off by default, so "no log" is now indistinguishable from "module not installed" by looking at output. And a module installed the wrong way is silent:
install()cannot replace a binding the installer already holds (Ray.Di'sContainer::merge()keeps existing keys), so$this->install(new DevQueryRepositoryLogModule(...))next to$this->install(new QueryRepositoryModule())leaves the null logger in place and records nothing.What it checks
module: new QueryRepositoryModule()) or overrides itDevQueryRepositoryLogModule, prod installsProdQueryRepositoryLogModulevar/logLogFileWriterprunes what it finds; a shared directory loses unrelated filesphp://stdoutor a path the process can writeSemanticLoggerInterfaceis not bound elsewhere withoutScope::SINGLETONerror_log(), which nobody readsPsrLogWriterbinding is absent where the injector is serializedAcceptance
Notes
DevQueryRepositoryLogModule/ProdQueryRepositoryLogModuledocblocks state the constraints;docs/reading-the-log.mdhas the destination table.