> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Estendere e risolvere di nuovo i channel di log nei pacchetti

> Scegliere tra custom/via e LogManager::extend() in Laravel 13 e testare le funzionalità di log tenendo conto della conservazione dei channel, di forgetChannel() e del fallback.

Quando un pacchetto deve fornire un proprio output di log, separa **il meccanismo che crea il logger** dalla **configurazione dei channel scelta dall'applicazione che usa il pacchetto**. Registrare un driver non ricrea i channel esistenti, e anche dopo una modifica della configurazione può capitare che venga ancora usato il vecchio Handler.

Questa pagina esamina l'implementazione di Laravel Framework `v13.39.0`. Per le operazioni di base consulta [Logging](https://kawax.biz/it/logging.md), per la preparazione del provider consulta [Sviluppo di pacchetti](https://kawax.biz/it/advanced/package-development.md).

## Scegliere il punto di estensione necessario

Se puoi ottenere il risultato con un driver esistente, non serve aggiungere un driver personalizzato.

| Obiettivo | Opzione | Dove si estende |
| - | - | - |
| Regolare il formatter o altri aspetti di un channel esistente | `tap` | L'`Illuminate\Log\Logger` generato |
| Scegliere l'Handler di Monolog tramite configurazione | `driver: monolog` | Opzioni come `handler` e `handler_with` |
| Costruire il logger con una factory del pacchetto | `driver: custom` e `via` | Una classe con `__invoke(array $config)` |
| Esporre un nome di driver specifico del pacchetto | `LogManager::extend()` | Una closure che riceve l'applicazione e l'array di configurazione |

Le classi di `via` e di `tap` vengono risolte dal container, quindi puoi usare anche l'iniezione nel costruttore. `tap` è una regolazione eseguita alla creazione del channel, non un hook eseguito a ogni singola scrittura di log.

> [!NOTE]
>
> `audit` in `logging.channels.audit` è il **nome del channel**. Il `driver` al suo interno è il **nome del metodo di creazione**. Con `Log::channel('audit')` si sceglie il nome del channel, quindi anche se in seguito cambi il driver puoi mantenere invariato il nome usato dal codice chiamante.

## Esporre prima una factory con custom e via

Nel pacchetto fittizio `Acme\Audit` forniamo una factory per l'output su file. La collochiamo in `src/Logging/CreateAuditLogger.php`, caricabile tramite PSR-4.

```php theme={null}
<?php

namespace Acme\Audit\Logging;

use Monolog\Handler\StreamHandler;
use Monolog\Logger;

final class CreateAuditLogger
{
    public function __invoke(array $config): Logger
    {
        return new Logger(
            $config['name'] ?? 'acme-audit',
            [new StreamHandler($config['path'], $config['level'] ?? 'info')],
        );
    }
}
```

Nell'applicazione che usa il pacchetto, aggiungi il seguente elemento all'array `channels` esistente in `config/logging.php`. Mantieni le configurazioni esistenti di `default` e `stack`.

```php theme={null}
'audit' => [
    'driver' => 'custom',
    'via' => \Acme\Audit\Logging\CreateAuditLogger::class,
    'path' => storage_path('logs/audit.log'),
    'level' => 'info',
],
```

`LogManager::createCustomDriver()` recupera la classe di `via` dal container e la invoca passando l'array di configurazione del channel come unico argomento. Con questo solo approccio non serve un provider per registrare il driver.

```php theme={null}
use Illuminate\Support\Facades\Log;

Log::channel('audit')->info('Ordine confermato.', ['order_id' => 42]);
```

Questa factory passa esplicitamente `level` allo StreamHandler. **LogManager non applica in modo uniforme il `level` della configurazione al logger restituito da una factory `custom`**. Anche quando aggiungi opzioni di configurazione personalizzate, applicale nella factory.

> [!TIP]
>
> Se ti serve solo cambiare la destinazione del file, il driver `single` è sufficiente. Questo esempio è la configurazione minima per integrare un Handler o un'inizializzazione specifici del pacchetto. Per `via` usa un nome di classe memorizzabile nella cache della configurazione e non salvare closure o istanze di logger nell'array di configurazione.

## Usare extend per fornire un nome di driver personalizzato

Se vuoi esporre all'utente una configurazione come `driver: acme-audit`, registra un driver che usa la stessa factory. Nel metodo `boot()` di `src/AuditServiceProvider.php` estendi il servizio `log` di Laravel.

```php theme={null}
<?php

namespace Acme\Audit;

use Acme\Audit\Logging\CreateAuditLogger;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Log\LogManager;
use Illuminate\Support\ServiceProvider;
use Monolog\Logger as Monolog;

class AuditServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->callAfterResolving('log', static function (LogManager $manager): void {
            $manager->extend('acme-audit', static function (Application $app, array $config): Monolog {
                return $app->make(CreateAuditLogger::class)($config);
            });
        });
    }
}
```

