How to set an config parameter from console argument? - symfony

I'm a newbie in Symfony.
I trying to change Monolog output formatter by console argument 'format=json'.
In short, I want to run any console command with the way:
app/console my_command --format=json # xml / txt / my own
...and get output of LoggerInterface in requested format.
For example, I set the default formatter in configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: json_formatter
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
When I create the some MyEventListener::onConsoleCommand (as described here), I cannot change the parameters bag because it is already compiled: "Impossible to call set() on a frozen ParameterBag."
Up2: My services config in this case looks like this:
services:
kernel.listener.command_dispatch:
class: My\Listener\MyEventListener
autowire: true
tags:
- { name: kernel.event_listener, event: console.command }
With another way, I can register console option inside initial file:
# app/console
$loader = require __DIR__.'/autoload.php';
# ...
$application->getDefinition()->addOption(
new InputOption(
'formatter',
'f',
InputOption::VALUE_OPTIONAL,
'The logs output formatter',
'json_formatter'
)
);
But I can't find a way to change parameters bag in the Container. Because $application->getKernel()->getContainer() is still empty.
So, how to change Symfony2 parameters from console input?
Alternatively, maybe I can just use some environments parameters? But how I can get an environment variable in YAML configuration?
Thank you.
UP3:
I have achieved the goal with environments variables like this:
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command
monolog:
handlers:
console:
type: console
#...
formatter: '%log.formatter%'

The only point to modify command arguments for every registered command of your application is handling CommandEvents::COMMAND that is triggered before any command has been executed. So you can modify its arguments and read them as described here. Also, at this point you have your container compiled and it is not possible to modify service's definitions at this point. But you can get any service.
So i think you can end up with following handler:
class LogFormatterEventListener
{
private $container;
private $consoleHandler;
public function __construct(ContainerInterface $container, HandlerInterface $consoleHandler)
{
$this->container = $container;
$this->consoleHandler = $consoleHandler;
}
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$inputDefinition = $event->getCommand()->getApplication()->getDefinition();
$inputDefinition->addOption(
new InputOption('logformat', null, InputOption::VALUE_OPTIONAL, 'Format of your logs', null)
);
// merge the application's input definition
$event->getCommand()->mergeApplicationDefinition();
$input = new ArgvInput();
// we use the input definition of the command
$input->bind($event->getCommand()->getDefinition());
$formatter = $input->getOption('logformat');
if ($formatter /** && $this->container->has($formatter) **/) {
$this->consoleHandler->setFormatter(
$this->container->get($formatter);
);
}
}
}

Here is alternative solution (compatible with common):
Configuration:
monolog:
handlers:
console:
type: console
channels: [!event, !doctrine]
formatter: "%log.formatter%"
services:
json_formatter:
class: Monolog\Formatter\JsonFormatter
Command execution:
# colored plain text
app/console my_command
# json
SYMFONY__LOG__FORMATTER=json_formatter app/console my_command

Related

Monolog handler ignoring yaml level

