Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Agents
ecourty/logger-bundle
claude-code
cursor
logging
symfony
monolog
logger
bundle
psr-3

A Symfony bundle providing a single, reusable Logger service that wraps PSR-3 Monolog channels with two things: structured context tagging (every log call is tied to a \BackedEnum case, e.g. OrderContext::PaymentFailed) and pluggable parameter serialization (non-scalar log parameters —...

Install
php artisan boost:add-skill ecourty/logger-bundle

Save this content to: AGENTS.md

# AGENTS.md - Coding Guidelines for AI Agents

## 🎯 Core Concept

A Symfony bundle providing a single, reusable `Logger` service that wraps PSR-3 Monolog
channels with two things: **structured context tagging** (every log call is tied to a
`\BackedEnum` case, e.g. `OrderContext::PaymentFailed`) and **pluggable parameter
serialization** (non-scalar log parameters — exceptions, domain objects — are converted to a
loggable shape by the first matching processor, instead of relying on Monolog's generic
object normalization).

### Problem Solved

Apps that log through the raw `Psr\Log\LoggerInterface` tend to end up with unstructured
messages, hardcoded channel and prefix logic scattered across the codebase, and ad hoc
serialization of exceptions or domain objects repeated at every call site. This bundle
centralizes that as a single reusable service, with the project-specific bits (channel name,
message prefix) exposed as configuration instead of hardcoded.

### Solution

- `Logger` (`src/Service/Logger.php`): a final service with the full 8-level PSR-3 interface
  (`emergency` → `debug`). Every method takes a `\BackedEnum $context` (any backed enum —
  no bundle-specific marker interface to implement), a `string $message`, and an
  `array $parameters`. It formats the message as `"{$context->value} - {$message}"` and
  runs every parameter through the first tagged processor whose `supports()` returns true.
- `LogParameterProcessorInterface` (`src/Contract/`): the extensibility contract. Any class
  implementing it — in this bundle or in a consuming project — is auto-registered and
  auto-injected into `Logger`, no service configuration needed. Tagging is wired
  imperatively by `LoggerExtension::load()` via `$container->registerForAutoconfiguration()`,
  **not** by a `#[AutoconfigureTag]` attribute on the interface — that attribute only
  self-applies when the *consuming app's own* resource-scanned service discovery happens to
  walk the file declaring it, which never happens for a file living in `vendor/`. Do not
  "simplify" this back to an attribute; it silently breaks processor autowiring for every
  consuming project's own custom processors (see `tests/Functional/LoggerFunctionalTest.php::testACustomProcessorRegisteredOutsideTheBundleIsAutowired`,
  which is designed to fail if this regresses).
- `ThrowableLogParameterProcessor` (`src/Processor/`): the only built-in processor, serializing
  `\Throwable` (recursively, including `getPrevious()`). Registered by `LoggerExtension`
  when `enable_builtin_processors` is true (default) — a consuming project that wants its own
  `\Throwable` handling can set it to `false`.
- `LoggerExtension` (`src/DependencyInjection/`): binds `Logger`'s PSR logger argument to
  the configured Monolog channel (reused, not created — the consuming project must declare
  it under its own `monolog.channels`), conditionally registers the built-in processors and
  `MessagePrefixProcessor`. Every service and argument in this bundle is wired explicitly —
  no `autowire()`/`autoconfigure()` on the bundle's own `services.php` — so its dependency
  graph never depends on the consuming app's container. See "Symfony Bundles" below.
- `DisallowLoggerInterfaceRule` (`src/PHPStan/`): opt-in PHPStan rule a consuming project
  can register in its own `phpstan.neon` to forbid injecting `Psr\Log\LoggerInterface`
  directly, forcing all logging through `Logger`.

---

## 🏗️ Architecture

### Overview

```
LogContext enum (any \BackedEnum, defined per-project, per-domain)
         │
         ▼
     Logger::error($context, $message, $parameters)
         │
         ├─ formatMessage()      "{$context->value} - {$message}"
         ├─ processParameters()  each value → first supports()-matching processor
         │
         ▼
   monolog.logger.<channel>  (an existing channel — declared by the consuming project under
         │                    monolog.channels, never created by this bundle)
         │
         ▼
   [MessagePrefixProcessor]  (only if message_prefix configured — Monolog processor tag)
         │
         ▼
      Monolog handlers (project's own monolog.yaml)
```

### Main Components

