Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Composer\InstalledVersions;
use Monolog\Logger;
use Symfony\Bundle\MonologBundle\DependencyInjection\Handler\HandlerExtensionInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand Down Expand Up @@ -402,6 +403,14 @@
*/
class Configuration implements ConfigurationInterface
{
/**
* @param list<HandlerExtensionInterface> $handlerExtensions
*/
public function __construct(
private array $handlerExtensions = [],
) {
}

/**
* Generates the configuration tree builder.
*/
Expand Down Expand Up @@ -665,21 +674,53 @@ public function getConfigTreeBuilder(): TreeBuilder

$this->addGelfSection($handlerNode);
$this->addMongoSection($handlerNode);
$this->addMongoDBSection($handlerNode);
// $this->addMongoDBSection($handlerNode);
$this->addElasticsearchSection($handlerNode);
$this->addRedisSection($handlerNode);
$this->addPredisSection($handlerNode);
$this->addMailerSection($handlerNode);
$this->addVerbosityLevelSection($handlerNode);
$this->addChannelsSection($handlerNode);

$types = [];
\assert($handlerNode instanceof ArrayNodeDefinition);
foreach ($this->handlerExtensions as $handlerExtension) {
$types[$handlerExtension->getName()] = [];
$arrayNode = $handlerNode->children()->arrayNode($handlerExtension->getName());
$handlerExtension->buildArrayNode($arrayNode);
foreach ($arrayNode->getChildNodeDefinitions() as $name => $childNodeDefinition) {
$types[$handlerExtension->getName()][] = $name;
}
}

$handlerNode
->beforeNormalization()
->always(static function ($v) {
->always(static function ($v) use ($types) {
if (empty($v['console_formatter_options']) && !empty($v['console_formater_options'])) {
$v['console_formatter_options'] = $v['console_formater_options'];
}

if (!\array_key_exists('type', $v)) {
$handlerFields = array_intersect_key($types, $v);
if (0 === \count($handlerFields)) {
throw new InvalidConfigurationException('The handler type could not be determined automatically. Please specify the "type" option.');
}

if (1 < \count($handlerFields)) {
throw new InvalidConfigurationException('The handler type could not be determined automatically as multiple handler types are configured: '.implode(', ', array_keys($handlerFields)).'. Please specify the "type" option.');
}

$v['type'] = key($handlerFields);
} elseif (isset($types[$type = $v['type'] ?? 'null']) && !isset($v[$type])) {
// migrate old config where type was also a key
foreach ($types[$type] as $field) {
if (isset($v[$field])) {
$v[$type][$field] = $v[$field];
unset($v[$field]);
}
}
}

return $v;
})
->end()
Expand Down
53 changes: 53 additions & 0 deletions src/DependencyInjection/Handler/DefaultHandlerExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\DependencyInjection\Handler;

use Monolog\Handler\AbstractHandler;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;

/**
* Handlers using the constructor of {@see AbstractHandler} without adding their own arguments.
*/
final class DefaultHandlerExtension implements HandlerExtensionInterface
{
/**
* @param non-empty-string $name
* @param class-string<AbstractHandler> $handlerClass
*/
public function __construct(
private string $name,
private string $handlerClass,
) {
}

public function getName(): string
{
return $this->name;
}

public function buildArrayNode(ArrayNodeDefinition $handlerNode): void
{
// No specific configuration
}

public function getDefinition(array $config, array $handler): Definition
{
$definition = new Definition($this->handlerClass);
$definition->setArguments([
$config['level'],
$config['bubble'],
]);

return $definition;
}
}
38 changes: 38 additions & 0 deletions src/DependencyInjection/Handler/HandlerExtensionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\DependencyInjection\Handler;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;

interface HandlerExtensionInterface
{
/**
* Returns the name of the handler and configuration subtree.
*/
public function getName(): string;

/**
* Add properties for the handler type.
*/
public function buildArrayNode(ArrayNodeDefinition $handlerNode): void;

/**
* @param array{
* level: string|int,
* bubble: bool,
* formatter: string|null,
* } $config Generic options
* @param array{} $handler Specific handler options
Copy link
Member

Choose a reason for hiding this comment

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

this is wrong. array{} is the shape representing an empty array.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is on purpose. By default this is an empty array. You have to specify another array shape in subclasses.

Copy link
Member

Choose a reason for hiding this comment

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

That's not the proper way to describe such API. We would need to use a generic type with THandlerConfig of array<string, mixed>, with child classes describing their array shape as the template type.

*/
public function getDefinition(array $config, array $handler): Definition;
}
113 changes: 113 additions & 0 deletions src/DependencyInjection/Handler/MongoDBHandlerExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\DependencyInjection\Handler;

use MongoDB\Client;
use Monolog\Formatter\MongoDBFormatter;
use Monolog\Handler\MongoDBHandler;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class MongoDBHandlerExtension implements HandlerExtensionInterface
{
public function getName(): string
{
return 'mongodb';
}

public function buildArrayNode(ArrayNodeDefinition $handlerNode): void
{
$handlerNode
->canBeUnset()
->beforeNormalization()
->ifString()
->then(function ($v) { return ['id' => $v]; })
->end()
->children()
->scalarNode('id')
->info('ID of a MongoDB\Client service')
->example('doctrine_mongodb.odm.logs_connection')
->end()
->scalarNode('uri')->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->scalarNode('database')->defaultValue('monolog')->end()
->scalarNode('collection')->defaultValue('logs')->end()
->end()
->validate()
->ifTrue(function ($v) {
return !isset($v['id']) && !isset($v['uri']);
})
->thenInvalid('The "mongodb" handler configuration requires either a service "id" or a connection "uri".')
->end()
->example([
'uri' => 'mongodb://localhost:27017',
'database' => 'monolog',
'collection' => 'logs',
])
;
}

/**
* @param array{
* id?: string,
* uri?: string,
* username?: string,
* password?: string,
* database: string,
* collection: string,
* } $handler
*/
public function getDefinition(array $config, array $handler): Definition
{
$definition = new Definition(MongoDBHandler::class);

if (!class_exists(Client::class)) {
throw new \RuntimeException('The "mongodb" handler requires the mongodb/mongodb package to be installed.');
}

if (isset($handler['id'])) {
$client = new Reference($handler['id']);
} else {
$uriOptions = ['appname' => 'monolog-bundle'];

if (isset($handler['username'])) {
$uriOptions['username'] = $handler['username'];
}

if (isset($handler['password'])) {
$uriOptions['password'] = $handler['password'];
}

$client = new Definition(Client::class, [
$handler['uri'],
$uriOptions,
]);
}

$definition->setArguments([
$client,
$handler['database'],
$handler['collection'],
$config['level'],
$config['bubble'],
]);

if (empty($config['formatter'])) {
$formatter = new Definition(MongoDBFormatter::class);
$definition->addMethodCall('setFormatter', [$formatter]);
}

return $definition;
}
}
113 changes: 113 additions & 0 deletions src/DependencyInjection/Handler/MongoHandlerExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\MonologBundle\DependencyInjection\Handler;

use MongoDB\Client;
use Monolog\Handler\MongoDBHandler;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

final class MongoHandlerExtension implements HandlerExtensionInterface
{
public function getName(): string
{
return 'mongo';
}

public function buildArrayNode(ArrayNodeDefinition $handlerNode): void
{
$handlerNode
->canBeUnset()
->beforeNormalization()
->ifString()
->then(function ($v) { return ['id' => $v]; })
->end()
->children()
->scalarNode('id')->end()
->scalarNode('host')->end()
->scalarNode('port')->defaultValue(27017)->end()
->scalarNode('user')->end()
->scalarNode('pass')->end()
->scalarNode('database')->defaultValue('monolog')->end()
->scalarNode('collection')->defaultValue('logs')->end()
->end()
->validate()
->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['host']); })
->thenInvalid('The "mongo" handler configuration requires either a service "id" or a connection "host".')
->end()
->validate()
->ifTrue(function ($v) { return isset($v['user']) && !isset($v['pass']); })
->thenInvalid('If you set user, you must provide a password.')
->end()
->example([
'host' => 'localhost',
'port' => 27017,
'database' => 'monolog',
'collection' => 'logs',
])
;
}

/**
* @param array{
* mongo: array{
* id?: string,
* host?: string,
* port: int|string,
* user?: string,
* pass?: string,
* database: string,
* collection: string,
* },
* level: int|string,
* bubble: bool,
* formatter?: string,
* } $handler
*/
public function getDefinition(array $config, array $handler): Definition
{
// Trigger the existing deprecation so behavior remains identical
trigger_deprecation('symfony/monolog-bundle', '3.11', 'The "mongo" handler type is deprecated in MonologBundle since version 3.11.0, use the "mongodb" type instead.');

if (!class_exists(Client::class)) {
throw new \RuntimeException('The "mongo" handler requires the mongodb/mongodb package to be installed.');
}

$definition = new Definition(MongoDBHandler::class);

if (isset($handler['id'])) {
$client = new Reference($handler['id']);
} else {
$server = 'mongodb://';
if (isset($handler['user'])) {
$server .= $handler['user'].':'.$handler['pass'].'@';
}
$server .= $handler['host'].':'.$handler['port'];

$client = new Definition(Client::class, [
$server,
['appname' => 'monolog-bundle'],
]);
}

$definition->setArguments([
$client,
$handler['database'],
$handler['collection'],
$config['level'],
$config['bubble'],
]);

return $definition;
}
}
Loading