In Symfony 4.3 using Monolog, I have created a custom handler to push logs to AWS Firehose. Here is the constructor:
class FirehoseLogHandler extends AbstractProcessingHandler {
public function __construct(
FirehoseClient $firehoseClient,
FormatterInterface $formatter,
$streamName,
$level = Logger::INFO,
$bubble = true
) {
$this->firehoseClient = $firehoseClient;
$this->streamName = $streamName;
$this->formatter = $formatter;
parent::__construct($level, $bubble);
}
And here is my monolog.yaml config:
monolog:
handlers:
firehose_handler:
type: service
id: kinesis_stream_handler
main:
type: stream
handler: firehose_handler
level: error
channels: ["!event"]
services:
kinesis_stream_handler:
class: App\Logger\Handler\FirehoseLogHandler
arguments:
$firehoseClient: '#aws.firehose'
$formatter: '#App\Logger\Formatter\FirehoseLogFormatter'
$streamName: 'firehose-stream-test'
The problem that I am having is that the $level is always set to Logger::INFO (200), or whatever is set in the constructor. It seems to be ignoring what is set in the yaml config.
What am I doing wrong here? I know I could always add $level: 400 to the service declaration but that doesn't seem to make much sense. Appreciate any help in advance.
Handlers defined as type: service are used strictly as-is, since they are instances you have already constructed. They are not passed any arguments from the main monolog configuration; that would only apply to services it is constructing for you. So you do need to add $level as an explicit argument to your custom handler for this reason.
There may be some further confusion stemming from your main handler definition. While handler is a valid configuration key, it does not apply to a stream handler as it only makes sense for handlers that wrap others, such as filter. So that is simply being ignored.
The full list of handler types and what configuration keys actually apply to each can be found in the code here.

Symfony 2 log command output

Is it possible to log a console command`s output to a log file ?
For example when a command has:
protected function execute(InputInterface $input, OutputInterface $output)
{
...
$output->writeln('command run');
...
}
How to log the output without modifying the execute method ?
According to Symfony2 documentation:
The Console component doesn't provide any logging capabilities out of the box. Normally, you run console commands manually and observe the output, which is why logging is not provided.
http://symfony.com/doc/2.8/console/logging.html
However you could use service container inside the command:
$logger = $this->getContainer()->get('logger');
And log some events:
$logger->info('Hello: '.$variable);
You should see the logged entries in app/logs/dev.log or app/logs/prod.log.
This is my approach to use the logger service in console commands.
First I configure the channel and handler for my logger, it is configured on config.yml
My custom channel is named as con and the handler as conlog, but you can put names you want:
# app/config/config.yml
monolog:
channels: ['con']
handlers:
conlog:
type: rotating_file
max_files: 31
path: %kernel.logs_dir%/command.log
level: debug
channels: ['con']
I use a rotating_file type because my console command is executed all days by a scheduled task and
I need to do a daily tracking of the command but you can use the type you need. More info about handler types in docs
Now you can use the logger in your console command, the important thing here is to inject monolog service using the automatically registered logger service with the name of the channel, in my sample it is:
monolog.logger.con
More info about this in docs
// src/AcmeBundle/Command/YourCommand
class YourCommand extends ContainerAwareCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getContainer()->get('monolog.logger.con');
$myMessage = 'The first message';
$output->writeln($myMessage);
$logger->info($myMessage);
}
}
And this is all, in this sample the log is saved in log directory with file names as: command-Y-m-d.log.
Finally it is important remarks that the con channel logger messages are added to dev.log too. If you want prevent it, it will be needed to configure channels option in main handler, on config_dev.yml, and prefix with ! our custom channel name:
# app/config/config_dev.yml
monolog:
handlers:
main:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: debug
channels: ['!con']

symfony get container not in controller and without service

i have problem with get my class function to which i send files from terminal. It's see that:
terminal: // php src/AppBundle/Provider/CommissionCost.php test.csv
in CommissionCost i want only put data to my function in Parser/CommissionDataParser.php
global $kernel;
$data = $kernel->getContainer()->get('data.parser')->getData($argv[1]);
var_dump($data);
//Uncaught Error: Call to a member function getContainer() on null
// this example i see in stackoverflow in topic about container : )
service:
services:
data.parser:
class: AppBundle\Parser\CommissionDataParser
arguments: ["#doctrine.orm.entity_manager"]
You need to add this in the configure method:
$this->addArgument('arg1', InputArgument::REQUIRED, 'Your first arg');
Then, you can access to it in the execute method by:
$input->getArgument('arg1');

Having mapping issues when trying to save monolog logs into Elasticsearch use ElasticsearchHandler

I'm trying to start sending my logs into elastic search using monolog. (I'm using Symfony2).
I've set up monolog like this:
monolog:
handlers:
elasticsearch:
elasticsearch:
host: %logger_elastic_host%
port: %logger_elastic_port%
type: elasticsearch
level: info
It worked only few minutes until it broke with this error messages(a fatal error, I removed useless stuff):
create: /monolog/logs/AVQKYsGRPmEhlo7mDfrN caused
MapperParsingException[failed to parse [context.stack.args]]; nested:
ElasticsearchIllegalArgumentException[unknown property [class]];
I've been looking with my collegue how to fix that. What we found out is:
Elastic search receive the first logs and automatically build a mapping
We send new logs with another mapping or slightly different to what was sent before and it breaks.
In this case it's breaking here: context.stack.args.
The problem is that the context will always be very different.
What we would like is:
is anyone out there using Monolog to log to Elasticsearch
How do you guys manage to avoid this issue. (How can we manage to avoid it)?
thanks guys.
This is happening because ES creates a mapping from the first document. If any document that is inserted after has the same property but with other type/format then ES will throw an error.
A solution is to create a custom monolog formatter and register it:
config.yml:
elasticsearch:
type: elasticsearch
elasticsearch:
host: elasticsearch
ignore_error: true
formatter: my_elasticsearch_formatter
This line will make Monolog\Handler\ElasticSearchHandler ignore any other errors from Ruflin's Elastica package:
ignore_error: true
Then register a service with this name: my_elasticsearch_formatter:
<service id="my_elasticsearch_formatter" class="AppBundle\Services\MyFormatter">
<argument type="string">monolog</argument>
<argument type="string">logs</argument>
</service>
first argument is the index name, second arg is the type.
And the formatter class:
<?php
namespace AppBundle\Services;
use function json_encode;
use Monolog\Formatter\ElasticaFormatter;
use function var_dump;
class MyFormatter extends ElasticaFormatter
{
/**
* #param string $index
* #param string $type
*/
public function __construct($index, $type)
{
parent::__construct($index, $type);
}
/**
* #param array $record
* #return array|\Elastica\Document|mixed|string
*/
public function format(array $record)
{
$record['context'] = json_encode($record['context']);
return parent::format($record);
}
}
The downside of this solution is that it will json_encode the context. You will not be able to filter by inner properties of the context in ES but at least you will not lose important information about your logs.

Add configuration when run cache:clear

I look for the way to inject more parameters into symfony configuration cache. Currently, I use kernel.cache_warmer hook to my class in services.yml to generate another yml file in a directory. Then, it will be include in the symfony configuration cache, are there any possible way to inject a variable into generated config cache without need to create the Yml file?
Basically, I would like to make cache key changed everytime when run app/console cache:clear. Here is my service,
services.yml
imports:
- { resource: version.yml }
services:
cacheManager:
class: "%cacheManager.class%"
calls:
- [ setCachePrefix, ["%memcache.deploymentPrefix%"]]
memcacheDataVersioning:
class: WarmUpListener
tags:
- { name: kernel.cache_warmer, priority: 0}
WarmUpListener.php
class WarmUpListener implements CacheWarmerInterface{
public function warmUp($dir)
{
$array = ['parameters' => ['memcache.deploymentPrefix' => date('Ymd')]];
$dumper = new Dumper();
$yaml = $dumper->dump($array);
file_put_contents(__DIR__ . '/../Resources/config/version.yml', $yaml);
}
public function isOptional()
{
return false;
}
}
I have added to the DependencyInjection/*Extension class as below
DependencyInjection/somethingExtension.php
$container->setParameter('memcache.deploymentPrefix', date('Ymd') );
This will help to inject the variable in the configuration cached without need to make the Yml file and can removed all warmUp hookup on the question.

Resources