- `src/Service/Logger.php` — the public API, the only class most consuming code touches.
- `src/Contract/LogParameterProcessorInterface.php` — the extension point for parameter
  serialization. Consuming projects add their own implementations freely.
- `src/Processor/ThrowableLogParameterProcessor.php` — default `\Throwable` processor.
- `src/Monolog/MessagePrefixProcessor.php` — optional message-prefixing Monolog processor.
- `src/DependencyInjection/{Configuration,LoggerExtension}.php` — config schema (`channel`,
  `message_prefix`, `enable_builtin_processors`) and wiring (channel argument binding,
  conditional processor registration).
- `src/PHPStan/DisallowLoggerInterfaceRule.php` — optional enforcement rule.

---

## 🚀 Typical Use Cases

- Any Symfony app that wants one consistent, structured logging entry point instead of
  scattering `LoggerInterface` injections and ad hoc `$logger->info('message: ' . $foo)`
  string concatenation across the codebase.
- Logging domain events with a typed context (`enum OrderLogContext: string { case
  PaymentFailed = 'PaymentFailed'; }`) instead of free-text categories.
- Logging exceptions (or other domain objects, via a custom processor) without manually
  extracting `getMessage()`/`getTrace()`/etc. at every call site.

---

## 💡 Design Patterns Used

- **Strategy pattern** for parameter processors (`LogParameterProcessorInterface`):
  first-match-wins over a tagged iterable, so behavior is added by writing a new class,
  never by editing `Logger` itself.
- **Marker-less extensibility**: log contexts are plain `\BackedEnum` cases — no interface
  to implement, no bundle coupling in domain code, just a backed enum per domain.

---

## Project breakdown

- `src/LoggerBundle.php` — bundle entry point.
- `src/Contract/` — the one public extension point (`LogParameterProcessorInterface`).
- `src/Processor/` — bundled processor implementations (currently: throwables only).
- `src/Monolog/` — Monolog-specific processors (currently: message prefixing).
- `src/Service/Logger.php` — the logger service.
- `src/DependencyInjection/` — configuration schema and container wiring.
- `src/PHPStan/` — optional static-analysis enforcement.
- `tests/App/` — minimal Symfony test application (FrameworkBundle + MonologBundle +
  LoggerBundle) used to boot a real kernel in Functional/Integration tests.

**IMPORTANT**: This section should evolve with the project. When a new feature is created,
updated or removed, this section should too.

## 🧪 Testing

