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.
Related
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,
// [..]
);
Have a distributed SF3.3 application running on multiple AWS EC2 instances with a central ElastiCache (redis) cluster.
Each EC2 instance also runs a local Redis instance which is used for Doctrine meta and query caching.
This application utilises Doctrines Second Level Cache, which works very well from a functional point of view. But performance is poor (900-1200ms page loads) on AWS due to the 400+ cache calls it makes to load in our Country and VatRate entities required on many of our pages.
As these Country and VatRate entities change rarely I'd like to utilise both the local Redis instance and ElastiCache for result caching by using different regions defined in the second level cache. This should reduce the latency problem with the 400+ cache calls as when running on a single box page loads are sub 100ms. Reading the documentation this all seems to be possible, just not entirely sure how to configure it with Symfony and PHP-Cache.
An example of the current configuration:
app/config/config.yml
doctrine:
dbal:
# .. params
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
auto_mapping: true
second_level_cache:
enabled: true
region_cache_driver:
type: service
id: doctrine.orm.default_result_cache
cache_adapter:
providers:
meta: # Used for version specific
factory: 'cache.factory.redis'
options:
host: 'localhost'
port: '%redis_local.port%'
pool_namespace: "meta_%hash%"
result: # Used for result data
factory: 'cache.factory.redis'
options:
host: '%redis_result.host%'
port: '%redis_result.port%'
pool_namespace: result
cache:
doctrine:
enabled: true
use_tagging: true
metadata:
service_id: 'cache.provider.meta'
entity_managers: [ default ]
query:
service_id: 'cache.provider.meta'
entity_managers: [ default ]
result:
service_id: 'cache.provider.result'
entity_managers: [ default ]
src/AppBundle/Entity/Country.php
/**
* #ORM\Table(name = "countries")
* #ORM\Cache(usage = "READ_ONLY")
*/
class Country
{
// ...
/**
* #var VatRate
*
* #ORM\OneToMany(targetEntity = "VatRate", mappedBy = "country")
* #ORM\Cache("NONSTRICT_READ_WRITE")
*/
private $vatRates;
// ...
}
src/AppBundle/Entity/VatRate.php
/**
* #ORM\Table(name = "vatRates")
* #ORM\Cache(usage = "READ_ONLY")
*/
class VatRate
{
// ...
/**
* #var Country
*
* #ORM\ManyToOne(targetEntity = "Country", inversedBy = "vatRates")
* #ORM\JoinColumn(name = "countryId", referencedColumnName = "countryId")
*/
private $country;
// ...
}
src/AppBundle/Entity/Order.php
/**
* #ORM\Table(name = "orders")
* #ORM\Cache(usage = "NONSTRICT_READ_WRITE")
*/
class Order
{
// ...
/**
* #var Country
*
* #ORM\ManyToOne(targetEntity = "Country")
* #ORM\JoinColumn(name = "countryId", referencedColumnName = "countryId")
*/
private $country;
// ...
}
Attempted Configuration
app/config/config.yml
doctrine:
dbal:
# .. params
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
auto_mapping: true
second_level_cache:
enabled: true
region_cache_driver: array
regions:
local:
type: service
service: "doctrine.orm.default_result_cache" # TODO: needs to be local redis
remote:
type: service
service: "doctrine.orm.default_result_cache" # TODO: needs to be remote redis
cache_adapter:
providers:
meta: # Used for version specific
factory: 'cache.factory.redis'
options:
host: 'localhost'
port: '%redis_local.port%'
pool_namespace: "meta_%hash%"
result: # Used for result data
factory: 'cache.factory.redis'
options:
host: '%redis_result.host%'
port: '%redis_result.port%'
pool_namespace: result
cache:
doctrine:
enabled: true
use_tagging: true
metadata:
service_id: 'cache.provider.meta'
entity_managers: [ default ]
query:
service_id: 'cache.provider.meta'
entity_managers: [ default ]
result:
service_id: 'cache.provider.result'
entity_managers: [ default ]
src/AppBundle/Entity/Country.php
/**
* #ORM\Table(name = "countries")
* #ORM\Cache(usage = "READ_ONLY", region = "local")
*/
class Country
{
// as above
}
src/AppBundle/Entity/VatRate.php
/**
* #ORM\Table(name = "vatRates")
* #ORM\Cache(usage = "READ_ONLY", region = "local")
*/
class VatRate
{
// as above
}
src/AppBundle/Entity/Order.php
/**
* #ORM\Table(name = "orders")
* #ORM\Cache(usage = "NONSTRICT_READ_WRITE", region = "remote")
*/
class Order
{
// as above
}
Which results in
Type error: Argument 1 passed to Doctrine\ORM\Cache\DefaultCacheFactory::setRegion() must be an instance of Doctrine\ORM\Cache\Region, instance of Cache\Bridge\Doctrine\DoctrineCacheBridge given,
Not too sure where to go from here, been working from the tests here: https://github.com/doctrine/DoctrineBundle/blob/74b408d0b6b06b9758a4d29116d42f5bfd83daf0/Tests/DependencyInjection/Fixtures/config/yml/orm_second_level_cache.yml but the lack of documentation for configuring this makes it a little more challenging!
After much playing around with the PHP-Cache library, it's clear from looking in the CacheBundle compiler that it will only ever support one DoctrineBridge instance from the configuration. https://github.com/php-cache/cache-bundle/blob/master/src/DependencyInjection/Compiler/DoctrineCompilerPass.php
Solution was to create my own compiler, not pretty but it seems to work.
src/AppBundle/DependencyInjection/Compiler/DoctrineCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Cache\Bridge\Doctrine\DoctrineCacheBridge;
use Cache\CacheBundle\Factory\DoctrineBridgeFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class DoctrineCompilerPass implements CompilerPassInterface
{
/** #var ContainerBuilder */
private $container;
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->enableDoctrineCache('local');
$this->enableDoctrineCache('remote');
}
private function enableDoctrineCache(string $configName)
{
$typeConfig = [
'entity_managers' => [
'default'
],
'use_tagging' => true,
'service_id' => 'cache.provider.' . $configName
];
$bridgeServiceId = sprintf('cache.service.doctrine.%s.entity_managers.bridge', $configName);
$this->container->register($bridgeServiceId, DoctrineCacheBridge::class)
->setFactory([DoctrineBridgeFactory::class, 'get'])
->addArgument(new Reference($typeConfig['service_id']))
->addArgument($typeConfig)
->addArgument(['doctrine', $configName]);
}
}
src/AppBundle/AppBundle.php
use AppBundle\DependencyInjection\Compiler\DoctrineCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new DoctrineCompilerPass());
}
}
app/config/config.yml
doctrine:
dbal:
# ... params
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
auto_mapping: true
second_level_cache:
enabled: true
regions:
remote:
cache_driver:
type: service
id: cache.service.doctrine.remote.entity_managers.bridge
local:
cache_driver:
type: service
id: cache.service.doctrine.local.entity_managers.bridge
cache_adapter:
providers:
local:
factory: 'cache.factory.redis'
options:
host: '%redis_local.host%'
port: '%redis_local.port%'
pool_namespace: "local_%hash%"
remote:
factory: 'cache.factory.redis'
options:
host: '%redis_result.host%'
port: '%redis_result.port%'
pool_namespace: 'result'
cache:
doctrine:
enabled: true
use_tagging: true
metadata:
service_id: 'cache.provider.local'
entity_managers: [ default ]
query:
service_id: 'cache.provider.local'
entity_managers: [ default ]
While this seems to work to some extent, there's some inconsistencies local cache calls resulting in 500 errors when theres probably something missing in the cache. Overall think I'm trying to bend the second level cache more than it was designed to.
The error message you are getting entirely reflects the root of your issue. You are passing DoctrineCacheBridge instances (the underlying class of doctrine.orm.default_result_cache) when instances of the Doctrine\ORM\Cache\Region interface expected:
second_level_cache:
#...
regions:
local:
type: service
service: "region_service_not_cache_service" # Here is a Region instance expected
remote:
type: service
service: "region_service_not_cache_service" #Here is a Region instance expected
In your former configuration the doctrine.orm.default_result_cache cache service is set as the default cache through the region_cache_driver setting. \Doctrine\ORM\Cache\DefaultCacheFactory generates instances of DefaultRegion on flight (as none was preconfigured) and feeds the default cache to them.
The latter configuration is expected to have pre-configured regions and could be fixed several ways. I suggest the next:
dbal:
# .. params
orm:
#...
second_level_cache:
#...
regions:
local:
type: default
cache_driver:
type: service
id: "doctrine.orm.default_query_cache" # NOTE that this is the service id of your local cache generated by PHP-Cache Bundle
remote:
type: default
cache_driver:
type: service
id: "doctrine.orm.default_result_cache" # NOTE that this is the service id of your remote cache generated by PHP-Cache Bundle
Here you tell Doctrine to create 2 DefaultRegion regions under the local and remote keys and pass local_cache and remote_cache to them correspondingly.
And it's better to return region_cache_driver to the former value otherwise DefaultRegions generated on flight will use array cache:
second_level_cache:
enabled: true
region_cache_driver:
type: service
id: doctrine.orm.default_result_cache
I am trying to create a manual provider to manually populate my FOS Elastica index to account for some complex joins. At the moment, I am just trying to get the provider to work even without the joins, and I am having trouble injecting the correct Elastica Type into my constructor for the provider. Here is the constructor of my provider:
// ...
class EmpPosDeptProvider implements ProviderInterface
{
private $em;
protected $type;
public function __construct(Type $type, EntityManager $em)
{
$this->type = $type;
$this->em = $em->getRepository('CcitEmployeesBundle:Position');
}
// ...
and here is my services.yml file:
services:
employees.search_provider.empPosDept:
class: Ccit\EmployeesBundle\Search\EmpPosDeptProvider
tags:
- { name: fos_elastica.provider, index: employees, type: empPosDept }
arguments:
- %fos_elastica.type.class%
- #doctrine.orm.entity_manager
When I try to execute php app/console fos:elastica:populate I am receiving the following error:
PHP Catchable fatal error: Argument 1 passed to Ccit\EmployeesBundle\Search
\EmpPosDeptProvider::__construct() must be an instance of Elastica\Type, string given,
called in /vagrant-nfs/employees/app/cache/dev/appDevDebugProjectContainer.php on line 736
and defined in /vagrant-nfs/employees/src/Ccit/EmployeesBundle/Search
/EmpPosDeptProvider.php on line 23
Does anyone know what I need to give as a correct argument in my services.yml file? Or could the problem be something else entirely?
You're passing a string containing Ccit\EmployeesBundle\Search\EmpPosDeptProvider. You have to pass an instance of EmpPosDeptProvider, and it may be declared in your services.yml something like:
services:
fos_elastica.type:
class: %fos_elastica.type.class%
employees.search_provider.empPosDept:
class: Ccit\EmployeesBundle\Search\EmpPosDeptProvider
tags:
- { name: fos_elastica.provider, index: employees, type: empPosDept }
arguments:
- #fos_elastica.type
- #doctrine.orm.entity_manager
Apparently I needed to provide the explicit path to the type I was referencing. The following line worked:
#fos_elastica.index.employees.employeePositionDepartment
This makes sense given that my config.yml file contains the following:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
employees:
client: default
types:
employeePositionDepartment:
mappings:
id: { type: integer }
title: { type: string }
startDate: { type: date, format: date_time_no_millis }
endDate: { type: date, format: date_time_no_millis }
supervisor: { type: integer }
Thanks to anyone who was considering helping me with this rather elementary question.
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.
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.