I have a Symfony command which is executed under a cron entry and I need some log in production environment.
As I read here in production, for console commands, verbosity level is related to log level shown, so I tried to configure monolog to show "notice" level.
My problem is, log is never written in prod environment.
This is my command:
use Psr\Log\LoggerInterface;
class MyCommand extends Command
{
private $logger;
public function __construct(LoggerInterface $logger)
{
parent::__construct();
$this->logger = $logger;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->notice('### HELLO ###');
}
}
And this is config/packages/prod/monolog.yml, see that action_level is set to notice:
monolog:
handlers:
main:
type: fingers_crossed
path: '%kernel.logs_dir%/%kernel.environment%.log'
action_level: notice
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
A file var/log/prod.log never appears.
Note: according to this SO answer, commands are always run in dev environment, but the log entries don't appear in dev.log either.
Related
I want to use the security.interactive_login event to update my User's last login field.
The event is successfully registered:
php bin/console debug:event-dispatcher security.interactive_login
Registered Listeners for "security.interactive_login" Event
===========================================================
------- ------------------------------------------------------------------------ ----------
Order Callable Priority
------- ------------------------------------------------------------------------ ----------
#1 App\EventSubscriber\UserLocaleSubscriber::onSecurityInteractiveLogin() 0
------- ------------------------------------------------------------------------ ----------
But it lands on Not called listeners in the Symfony profiler.
This is the event subscriber:
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
/** #var User $user */
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLoginAt(new DateTime());
$this->em->persist($user);
$this->em->flush();
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
];
}
}
And there is my security.yaml file:
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|fonts)/
security: false
main:
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginAuthenticator
logout:
path: app_logout
target: app_login # where to redirect after logout
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/(?!login), roles: ROLE_ADMIN }
The LoginAuthenticator class is Symfony's default generated one.
Why the interactive login event is not called?
When using the new(ish) authentication manager, the INTERACTIVE_LOGIN event is replaced with the LoginSuccessEvent.
# my subscriber
public static function getSubscribedEvents()
{
return [
//SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
LoginSuccessEvent::class => 'onLoginSuccess'
];
}
public function onLoginSuccess(LoginSuccessEvent $event)
{
$user = $event->getUser();
$user->setCount($user->getCount() + 1);
$this->em->flush();
//dd($user);
}
I'm not sure if this is explicitly documented yet. Like many upgrade deprecations, the code is very confusing. I tried to trace through what was happening and quickly got lost (once again) in the Security forest.
Events are talked about here.
I discovered this behavior by creating a fresh 5.1 project, running make:auth and adding a listener for both events. But I forgot to add enable_authenticator_manager: true to the security config.
So the INTERACTIVE_LOGIN event was fired. After enabling the new manager, the LoginSuccessEvent was fired. Notice that the new event has some additional helper methods such as getUser. Makes the code a tiny bit cleaner.
Off-topic but I would caution against flushing the entity manager inside of a listener. It can be a bit unpredictable depending on what else is going on. Might consider just getting the database connection and executing a SQL update.
I try to write logs to a specific file using a specific channel in Monolog (called encuestas_cloud) inside a Command in Symfony 3.4 but I'm not able to do it.
I've read Symfony docs and searched in the web and I think it's well configured but i get an error.
The code is:
In config_dev.yml:
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ['!event']
...
encuestas_cloud_logger:
#type: rotating_file
type: stream
path: 'D:/web/xampp/htdocs/temp/logs/encuestas_cloud.log'
level: info
channels: ['encuestas_cloud']
max_files: 10
In services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\Command\EncuestasCloudCommand\:
resource: '../../src/AppBundle/Command/EncuestasCloudCommand.php'
arguments: ['#logger']
public: true
tags:
- { name: monolog.logger, channel: encuestas_cloud }
The command:
// src/AppBundle/Command/EncuestasCloudCommand.php
namespace AppBundle\Command;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
...
class EncuestasCloudCommand extends ContainerAwareCommand
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
parent::__construct();
}
...
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->logger;
$logger->addInfo('My logger is now ready');
When I execute it I get:
In LoggerChannelPass.php line 98:
Monolog configuration error: The logging channel "encuestas_cloud" assigned to the
"encuestas_cloud_logger" handler does not exist.
In ContainerBuilder.php line 1063:
You have requested a non-existent service "monolog.logger.encuestas_cloud".
If I add channels: ['encuestas_cloud'] in config_dev.yml
monolog:
channels: ['encuestas_cloud']
handlers:
main:
type: stream
...
The error dissappear but the log still goes to the general log file: dev.log
Please, could somebody help me to find out what is wrong with the configuration?
Thanks a lot!!!
Does changing the argument for your Command from #logger to #monolog.logger.encuestas_cloud work? That should inject the specific configured logger and therefor your logging would appear in the correct logger.
monolog:
channels: ['encuestas_cloud']
Should be defined AFAIK, and explicitly excluding the channel for your main logger, to not appear there:
monolog:
handlers:
main:
...
channels: [ '!encuestas_cloud' ]
Thanks a lot evertjes for your answer, it didn't solve the problem but helped me to investigate other paths...
The problem was that the command wasn't defined as a service in the right way, so Symfony was unable to match the channel to the service (and to the command).
I executed :
php bin/console debug:container
and the service of the command didn't appear.
So after investigating how to define a command as a service the configuration worked fine... uffff.
Here I post the final code that works.
In config_dev.yml:
monolog:
channels: ['encuestas_cloud']
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ['!event','!encuestas_cloud']
...
encuestas_cloud_logger:
type: rotating_file
path: 'D:/web/xampp/htdocs/temp/logs/encuestas_cloud.log'
level: info
channels: ['encuestas_cloud']
max_files: 10
In services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
console.command.encuestas_cloud_command:
class: 'AppBundle\Command\EncuestasCloudCommand'
arguments: ['#logger']
public: true
tags:
- { name: monolog.logger, channel: encuestas_cloud }
The command:
// src/AppBundle/Command/EncuestasCloudCommand.php
namespace AppBundle\Command;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
...
class EncuestasCloudCommand extends ContainerAwareCommand
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
parent::__construct();
}
...
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->logger;
$logger->addInfo('My logger is now ready');
Now the file is created and the log appears with the configured channel.
Thanks a lot to everybody!!!
I am using Monolog in a Symfony 2.8 project. I have configured a mail handler to send me notification about all errors.
Since not all errors are important (e.g. PageNotFound for /apple-app-site-association) I would like to add a filter. I did this by adding a custom Handler:
Config:
services:
monolog.custom_handler.service:
class: AppBundle\Log\CustomHandler
monolog:
main:
type: service
id: monolog.custom_handler.service
level: error
handler: mail_buffer
mail_buffer:
type: buffer
handler: mailer
mailer:
type: swift_mailer
from_email: "my address"
to_email: "my address"
subject: ERROR!
formatter: monolog.formatter.extended
Handler:
namespace AppBundle\Log;
use Monolog\Handler\AbstractHandler;
class CustomHandler extends AbstractHandler {
public function handle(array $record) {
// Check if handling level was reached
if ($record['level'] < $this->level)
return false;
if (!empty($record)) {
$url = (isset($record['extra']) && isset($record['extra']['url']) ? $record['extra']['url'] : '');
$message = (isset($record['message']) ? $record['message'] : '');
// Well Known Pages
if (strpos($url, '/.well-known') === 0) {
//echo "...match 1</br>";
return true;
}
if ($url == '/apple-app-site-association') {
//echo "...match 2</br>";
return true;
}
if (strpos($url, '/apple-touch-icon') === 0) {
//echo "...match 3</br>";
return true;
}
...
}
// Record was NOT handled --> DO NOT stop bubbeling
return false;
}
}
Using the echo statements I can confirm, that the handler is called, and the if clause correctly matches. So, if /apple-app-site-association is called for example, the handler returns true. I would expect, that this stops bubbeling, and that thus the nested mail_buffer handler is NOT called anymore. This is not the case.
No matter if the handler return true or false, I still receive mail for all error.
What am I doing wrong? How can I stop the processing of filtered messages?
I made a command that automatically writes in routing.yml.
My problem is that when I try to browse one of the routes
api:
resource: "."
type: "api"
prefix: /api
I get this exception
Cannot load resource "."
I tried to add a cache:clear to my command but I get the same exception.
I added a cache warmup that runs after the command termination that way Symfony dumps routes into generated code .
class TerminateListener {
public function onConsoleTerminate(ConsoleTerminateEvent $event) {
if ($event->getCommand()->getName() == ('my:command')) {
$app = new Application();
$cache_clear_command = $event->getCommand()->getApplication()->find('cache:warmup');
$cache_clear_command->setApplication($app);
$event->getOutput()->setVerbosity('VERBOSITY_QUIET');
$cache_clear_command->run($event->getInput(), $event->getOutput());
}
}
}
services:
warmup.listener:
class:TerminateListener
tags:
- { name: kernel.event_listener, event: console.terminate , method: onConsoleTerminate }
I am working on a Symfony 2 web app and I would like to inject a Monolog logger using a specific channel to a service:
The Config:
monolog:
handlers:
main:
type: stream
path: %kernel.root_dir%/%kernel.environment%.log
level: error
#channels: [!alert]
alert:
type: stream
path: %kernel.root_dir%/%kernel.environment%.alert.log
level: info
channels: [alert]
Service Config:
services:
some_service:
class: Some\Service
arguments: [#logger]
tags:
- { name: monolog.logger, channel: alert }
The Service:
class SomeService {
protected $logger;
public function __construct($logger) {
$this->logger = $logger;
$this->logger->info('Log this!');
}
The prod.log file:
[2016-03-28 11:25:47] alert.INFO: Log this!
The Problem: Although I specifically inject the logger using the alert channel, the message is handled by the main handler. Thus the messages are logged into the prod.log file instead of the prod.alert.log file.
When I leave the line channels: [!alert] as comment, the message is logged to prod.log. When I activate this line by removing the comment, the message is not logged at all (main handler ignores the channel correctly).
What have I to to, in order to use a specific handler to target a specific log file, mailer, etc? Messages to alert channel should be handled by the alert handler while all other handlers are ignored.
Use special service created for Monolog handler:
services:
some_service:
class: Namespace\Some\Service
arguments: ["#monolog.logger.alert"]