Il provider viene caricato, ad esempio, tramite il [package auto-discovery](https://kawax.biz/it/advanced/package-discovery.md). Nell'applicazione, sostituisci la configurazione precedente di `channels.audit` con la seguente. `via` non è più necessario.

```php theme={null}
'audit' => [
    'driver' => 'acme-audit',
    'path' => storage_path('logs/audit.log'),
    'level' => 'info',
],
```

* Il `LogServiceProvider` di Laravel registra `log` come singleton. `callAfterResolving()` attende se il servizio non è ancora stato risolto e, se è già risolto, applica la registrazione anche a quell'istanza. Per i dettagli consulta [Hook di risoluzione dei servizi](https://kawax.biz/it/advanced/package-service-resolution.md).
* `extend()` registra soltanto la closure di creazione. Al momento della registrazione di questa callback non vengono creati channel né scritti log.
* `callCustomCreator()` passa alla closure `$app` e l'array di configurazione del channel. Gli argomenti sono diversi da quelli passati alla factory di `via`.
* Registrare di nuovo lo stesso nome di driver sovrascrive la closure di creazione. Non usare nomi integrati come `single` né nomi che possano entrare in conflitto con altri pacchetti.

> [!WARNING]
>
> Nell'implementazione esaminata, la closure anonima passata a `extend()` viene ricollegata allo scope di LogManager. In una closure non static, `$this` non è il service provider. Come nell'esempio, usa una closure static che si basa sull'argomento `$app`, senza dipendere dal `$this` del provider.

Il pacchetto fornisce il driver, mentre è l'utente a scegliere in quale channel o stack usarlo. Progetta il provider in modo che non riscriva `logging.default` né l'intero `logging.channels` esistente.

## I channel vengono conservati per nome

`LogManager::get()` salva i channel risolti in `$channels`. Quando richiedi un channel con lo stesso nome, restituisce l'oggetto conservato senza rileggere l'array di configurazione.

```mermaid theme={null}
flowchart TD
    A["Log::channel('audit')"] --> B{"audit è già risolto?"}
    B -->|Yes| C["Restituisce il Logger conservato"]
    B -->|No| D["Seleziona il driver di channels.audit"]
    D --> E["Factory di via o di extend"]
    E --> F["Avvolge in un Illuminate Logger<br>Applica tap e il contesto condiviso"]
    F --> G["Conserva con il nome audit"]
```

Si tratta della **conservazione degli oggetti all'interno del LogManager corrente**, distinta dalla cache della configurazione salvata su file. Modificare `config()` o registrare di nuovo `extend()` non basta a sostituire un Handler già creato.

```php theme={null}
use Illuminate\Support\Facades\Log;

$before = Log::channel('audit');

config(['logging.channels.audit.level' => 'error']);

$same = Log::channel('audit');

Log::forgetChannel('audit');

$after = Log::channel('audit');
```

In questo esempio `$same` è lo stesso oggetto di `$before`. Quando `forgetChannel('audit')` rimuove la conservazione nel Manager, alla richiesta successiva `$after` viene generato dalla nuova configurazione.

### forgetChannel non aggiorna i riferimenti esistenti

`forgetChannel()` si limita a eliminare la voce all'interno del Manager.

* Un servizio che conserva `$before` può continuare a usare il vecchio logger. Non è un'operazione che sostituisce i riferimenti esistenti con il nuovo oggetto.
* Uno stack già generato, ad esempio con `Log::channel('stack')`, possiede un Monolog indipendente che raccoglie gli Handler e i Processor dei channel figli. Applicare `forgetChannel()` solo a un figlio non modifica la composizione dello stack esistente. Se necessario, rimuovi la conservazione anche per il nome di channel dello stack e richiedilo di nuovo.
* `forgetChannel()` di per sé non chiama `close()` sugli Handler. Non è un'API che garantisce la chiusura di connessioni o buffer.

Nei normali deploy, includi nelle procedure operative l'[aggiornamento della cache della configurazione](https://kawax.biz/it/advanced/package-config-merging.md) e il riavvio dei worker di lunga durata in uso. Non affidarti a un meccanismo che riscrive la configurazione e risolve di nuovo i channel a ogni richiesta, e considera anche il [ciclo di vita dei servizi](https://kawax.biz/it/advanced/package-service-lifetimes.md).

### L'integrazione in uno stack richiede anche operazioni compatibili con Monolog

Un driver personalizzato può restituire un'implementazione di `Psr\Log\LoggerInterface`, ma `createStackDriver()` di Laravel chiama `getHandlers()` e `getProcessors()` sui channel figli. Questi metodi non fanno parte dell'interfaccia PSR.

Restituire un qualsiasi logger PSR non garantisce che funzioni anche in uno stack, quindi i pacchetti che supportano anche l'integrazione negli stack dovrebbero restituire un Monolog come nell'esempio, oppure documentare chiaramente l'ambito d'uso e testarlo.

## Verificare la risoluzione reale dei channel, non solo i mock

Con il solo `Log::shouldReceive('channel')` non puoi verificare la registrazione del provider, la scelta della factory né l'applicazione della configurazione. Di seguito un esempio che, nell'[ambiente di test dei pacchetti](https://kawax.biz/it/advanced/package-testing.md), passa per il Manager e la factory reali.

Come destinazione dell'output nei test specifichiamo `php://memory`. Qui verifichiamo la costruzione del channel e la configurazione dell'Handler, senza scrivere effettivamente log.

```php theme={null}
<?php

namespace Acme\Audit\Tests;

use Acme\Audit\AuditServiceProvider;
use Acme\Audit\Logging\CreateAuditLogger;
use Illuminate\Log\Logger;
use Illuminate\Support\Facades\Log;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger as Monolog;
use Orchestra\Testbench\TestCase;

class AuditLoggingTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [AuditServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('logging.channels.audit', [
            'driver' => 'acme-audit',
            'path' => 'php://memory',
            'level' => 'warning',
        ]);

        $app['config']->set('logging.channels.emergency.path', 'php://memory');
    }

    public function test_the_package_driver_creates_the_configured_logger(): void
    {
        $this->assertAuditChannel(Log::channel('audit'), Level::Warning);
    }

    public function test_configuration_changes_require_a_new_channel(): void
    {
        $before = Log::channel('audit');

        config(['logging.channels.audit.level' => 'error']);

        $this->assertSame($before, Log::channel('audit'));
        $this->assertAuditChannel($before, Level::Warning);

        Log::forgetChannel('audit');

        $after = Log::channel('audit');

        $this->assertNotSame($before, $after);
        $this->assertAuditChannel($after, Level::Error);
        $this->assertAuditChannel($before, Level::Warning);
    }

    public function test_the_same_factory_can_be_used_with_via(): void
    {
        config([
            'logging.channels.audit.driver' => 'custom',
            'logging.channels.audit.via' => CreateAuditLogger::class,
        ]);

        Log::forgetChannel('audit');

        $this->assertAuditChannel(Log::channel('audit'), Level::Warning);
    }

    private function assertAuditChannel(Logger $channel, Level $level): void
    {
        $logger = $channel->getLogger();

        $this->assertInstanceOf(Monolog::class, $logger);
        $this->assertSame('acme-audit', $logger->getName());
        $this->assertCount(1, $logger->getHandlers());

        $handler = $logger->getHandlers()[0];

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertSame($level, $handler->getLevel());
    }
}
```

> [!WARNING]
>
> `LogManager::get()` può catturare le eccezioni sollevate durante la creazione del channel e restituire l'emergency logger. Verificare soltanto che "non è stata sollevata alcuna eccezione" o che "si tratta di un LoggerInterface" non prova che il channel desiderato sia stato costruito. Il test qui sopra controlla anche il nome del Monolog, l'Handler e il livello di log, per non scambiare il fallback per un successo. Gli errori di scrittura successivi alla creazione non vengono necessariamente catturati dallo stesso meccanismo.

## Verifiche da aggiungere per la manutenzione a lungo termine

| Modifica o modalità d'uso | Cosa verificare |
| - | - |
| Aggiunta di `tap` | La regolazione attesa viene applicata al logger restituito dalla factory reale |
| Integrazione in uno stack | Gli Handler e i Processor dei channel figli vengono integrati e la composizione è corretta anche dopo una nuova risoluzione |
| Modifica della destinazione o del formato dell'output | Verificare in un ambiente di test separato il contenuto effettivamente scritto, i permessi, la rotazione e così via |
| Modifica del momento della registrazione | Il driver viene registrato e il channel può essere creato anche se `log` è già stato risolto |
| Aggiornamento di Laravel | Continuare a verificare argomenti e scope della callback, la rimozione della conservazione e il fallback all'emergency logger |

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: Logging](https://github.com/laravel/docs/blob/13.x/logging.md)
* [Framework v13.39.0: creazione, estensione e rimozione della conservazione in LogManager](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Log/LogManager.php)
* [Framework v13.39.0: wrapping e inoltro dei metodi in Logger](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Log/Logger.php)
* [Framework v13.39.0: registrazione singleton in LogServiceProvider](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Log/LogServiceProvider.php)
* [Framework v13.39.0: ServiceProvider::callAfterResolving](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/ServiceProvider.php)
* [Framework v13.39.0: RebindsCallbacksToSelf](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/RebindsCallbacksToSelf.php)
* [Test ufficiali di Laravel: LogManagerTest](https://github.com/laravel/framework/blob/v13.39.0/tests/Integration/Log/LogManagerTest.php)


## Related topics

- [Argomenti avanzati](https://kawax.biz/it/advanced/index.md)
- [Logging](https://kawax.biz/it/logging.md)
- [Tag del container e aggregazione di plugin nei pacchetti](https://kawax.biz/it/advanced/package-container-tags.md)
- [Hook di risoluzione dei servizi nei pacchetti](https://kawax.biz/it/advanced/package-service-resolution.md)
- [Progettare il ciclo di vita dei servizi di un pacchetto](https://kawax.biz/it/advanced/package-service-lifetimes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.