I have used the following configuration for my production logging:
monolog:
handlers:
mail:
type: fingers_crossed
action_level: error
handler: grouped
grouped:
type: group
members: [streamed, buffered]
streamed:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: debug
# buffered is used to accumulate errors and send them as batch to the email address
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: info#....com
to_email: info#....com
subject: Error Occurred!
level: debug
This sends emails like this:
[2012-03-21 21:24:09] security.DEBUG: Read SecurityContext from the
session [] []
[2012-03-21 21:24:09] security.DEBUG: Reloading user from user
provider. [] []
[2012-03-21 21:24:09] security.DEBUG: Username "jakob.asdf" was
reloaded from user provider. [] [] [2012-03-21 21:24:09]
request.INFO: Matched route "_user_settings" (parameters:
"_controller": "...Bundle\Controller\UserController::settingsAction",
"username": "Jakob.asdf", "_route": "_user_settings") [] []
[2012-03-21 21:24:09] request.ERROR:
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
...Bundle\Entity\User object not found. (uncaught exception) at
/var/www/.../vendor/bundles/Sensio/Bundle/FrameworkExtraBundle/Request/ParamConverter/DoctrineParamConverter.php
line 50 [] []
[2012-03-21 21:24:09] security.DEBUG: Write SecurityContext in the
session [] []
I would really love to have a stack trace here, or at least the line number in my controller which triggered the error. Otherwise it's really a lot of guessing of what could have gone wrong.
Now, the question: Is there any way to achieve such an even more verbose logging?
Yes it can be achievable.
Create a ExceptionListener class.
//namespace declarations
class ExceptionListener{
/**
* #var \Symfony\Component\HttpKernel\Log\LoggerInterface
*/
private $logger =null;
/**
* #param null|\Symfony\Component\HttpKernel\Log\LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* #param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
if($this->logger === null)
return;
$exception = $event->getException();
$flattenException = FlattenException::create($exception);
$this->logger->err('Stack trace');
foreach ($flattenException->getTrace() as $trace) {
$traceMessage = sprintf(' at %s line %s', $trace['file'], $trace['line']);
$this->logger->err($traceMessage);
}
}
}
And then register listener.
kernel.listener.your_listener_name:
class: FQCN\Of\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException , priority: -1}
- { name: monolog.logger, channel: mychannel }
arguments:
- "#logger"
You can tweak it as your requirement.
I like the solution from the Symfony docs. All you have to do is add the following code to your services.yml file:
services:
my_service:
class: Monolog\Processor\IntrospectionProcessor
tags:
- { name: monolog.processor }
This uses the IntrospectionProcessor, a tested processor to add more information to your log. It pulls out the information you care about too probably.
Related
I would like to make a configuration for my symfony logs in dev environment that allows to log all messages except 404 errors.
I tested the following configuration:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404]
buffer_size: 50
nested:
type: rotating_file
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
max_files: 30
With this configuration, 404 messages don't appear in the logs but the problem is that messages like "event.DEBUG" or "request.INFO" don't appear either and I would like them to appear because I am in a dev environment.
So I thought that the "action_level" was too high so I modified it:
monolog:
handlers:
main:
type: fingers_crossed
action_level: debug <========== I replaced error with debug
handler: nested
excluded_http_codes: [404]
buffer_size: 50
nested:
type: rotating_file
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
max_files: 30
Now in the logs, I can see "event.DEBUG" or "request.INFO" messages but I also see 404 errors...
It's like the excluded_http_codes is not working.
My question is how can I do to not show 404 errors in the logs while keeping messages like "event.DEBUG" or "request.INFO"?
Thank you (and apologies for my English)
You can create a custom ErrorListener which is responsible for HTTP error logging and overrides the default ErrorListener. In the code below, the dev environment is determined by $this->debug property which contains APP_DEBUG env variable.
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ErrorListener as BaseErrorListener;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ErrorListener extends BaseErrorListener
{
protected function logException(\Throwable $exception, string $message, string $logLevel = null): void
{
if (null !== $this->logger) {
if (null !== $logLevel) {
$this->logger->log($logLevel, $message, ['exception' => $exception]);
} elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, ['exception' => $exception]);
} else {
// beginning of changes
if ($exception->getStatusCode() !== 404 || !$this->debug) {
$this->logger->error($message, ['exception' => $exception]);
}
// ending of changes
}
}
}
}
And register this listener
namespace App\DependencyInjection\Compiler;
use App\EventListener\ErrorListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
class ErrorListenerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
// override `exception_listener`
$exceptionListenerDefinition = $container->getDefinition('exception_listener');
$exceptionListenerDefinition->setClass(ErrorListener::class);
}
}
I'm working on a symfony project that need to be refactorized.
I'm trying to call a environnement variable declared in servces.yml
When i'm running php bin/console debug:container --parameters I can see the variables.
I tried $this->getParameters('param_name');
When I'm dumping them in a controller it's working fine but when i'm trying to access them from a service it is failing -> Method 'getParameter' not found in 'TwitterService'
I thought that this method was native in symfony.
Here is my twitter service
```namespace App\Services;
use Abraham\TwitterOAuth\TwitterOAuth;
/**
* Class TwitterService.
*/
class TwitterService
{
/** #const int NUMBER_OF_TWEETS */
private const NUMBER_OF_TWEETS = 2;
/**
* #param string $twitterID
*
* #return array|object
*/
public function getTwitterFeed($twitterID = 'RISKandMe')
{
// #todo move config to .env
$consumerKey = $this->getParameter('consumer_key');
$consumerSecret = $this->getParameter('consumer_secret');
$accessToken = $this->getParameter('access_token');
$accessTokenSecret = $this->getParameter('access_token');
// Authenticate with twitter
$twitterConnection = new TwitterOAuth(
$consumerKey,
$consumerSecret,
$accessToken,
$accessTokenSecret
);
// Get the user timeline feeds
return $twitterConnection->get(
'statuses/user_timeline',
[
'screen_name' => $twitterID,
'count' => self::NUMBER_OF_TWEETS,
'exclude_replies' => false,
]
);
}
}
Here is my services.yml:
parameters:
locale: 'fr'
api_key_validity: '%env(int:APIKEYVALIDITY)%'
activation_delay: '%env(int:ACTIVATIONDELAY)%'
gophish_api_key: '%env(GOPHISHAPIKEY)%'
gophish_server: '%env(GOPHISHSERVER)%'
gophish_white_list: '%env(GOPHISHWHITELIST)%'
mailjet_public: '%env(MAILJET_PUBLIC_KEY)%'
mailjet_secret: '%env(MAILJET_PRIVATE_KEY)%'
sender_address: '%env(SENDER_ADDRESS)%'
sender_name: '%env(SENDER_NAME)%'
consumer_key: '%env(CONSUMER_KEY)%'
consumer_secret: '%env(CONSUMER_SECRET)%'
access_token: '%env(ACCESS_TOKEN)%'
access_token_secret: '%env(ACCESS_TOKEN_SECRET)%'
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# user_listener:
# class: App\EntityListener\UserListener
# arguments: ['#logger']
# tags:
# - { name: doctrine.orm.entity_listener,entity: App\Entity\User}
#
form_authenticator:
class: App\Security\FormAuthenticator
arguments: ["#logger","#router","#security.password_encoder","#doctrine.orm.default_entity_manager",CsrfTokenManagerInterface]
token_authenticator:
class: App\Security\TokenAuthenticator
arguments: ["#logger","#router","#security.password_encoder","#doctrine.orm.default_entity_manager",CsrfTokenManagerInterface,ParameterBagInterface]
DbUserProvider:
class: App\Security\DbUserProvider
FickleUserProvider:
class: App\Security\FickleUserProvider
BackEndUserProvider:
class: App\Security\BackEndUserProvider
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Controller\Services\Helper:
arguments: ['#logger','#router','#security.password_encoder','#mailer','#session','#doctrine.orm.default_entity_manager']
tags: ['controller.service_arguments']
App\Controller\Services\GoPhish:
arguments: []
tags: []
App\Controller\:
resource: '../src/Controller/DashboardController.php'
arguments: ["#logger","#router","#security.password_encoder",'#doctrine.orm.default_entity_manager',App\Controller\Services\Helper]
tags: ['controller.service_arguments']
twig.extension.intl:
class: Twig_Extensions_Extension_Intl
tags:
- { name: twig.extension }
# session.listener:
# class: App\Listeners\SessionListener
# tags:
# - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
# user.activity_listener:
# class: App\Listeners\ActivityListener
# tags:
# - { name: kernel.event_listener, event: kernel.controller, method: onCoreController }
Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
arguments:
- '%env(string:DBCONNEXIONPARAMETERS)%'
- { db_table: 'ht_session', db_username: '%env(string:DBUSER)%', db_password: '%env(string:DBPASSWORD)%'}
app.services.twitter:
class: App\Services\TwitterService
If someone has any idea they would be more than appreciated.
Thank you very much for you attention.
The $this->getParameter() helper function is only available in controllers extending from the class Symfony\Bundle\FrameworkBundle\Controller\AbstractController;.
In order to access a container-parameter in your service you need to inject or autowire it into your service.
# services.yml
services:
_defaults:
bind:
# schema -> [constructor-variable name]: [parameter name]
$consumerSecret: '%consumer_secret%'
$consumerKey: '%consumer_key%'
# [..]
Then use them as follows:
class TwitterService
{
private $csonsumerSecret;
private $consumerKey;
public function __construct(
string $consumerKey,
string $consumerSecret,
// [..]
) {
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
// [..]
}
public function getTwitterFeed($twitterID = 'RISKandMe')
{
$twitterConnection = new TwitterOAuth(
$this->consumerKey,
$this->consumerSecret,
// [..]
);
How to acknowledge the message and produce a new message at the same time?
When I start my consumer from the command line, the messages will stay in the original queue. But new ones will be created in the new queue, in an infinite loop. Because it keeps consuming the messages that are not being acknowledged.
Even though TRUE is returned in the execute() function of the consumer. Which should acknowledge it, like it says in the documentation.
I am producing messages from a callback inside a consumer. This producer is injected using the standard Symfony DI.
If I remove the method that publishes the new message, the messages are acknowledged just fine...
services.yml
services:
my_importlog_repository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [AppBundle\Entity\MyImportlogEntity]
my_distributor:
class: AppBundle\DistributorImport\MyDistributor
arguments: [#my_importlog_repository,#logger,#old_sound_rabbit_mq.my_download_producer, %my_config%]
my_download:
class: AppBundle\Consumer\MyDownloadConsumer
arguments: [#logger,#old_sound_rabbit_mq.my_extract_producer,#my_distributor,%my_config%]
my_extract:
class: AppBundle\Consumer\MyExtractConsumer
arguments: [#logger,#old_sound_rabbit_mq.my_convert_producer,#my_distributor,%my_config%]
config.yml
# rabbitmq
old_sound_rabbit_mq:
connections:
default:
host: '192.168.99.100'
port: 5672
user: 'guest'
password: 'guest'
vhost: '/'
lazy: false
connection_timeout: 60
read_write_timeout: 60
# requires php-amqplib v2.4.1+ and PHP5.4+
keepalive: false
# requires php-amqplib v2.4.1+
heartbeat: 30
producers:
# my producers
my_download:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_download'}
my_extract:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_extract'}
my_convert:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_convert'}
consumers:
# my consumers
my_download:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_download'}
callback: my_download
qos_options: {prefetch_size: 0, prefetch_count: 1, global: false}
idle_timeout: 60
my_extract:
connection: default
exchange_options: {name: 'distributor_import', type: direct}
queue_options: {name: 'my_extract'}
callback: my_extract
qos_options: {prefetch_size: 0, prefetch_count: 1, global: false}
idle_timeout: 60
MyDownloadConsumer.php
<?php
namespace AppBundle\Consumer;
use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface;
class MyDownloadConsumer implements ConsumerInterface
{
private $logger;
private $producer;
private $distributor;
private $config;
public function __construct(\Symfony\Component\HttpKernel\Log\LoggerInterface $logger, \OldSound\RabbitMqBundle\RabbitMq\Producer $producer, \AppBundle\DistributorImport\MyDistributor $distributor, Array $config)
{
$this->logger = $logger;
$this->producer = $producer;
$this->distributor = $distributor;
$this->config = $config;
}
public function execute(\PhpAmqpLib\Message\AMQPMessage $message)
{
$data = unserialize($message->body);
$this->producer->publish(serialize($data));
return true;
}
}
If I remove
$data = unserialize($message->body);
$this->producer->publish(serialize($data));
It works like it should...
Was able to publish a message from inside my Consumer execute() method, while also acknowledging the current message being consumed. Using the following code.
$message->delivery_info['channel']
->basic_publish(
new AMQPMessage (serialize($data)),
'name_of_my_exchange',
'key.of.my.routing'
);
Publishing directly on the channel of the message that is being consumed.
I have problem with configure blueimp. When I send file (click: start upload) in jsconsole is:
POST XXXapp_dev.php/_uploader/gallery/upload 500 (Internal Server
Error)
I used:
Simple example of the entity file to be uploaded with OneupUploaderBundle
https://github.com/1up-lab/OneupUploaderBundle/issues/51#issuecomment-24878715
My file:
services.yml:
Cms.upload_listener:
class: Cms\AdminBundle\EventListener\UploadListener
arguments: [#doctrine]
tags:
- { name: kernel.event_listener, event: oneup_uploader.post_upload, method: onUpload }
config.yml:
oneup_uploader:
mappings:
gallery:
frontend: blueimp
storage:
service: ~
type: filesystem
filesystem: ~
directory: web/uploads/gallery
stream_wrapper: ~
sync_buffer_size: 100K
routing.yml
oneup_uploader:
resource: .
type: uploader
~Entity/File.php
/* ... */
/**
* #ORM\Entity
* #ORM\Table(name="file")
*/
class file {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $filename;
}
~/EventListner/UploadListner.php
class UploadListener
{
protected $manager;
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
public function onUpload(PostPersistEvent $event)
{
$file = $event->getFile();
$object = new file();
$object->setFilename($file->getPathName());
$this->manager->persist($object);
$this->manager->flush();
}
}
debug route:
_uploader_upload_gallery POST ANY ANY
/_uploader/gallery/upload
Directory /web/uploads/gallery is on the server.
What am I trying to achieve? I need upload files to server and add record to db.
I think that config is incorrect, but where?
Edit 1:
Maybe help
INFO - An exception was thrown while getting the uncalled listeners
(Catchable Fatal Error: Argument 1 passed to
Cms\AdminBundle\EventListener\UploadListener::__construct() must be an
instance of Cms\AdminBundle\EventListener\EntityManager, instance of
Doctrine\Bundle\DoctrineBundle\Registry given, called in
/home/cms/public_html/app/cache/dev/appDevDebugProjectContainer.php on
line 455 and defined) Context:
{"exception":"Object(Symfony\Component\Debug\Exception\ContextErrorException)"}
CRITICAL - Uncaught PHP Exception
Symfony\Component\HttpFoundation\File\Exception\FileException: "Unable
to create the "/uploads/gallery" directory" at
/home/cms/public_html/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/File/File.php
line 134 Context:
{"exception":"Object(Symfony\Component\HttpFoundation\File\Exception\FileException)"}
I created uploads/gallery! chmod 777. I don't understand.
Edit 2:
Cms.upload_listener:
class: Cms\AdminBundle\EventListener\UploadListener
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_listener, event: oneup_uploader.post_upload, method: onUpload }
and:
directory: ./uploads/gallery
Now created, uploaded file in "uploads/gallery/..."
But...
CRITICAL - Uncaught PHP Exception
Symfony\Component\Debug\Exception\ContextErrorException: "Catchable
Fatal Error: Argument 1 passed to
Cms\AdminBundle\EventListener\UploadListener::onUpload() must be an
instance of Oneup\UploaderBundle\Event\PostPersistEvent, instance of
Oneup\UploaderBundle\Event\PostUploadEvent given" at
/home/cms/public_html/src/Cms/AdminBundle/EventListener/UploadListener.php
line 17 Context:
{"exception":"Object(Symfony\Component\Debug\Exception\ContextErrorException)"}
Returns blueimp "Internal Server Error ..." and doesn't add records in the Entity.
In an attempt to combine Monolog e-mailing errors with a custom exception handler I get the following:
[Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]
Circular reference detected for service "router", path: "router ->
monolog.logger.router -> monolog.handler.grouped ->
mana.exception.listener -> templating -> twig ->
templating.helper.logout_url".
I make no claims to really know what I'm doing here, as evidenced by all the errors.
Services excerpt:
mana.exception.listener:
class: Mana\ClientBundle\EventListener\ExceptionListener
arguments: [#templating, #kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Config excerpt:
monolog:
handlers:
main:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: debug
handler: grouped
grouped:
type: group
members: [mail, custom]
mail:
type: fingers_crossed
action_level: error
handler: buffered
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: error#projectmana.org
to_email: truckeetrout#yahoo.com
subject: An Error Occurred!
level: debug
custom:
type: service
id: mana.exception.listener
Custom handler:
class ExceptionListener {
protected $templating;
protected $kernel;
public function __construct(EngineInterface $templating, $kernel) {
$this->templating = $templating;
$this->kernel = $kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event) {
// provide the better way to display a enhanced error page only in prod environment, if you want
if ('prod' == $this->kernel->getEnvironment()) {
// exception object
$exception = $event->getException();
// new Response object
$response = new Response();
// set response content
$response->setContent(
// create you custom template AcmeFooBundle:Exception:exception.html.twig
$this->templating->render(
'ManaClientBundle:Exception:exception.html.twig', array('exception' => $exception)
)
);
// HttpExceptionInterface is a special type of exception
// that holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(500);
}
// set the new $response object to the $event
$event->setResponse($response);
}
}
It's your Exception Listener. Delete #kernel from the arguments array and you're fine
mana.exception.listener:
class: Mana\ClientBundle\EventListener\ExceptionListener
arguments: [#templating]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Your custom exception listener probably depends on monolog either in the templating or kernel service.