I'm using Laravel 5.1 to create a console based application. During development I would like to display the exception trace when an error occurs. However, even if I use -v -vv or -vvv option in php artisan, I don't get an exception trace for my custom commands. I set APP_DEBUG=true in my .env, still no exception trace.
Output of php artisan some:unknowncommand is:
[InvalidArgumentException]
There are no commands defined in the "some" namespace.
Output of php artisan -v some:unknowncommand is:
[InvalidArgumentException]
There are no commands defined in the "some" namespace.
Exception trace:
() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:501
Symfony\Component\Console\Application->findNamespace() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:535
Symfony\Component\Console\Application->find() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:192
Symfony\Component\Console\Application->doRun() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:126
...
Now, I created a very simple console command called dp:test, with following handle function:
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
generate error here
}
Output of php artisan dp:test is:
[Symfony\Component\Debug\Exception\FatalErrorException]
syntax error, unexpected 'error' (T_STRING)
Output of php artisan -v dp:test is the same.
Output of php artisan -vvv dp:test is the same.
The log file DOES show the exception trace, so somehow it should be possible to display it in cli. I don't even see the filename and linenumer where the error occurs... How can I take care of this?
Thanks in advance!
EDIT:
Is digged a bit further. In case I use this in my Command:
public function handle()
{
throw new \Exception("test exception");
}
and I issue the command php artisan -v dp:test, the error trace IS printed. The trace is only not printed when the exception is thrown due to a PHP error. In Illuminate/Foundation/Bootstrap/HandleExceptions.php method bootstrap PHP errors are converted to Exceptions. When this occurs, an exception is thrown, but the -v is somehow ignored when printing. This is very inconvenient because it makes debugging CLI apps hard.
I think the solution can be found in vendor/symfony/console/Application.php, method renderException.
I'm going to dig further later, unless someone else can point the solution faster than me :-)
I found reason why -v is ignored:
in Illuminate/Foundation/Bootstrap/HandleExceptions.php, the renderForConsole method instantiates a ConsoleOutput object, with default settings, not taking into account the verbosity settings the user asked for:
protected function renderForConsole($e)
{
$this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}
For this reason, whatever -v -vv or -vvv is set, the $output->getVerbosity() in vendor/symfony/console/Application.php is always lower than OutputInterface::VERBOSITY_VERBOSE, for that reason, the stack trace is not printed.
I probably start an issue on github for this, because I think it's much more convenient if errors are shown in CLI if user sets -v.
You can set the verbosity to whatever level you'd like by adding the following use statement:
use Symfony\Component\Console\Output\OutputInterface;
And then adding this to the top of your handle function:
$this->output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
See the documentation for symfony console here http://symfony.com/doc/current/console/verbosity.html
for more information.
If you look at Illuminate\Console\Command class you will see the definition of the function that writes the error string to the console:
/**
* Write a string as error output.
*
* #param string $string
* #return void
*/
public function error($string)
{
$this->output->writeln("<error>$string</error>");
}
Now that you see it only writes the error string, you can play just a little bit with it to achieve what you want. You can get the function call back trace and output as many lines and files backwards as you wish. Just create class that extends command, override the function and make all of your commands to inherit that command class:
/**
* Write a string as error output.
*
* #param string $string
* #return void
*/
public function error($string)
{
$this->output->writeln("<error>$string</error>");
$trace = debug_backtrace();
foreach ($trace as $t)
{
$this->output->writeln("Trace : " . $t['file'] . " on line " . $t['line'] . " function " . $t['function']);
}
}
Related
I have a strange problem in a symfony 5.3.10 app and i can't wrap my head around it.
If I trigger a 500 error, like so:
/**
* #Route("/", name="pvr_index", methods={"GET"})
*/
public function index(): Response
{
echo $a;
Where $a is not defined, in dev environment I get a nice error page.
I'd expect that in prod I would get a not so nice error page that just tells me that i have a 500 error. And this would happen correctly until some time ago, when something changed, but I don't know what.
Now instead I get no error, not even in the symfony server console, and the script execution would keep on going like nothing happened.
What I have tried:
Adding the supposed exception inside a try/catch:
try {
echo $a;
}
catch (\Exception $e)
{
dump($e);
die();
}
Nothing happens, the catch isn't triggered.
Creating an exception listener:
In services.yaml i have:
exception_listener:
class: App\Event\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception }
And in ExceptionListener.php i have:
class ExceptionListener
{
public function __construct(Environment $engine) {
$this->engine = $engine;
}
public function onKernelException(ExceptionEvent $event)
{
dd($event->getThrowable()->getMessage());
}
}
Still nothing happens, the listener isn't triggered.
Everything works as expected in dev environment, the problem presents itself only in prod.
Looking at a phpinfo(); page I see that the error reporting is enabled, so I think that the problem lies in the symfony configuration.
I'm using php 7.4 and have no real access to the php.ini file
This is not a Symfony error (nor a PHP error for that matter).
On PHP 7.4, trying to output an undefined variable would only raise a notice, not a fatal error (code 500). Notices are silently swallowed up by the engine unless you configure your server to log them or output them, where you'd get something like:
Notice: Undefined variable: a in file xxx.php line Y
A try/catch won't do anything for you, since no exception is thrown for you to catch.
Even on PHP 8 or 8.1, this will only raise a warning like:
Warning: Undefined variable $a
You can see the result under version < 8 and >= 8 here.
I've updated my crontab using crontab -e to run the Laravel task scheduler every minute. It wasn't working so I set it to add its output to a log file, like so:
* * * * * php /path/to/project/artisan schedule:run >> cron_output.log
Now the log contains the following message:
Parse error: parse error in /path/to/project/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php on line 233
This is the method it points to (I've commented next to line 233):
function cache()
{
$arguments = func_get_args();
if (empty($arguments)) {
return app('cache');
}
if (is_string($arguments[0])) {
return app('cache')->get($arguments[0], $arguments[1] ?? null); //line 233
}
if (! is_array($arguments[0])) {
throw new Exception(
'When setting a value in the cache, you must pass an array of key / value pairs.'
);
}
if (! isset($arguments[1])) {
throw new Exception(
'You must specify an expiration time when setting a value in the cache.'
);
}
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1]);
}
I don't understand this issue, as Laravel runs normally otherwise. PHP version is 7.2 (based on php -v and also phpinfo()) so that shouldn't be a problem. Is it possible that somehow the crontab is using an older version of PHP? If so how do I fix this? Any other ideas?
I have given myself a challenge to making use of testing my code as I develop it as I figured this is not only a good practice especially for a back end developer but also helps one a lot in building a scalable and extendable code in the long run.
I have a class that defines an array of supported doctrine drivers. Inside this class there is a method that creates an instance of any annotation driver that is supported by my bundle.
protected $supportedAnnotationDrivers = array(
'Doctrine\ORM\Mapping\Driver\XmlDriver',
'Doctrine\ORM\Mapping\Driver\YamlDriver',
'Doctrine\ORM\Mapping\Driver\AnnotationDriver'
);
The method that creates any instance of these drivers can be seen below:
public function getAnnotationDriver($classDriver, $entityDriver)
{
if(!in_array($classDriver, $this->supportedAnnotationDrivers)){
throw new \RuntimeException(sprintf(
"%s is not a supported Annotation Driver.", $classDriver
));
}
if('Doctrine\ORM\Mapping\Driver\AnnotationDriver' === $classDriver){
$annotationDriver = new $classDriver(new AnnotationReader(), array($entityDriver));
AnnotationRegistry::registerLoader('class_exists');
}else{
$annotationDriver = new $classDriver($entityDriver);
}
return $annotationDriver;
}
Before I go any further I have to say I have consulted PHPUnit Manual and StackOverflow, here; here; here and there , for some insights but I am getting confused all the time. and none of these links are providing me with certainty that I am going about this the right way.
Below is my test function inside my test class:
public function testUnsupportedAnnotationDriverException()
{
try{
$entityManagerService = new EntityManagerService();
//Client requests a unsupported driver
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}catch(\RuntimeException $actualUnsupportedException){
return;
}
$this->fail('An expected exception has not been raised!.');
}
I am really confused when it comes to this part. In my test I am using an exception that is raised by my code which is RuntimeException and my test passes. When I change client code to throw just Exception but leave my test as is it fails and throws the exception from client code. I am happy to know that my test fails because the exception thrown in my test gives me what I would hope to get when a user passes an unsupported driver.
However, I am lacking contextualization herein of the annotation usage and the $this->fail("text/for/failer/) function when testing exceptions.
How best could I go about writing a test for this method? Is what I have done exactly how the test should behave perhaps its me not having grasp the concept behind this part of PHPUnit? Below is an output I get from PHPUnit:
Case 1:Exceptions are the same from test and client code
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
$ vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.
Configuration read from D:\web\doctrine-bundle\phpunit.xml
......
Time: 1.49 seconds, Memory: 3.00Mb
OK (6 tests, 9 assertions)
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
Case 2:Exceptions are not the same from test and client code
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
$ vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.
Configuration read from D:\web\doctrine-bundle\phpunit.xml
..E...
Time: 1.59 seconds, Memory: 3.00Mb
There was 1 error:
1) PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest::testUnsupportedAnnotationDriverException
Exception: Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver.
D:\web\doctrine-bundle\lib\DoctrineBundle\ApplicationContext\Service\EntityManagerService.php:33
D:\web\doctrine-bundle\lib\PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest.php:68
FAILURES!
Tests: 6, Assertions: 9, Errors: 1.
I really want to know the best way to know what I am doing here and if I am doing it right - if not the best way to do it. Perpahs what I am trying to do is not even worthe it but I believe no single line of code should be seen as worthless and unworthy of testing.
In general, code that you provided is valid and work as it should. You describe what you expects from code and then test that your code meet your expectations.
In your case you are expecting that exception with type \RuntimeException will be thrown and if it is just \Exception - there is an error
Another question is how phpunit tell about it to you. In your case it shows message
1) PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest::testUnsupportedAnnotationDriverException
Exception: Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver.
It is not very obviouse.
You can make expectations more explicit if descibe them with setExpectedException:
/**
* Test fail if client requests a unsupported driver
*/
public function testUnsupportedAnnotationDriverException()
{
$entityManagerService = new EntityManagerService();
$this->setExpectedException(\RuntimeException::class);
// $this->setExpectedException('RuntimeException'); // for PHP < 5.5
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}
or using proper annotation #expectedException:
/**
* Test fail if client requests a unsupported driver
*
* #expectedException \RuntimeException
*/
public function testUnsupportedAnnotationDriverException()
{
$entityManagerService = new EntityManagerService();
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}
Now if you change throw \RuntimeException to throw \Exception in tested method, you can note that phpunit's message now look like this
Failed asserting that exception of type "Exception" matches expected exception "\RuntimeException". Message was: "Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver."
So you see more explicit what is wrong
When a user accesses /user/validate without the correct post parameters, my Zend application throws a zend exception. (I get the standard "An Error Occurred" message, framed within my layout). This is intentional.
I am now trying to test that behaviour with PHPUnit. Here's my test:
/**
* #expectedException Zend_Exception
*/
public function testEmptyUserValidationParametersCauseException() {
$this->dispatch('/user/validate');
}
When I run the test I get a message saying it failed, "Expected exception Zend_Exception". Any ideas?
I have other tests in the file which are working just fine...
Thanks!
The Zend_Controller_Plugin_ErrorHandler plugin handles exceptions for you and the default Error_Controller forces a 500 redirect which may mean the exception you are testing for no longer exists. Try the following unit test and see if it passes:
public function testUnknownUserRedirectsToErrorPage()
{
$this->dispatch('/user/validate');
$this->assertController('error');
$this->assertAction('error');
$this->assertResponseCode('500');
}
If this works then it shows that by the time you are rendering the error view the exception will no longer exist as it is contained in the code before the redirect.
Well, maybe it simply fails because no exception is thrown?
Did you try running the test without "#expectedException Zend_Exception"? Is there an exception at all?
For example, if we try to get to some api but fail, or try to connect to our database but also fail.
There are several ways to deal with this:
Bubble the error into your server log file
Write the error to a text file
Store the errors in a database table
You can user PHP Error Handling. See set_error_handler
php error handling is tricky, and there are many points to take into account. In php, we have 3 types of errors:
Fatal errors, like calling an undefined function. There's not much you can do about them. At least, set display_errors=0, log_errors=1 in php.ini, so that they won't be displayed. Better yet, write a petition on bugs.php.net and demand eliminating fatals altogether. Fatal errors are such a shame!
"Normal" warnings and notices. The best is to convert them into exceptions, see example #1
Exceptions - the funny part. You can catch them in your code when appropriate + install a generic exception handler as a last chance option to log the error and tell users that something wrong is happened.
There is a default function of PHP to log errors; error_log
Example from PHP.net:
<?php
// Send notification through the server log if we can not
// connect to the database.
if (!Ora_Logon($username, $password)) {
error_log("Oracle database not available!", 0);
}
// Notify administrator by email if we run out of FOO
if (!($foo = allocate_new_foo())) {
error_log("Big trouble, we're all out of FOOs!", 1,
"operator#example.com");
}
// another way to call error_log():
error_log("You messed up!", 3, "/var/tmp/my-errors.log");
?>
Be aware of extensive logging. Especially on productive Systems.
If you just try to handle programming errors, first you should raise a debug mode flag in your code. strict php handling is also very helpful. (set in php.ini or by your apache vhost settings).
dont try browser/screen debugging. as mentioned in other postings. set diplay_errors=0 and log_errors=1 in your php ini (or set it by your apache vhost settings)
then open a console window and do: tail -f
on your php_error.log (path is set in php.ini or by apache vhost settings)
if you use a framework. use the framework debugging tools (cake)
if you have your own framework/or just code. You probably should write an own exception handler class with debug capabilities.
example:
class MyFactory{
public static function getLogger(){
return new MyLogger();
}
}
class ExampleExceptionWithLogging extends Exception{
public __construct ($message=''){
MyFactory::getLogger()->exception($message,$this->getTrace());
}
}
class MyLogger{
/**
* #var string $logfile
**/
protected $logFile = '/var/log/php_error.log';
/**
* #param string $message
* #param array $stackTrace
**/
public function exception($message,$stackTrace){
$prefix = '[EXCEPTION] ';
$this->writeOut($prefix.$message.' '.print_r($stackTrace,TRUE));
}
/**
* writes $value to given Logfile.
* #param string $value
* #param string|NULL $logFile FileName with full path
*/
protected function writeOut($value,$logFile = NULL){
if(is_null($logFile)){
$logFile = $this->logFile;
}
error_log($value,3,$logFile);
}
}
Usage:
throw new ExampleExceptionWithLogging('Sample Message');
There are 3 issues here:
redirecting the program flow to the error handling
Capturing information relevant to resolving the error
Making that information available to the relevant parties
As others have said, (1) can be dealt with using set_error_handler(), note that you can instantiate your own customer errors within your code, e.g.
if (!$_SESSION['authenticated_user']) {
$login="<a href='/login.php'>login</a>";
trigger_error("Not authorized please $login", E_USER_WARNING);
}
The established practice for capturing information is the stack trace - and this is indeed available in PHP, however this is a static snapshot of the state of the PHP code at the point the error occurred - if you've tested your code properly, then the fault likely has nothing to do with your PHP code. Its a good indicator of where you should try to fix your code, but not a good indicator of what you need to fix. The stacktrace is still a useful tool, but it was often the only tool for programs which were running for any length of time, other than recording detailled logs of what the program did in the run up to the error. As well as an obvious performance hit, wading through several megabytes of logfiles looking for an error can be like looking for a needle in a haystack. However since PHP programs usually just generate a web page the exit, this presents the opportunity to accumulate the detailled log of events in a PHP variable, then you can choose to write the variable to a file only once an error occurs.
Like most things about programming, there is a trade-off here - if an error has happenned that you didn't expect/ plan for, how do you know that you're error handling is going to work?
In terms of making the data available, you probably should not record it in a database - chances are your program may have failed because the database isn't working properly - error handling must be very, very robust. Dumping it into a uniquely named file is a good approach which avoids the file contention problems you'd have with appending to a consolidated log file. Or use the syslog facility. You might even email a copy of the error out (but again this is relying on another complex subsystem).
HTH
C.