This bundle is covered by unit, integration, functional, and PHPStan-rule tests, in
`tests/{Unit|Integration|Functional|PHPStan}`. Unit tests use PHPUnit mocks/stubs.
Integration tests exercise `LoggerExtension` directly against a `ContainerBuilder` (no HTTP
kernel). Functional tests boot `tests/App/TestKernel` end-to-end and assert against a
`Monolog\Handler\TestHandler` — including a processor defined outside the bundle, to prove
the extensibility contract actually autowires for a consuming project. PHPStan-rule tests
use `PHPStan\Testing\RuleTestCase` against fixtures in `tests/PHPStan/data/` (excluded from
this project's own CS-Fixer/PHPStan runs — they intentionally contain violations).

---

## Remarks & Guidelines

### General

- NEVER commit or push the git repository.
- When unsure about something, you MUST ask the user for clarification. Same goes if the
  user request is unclear.
- When facing a problem that has an easy "hacky" solution, and a more robust but more
  difficult to implement one, always choose the robust one:
  - Easy hacky fixes become technical debt, and can lead to issues down the road
  - Robust solutions means the project will remain serious and well-built.
- ALWAYS write tests for the important components. Better safe than sorry!
- Do NOT write ANY type documentation unless explicitly asked.
- Once a feature is complete, update the @README.md and @AGENTS.md accordingly.
- The @README.md file should consist of a project overview for end-users, not a technical
  explanation of the project.
- Keep comments and documentation succinct — state what's non-obvious, don't over-explain.

### Commits

- Format: `type(domain): quick explanation`, where `type` is one of `feat`, `fix`, `docs`,
  `tests`. Keep the explanation short — this is a summary line, not a changelog entry.

### Symfony Bundles

- This bundle is meant to be installed via Composer across multiple Symfony projects. When
  developing features, keep this in mind: no hardcoded channel names, prefixes, or
  project-specific assumptions — those belong in configuration.
- Architecture, naming, design, extensibility and easiness to install and use should be key
  priorities to consider when developing this project.
- Never use `autowire()`/`autoconfigure()` defaults for the bundle's own services in
  `src/Resources/config/services.php`. Wire every argument explicitly (`Reference`,
  `tagged_iterator()`, `Definition::addTag()`) so the bundle's dependency graph is fixed and
  doesn't depend on what the consuming app's container happens to contain. This does not
  apply to consuming projects' own code — they can and should autowire normally; it's only
  about this bundle's own service definitions.

## 📚 References

- **Source code**: `/src`
- **Tests**: `/tests`
- **README**: User documentation
- **Symfony Docs**: https://symfony.com/doc/current/bundles.html

AGENTS.md - Coding Guidelines for AI Agents

🎯 Core Concept

A Symfony bundle providing a single, reusable Logger service that wraps PSR-3 Monolog channels with two things: structured context tagging (every log call is tied to a \BackedEnum case, e.g. OrderContext::PaymentFailed) and pluggable parameter serialization (non-scalar log parameters — exceptions, domain objects — are converted to a loggable shape by the first matching processor, instead of relying on Monolog's generic object normalization).

Problem Solved

Apps that log through the raw Psr\Log\LoggerInterface tend to end up with unstructured messages, hardcoded channel and prefix logic scattered across the codebase, and ad hoc serialization of exceptions or domain objects repeated at every call site. This bundle centralizes that as a single reusable service, with the project-specific bits (channel name, message prefix) exposed as configuration instead of hardcoded.

Solution

  • Logger (src/Service/Logger.php): a final service with the full 8-level PSR-3 interface (emergency → debug). Every method takes a \BackedEnum $context (any backed enum — no bundle-specific marker interface to implement), a string $message, and an array $parameters. It formats the message as "{$context->value} - {$message}" and runs every parameter through the first tagged processor whose supports() returns true.
  • LogParameterProcessorInterface (src/Contract/): the extensibility contract. Any class implementing it — in this bundle or in a consuming project — is auto-registered and auto-injected into Logger, no service configuration needed. Tagging is wired imperatively by LoggerExtension::load() via $container->registerForAutoconfiguration(), not by a #[AutoconfigureTag] attribute on the interface — that attribute only self-applies when the consuming app's own resource-scanned service discovery happens to walk the file declaring it, which never happens for a file living in vendor/. Do not "simplify" this back to an attribute; it silently breaks processor autowiring for every consuming project's own custom processors (see tests/Functional/LoggerFunctionalTest.php::testACustomProcessorRegisteredOutsideTheBundleIsAutowired, which is designed to fail if this regresses).
  • ThrowableLogParameterProcessor (src/Processor/): the only built-in processor, serializing \Throwable (recursively, including getPrevious()). Registered by LoggerExtension when enable_builtin_processors is true (default) — a consuming project that wants its own \Throwable handling can set it to false.
  • LoggerExtension (src/DependencyInjection/): binds Logger's PSR logger argument to the configured Monolog channel (reused, not created — the consuming project must declare it under its own monolog.channels), conditionally registers the built-in processors and MessagePrefixProcessor. Every service and argument in this bundle is wired explicitly — no autowire()/autoconfigure() on the bundle's own services.php — so its dependency graph never depends on the consuming app's container. See "Symfony Bundles" below.
  • DisallowLoggerInterfaceRule (src/PHPStan/): opt-in PHPStan rule a consuming project can register in its own phpstan.neon to forbid injecting Psr\Log\LoggerInterface directly, forcing all logging through Logger.

🏗️ Architecture

Overview

LogContext enum (any \BackedEnum, defined per-project, per-domain)
         │
         ▼
     Logger::error($context, $message, $parameters)
         │
         ├─ formatMessage()      "{$context->value} - {$message}"
         ├─ processParameters()  each value → first supports()-matching processor
         │
         ▼
   monolog.logger.<channel>  (an existing channel — declared by the consuming project under
         │                    monolog.channels, never created by this bundle)
         │
         ▼
   [MessagePrefixProcessor]  (only if message_prefix configured — Monolog processor tag)
         │
         ▼
      Monolog handlers (project's own monolog.yaml)

Main Components

  • src/Service/Logger.php — the public API, the only class most consuming code touches.
  • src/Contract/LogParameterProcessorInterface.php — the extension point for parameter serialization. Consuming projects add their own implementations freely.
  • src/Processor/ThrowableLogParameterProcessor.php — default \Throwable processor.
  • src/Monolog/MessagePrefixProcessor.php — optional message-prefixing Monolog processor.
  • src/DependencyInjection/{Configuration,LoggerExtension}.php — config schema (channel, message_prefix, enable_builtin_processors) and wiring (channel argument binding, conditional processor registration).
  • src/PHPStan/DisallowLoggerInterfaceRule.php — optional enforcement rule.

🚀 Typical Use Cases

  • Any Symfony app that wants one consistent, structured logging entry point instead of scattering LoggerInterface injections and ad hoc $logger->info('message: ' . $foo) string concatenation across the codebase.
  • Logging domain events with a typed context (enum OrderLogContext: string { case PaymentFailed = 'PaymentFailed'; }) instead of free-text categories.
  • Logging exceptions (or other domain objects, via a custom processor) without manually extracting getMessage()/getTrace()/etc. at every call site.

💡 Design Patterns Used

  • Strategy pattern for parameter processors (LogParameterProcessorInterface): first-match-wins over a tagged iterable, so behavior is added by writing a new class, never by editing Logger itself.
  • Marker-less extensibility: log contexts are plain \BackedEnum cases — no interface to implement, no bundle coupling in domain code, just a backed enum per domain.

Project breakdown

  • src/LoggerBundle.php — bundle entry point.
  • src/Contract/ — the one public extension point (LogParameterProcessorInterface).
  • src/Processor/ — bundled processor implementations (currently: throwables only).
  • src/Monolog/ — Monolog-specific processors (currently: message prefixing).
  • src/Service/Logger.php — the logger service.
  • src/DependencyInjection/ — configuration schema and container wiring.
  • src/PHPStan/ — optional static-analysis enforcement.
  • tests/App/ — minimal Symfony test application (FrameworkBundle + MonologBundle + LoggerBundle) used to boot a real kernel in Functional/Integration tests.

IMPORTANT: This section should evolve with the project. When a new feature is created, updated or removed, this section should too.

🧪 Testing

This bundle is covered by unit, integration, functional, and PHPStan-rule tests, in tests/{Unit|Integration|Functional|PHPStan}. Unit tests use PHPUnit mocks/stubs. Integration tests exercise LoggerExtension directly against a ContainerBuilder (no HTTP kernel). Functional tests boot tests/App/TestKernel end-to-end and assert against a Monolog\Handler\TestHandler — including a processor defined outside the bundle, to prove the extensibility contract actually autowires for a consuming project. PHPStan-rule tests use PHPStan\Testing\RuleTestCase against fixtures in tests/PHPStan/data/ (excluded from this project's own CS-Fixer/PHPStan runs — they intentionally contain violations).


Remarks & Guidelines

General

  • NEVER commit or push the git repository.
  • When unsure about something, you MUST ask the user for clarification. Same goes if the user request is unclear.
  • When facing a problem that has an easy "hacky" solution, and a more robust but more difficult to implement one, always choose the robust one:
    • Easy hacky fixes become technical debt, and can lead to issues down the road
    • Robust solutions means the project will remain serious and well-built.
  • ALWAYS write tests for the important components. Better safe than sorry!
  • Do NOT write ANY type documentation unless explicitly asked.
  • Once a feature is complete, update the @README.md and @AGENTS.md accordingly.
  • The @README.md file should consist of a project overview for end-users, not a technical explanation of the project.
  • Keep comments and documentation succinct — state what's non-obvious, don't over-explain.

Commits

  • Format: type(domain): quick explanation, where type is one of feat, fix, docs, tests. Keep the explanation short — this is a summary line, not a changelog entry.

Symfony Bundles

  • This bundle is meant to be installed via Composer across multiple Symfony projects. When developing features, keep this in mind: no hardcoded channel names, prefixes, or project-specific assumptions — those belong in configuration.
  • Architecture, naming, design, extensibility and easiness to install and use should be key priorities to consider when developing this project.
  • Never use autowire()/autoconfigure() defaults for the bundle's own services in src/Resources/config/services.php. Wire every argument explicitly (Reference, tagged_iterator(), Definition::addTag()) so the bundle's dependency graph is fixed and doesn't depend on what the consuming app's container happens to contain. This does not apply to consuming projects' own code — they can and should autowire normally; it's only about this bundle's own service definitions.

📚 References

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle