Customize Laravel error reporting to remove stack trace - php

How can you turn off or remove the stack traces from Laravel's error reports. They're annoying if you want to read them in your console. I know you can add a custom handler in app/Exceptions/Handler.php, but I have no idea how to do that.

Setting APP_DEBUG=false in your .env file works fine for the frontend.
If you don't want the stack trace lines to be outputted in the log files only, try this.
In /app/Exceptions/Handler.php add use Log; at the top, then add this in the report function:
Log::error('['.$e->getCode().'] "'.$e->getMessage().'" on line '.$e->getTrace()[0]['line'].' of file '.$e->getTrace()[0]['file']);
And remove:
parent::report($e);
More info: https://laracasts.com/discuss/channels/laravel/remove-stacktrace-from-log-files

Since I can not comment on or edit #Harold answer, here is a improved sollution:
In /app/Exceptions/Handler.php add use Log; at the top,
Then modify the report function:
public function report(Exception $e)
{
if (!config('app.debug')) {
Log::error('['.$e->getCode().'] "'.$e->getMessage().'" on line '.$e->getTrace()[0]['line'].' of file '.$e->getTrace()[0]['file']);
} else {
parent::report($e);
}
}

For those who don't like "add this, remove that" kind of instructions, here's what app/Exceptions/Handler.php should look like, based on Laravel 5.7, and sticking to a message format like PHP's native one:
<?php
namespace App\Exceptions;
use Log;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
Log::error(sprintf(
"Uncaught exception '%s' with message '%s' in %s:%d",
get_class($exception),
$exception->getMessage(),
$exception->getTrace()[0]['file'],
$exception->getTrace()[0]['line']
));
// parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
}
Note that you can replace Log::error() with a simple call to error_log() to write to PHP's standard error log. If you do that, you don't have to remove the call to parent::report(). That way you can keep the traces in the Laravel log for further debugging, but keep your main PHP log for quick checks.

Another way is to edit the log format in your logging config file. This means you don't have to override the default report function which would mean missing out on other logic within the parent function.
I set mine to this:
'daily' => [
'driver' => 'daily',
'formatter' =>LineFormatter::class,
'formatter_with' => [
'format' => "[%datetime%] %channel%.%level_name%: %message%\n %context%\n",
'dateFormat' => "Y-m-d H:i:s"
],
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],

simplely set your APP_DEBUG=false in your env file.

Harold and Jani's answers are correct.
However the log line is less detailed than the default one.
The best solution would be to edit the file:
vendor/laravel/frameworks/src/Illuminate/Log/LogManager.php
and add false argument when the includeStacktraces method is called.
add false to
$formatter->includeStacktraces();
so:
$formatter->includeStacktraces(false);
This will simply disable the stacktrace. Everything else remains the same.

Related

Laravel MonoLog, pushProcessor not Logging Addition attributes

I want to be able to add a unique id (Uid) to my logging.
In Example 1: Which is depended on config/logging.php and ProcessorTap files below is not working as expected. The logging is configured to use stdout which refers to the ProcessorTap class that is suppose to add a Uid, when the log statement is created (in accordance with UidProcessor)
Example 2: Which uses purely Mono classes works as expected.
Why isnt Example 1 adding the Uid to the logs, when laravel ("laravel/framework": "5.7.*") should be using Monolog classes as well ?
Example 1: When this api is invoked, the output for Log::info('test') does not include UiD
Route::get('/test', function () {
Log::info('test'); //output = [2020-03-24 04:51:16] local.INFO: test
});
config/logging.php:
'default' => env('LOG_CHANNEL', 'stdout'), //.env LOG_CHANNEL=stdout
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'tap' => [
ProcessorTap::class,
],
]
ProcessorTap:
use Monolog\Processor\UidProcessor;
class ProcessorTap
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
$logger->pushProcessor(new UidProcessor());
}
}
Example 2: Working correctly the Uid (a484a6729e14996c0af1)
is added to the log for $logger->info('test')
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
Route::get('/test', function () {
$logger = new Logger('main');
$logger->pushProcessor(new UidProcessor(20));
$logger->info('test'); // output = [2020-03-24 04:57:26] main.INFO: test [] {"uid":"a484a6729e14996c0af1"}
});
This might be a laravel (5.7)/mono version specific issue, but I was able to resolve the via iterating via the handlers and calling pushProcessor
use Monolog\Processor\UidProcessor;
class ProcessorTap
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
collect($logger->getHandlers())->each(function ($handler) {
$handler->pushProcessor(new UidProcessor());
});
}
}

Laravel logging.channels.single.path with queue is cached?

Im using Queue::before in AppServiceProvider.php and set logging.channels.single.path value every time when job started:
config(['logging.channels.single.path' => storage_path('logs/accounts/'.$command->acc->login.'.log')]);
When I running 1 job all ok - logs in the right place.
When running 2 or more it writing logs to different files - one account can write to another accounts logfile. Why is it happening? It looks like it is caching the config variable.
Queue on horizon redis. One job after done dispatching another same job with the same $acc instance.
Queue::before(function (JobProcessing $event) {
$job = $event->job->payload();
$command = unserialize($job['data']['command']);
Added ^^^ from where $command going.
Customization is now done through invoking a custom formatter for Monolog.
This can be setup in config/logging.php, note the non-default tap parameter:
'channels' => [
'daily' => [
'driver' => 'daily',
'tap' => [App\Logging\CustomFilenames::class],
'path' => storage_path('logs/accounts/laravel.log'),
'level' => 'debug',
],
]
In your custom formatter, you can manipulate the Monolog logger however you wish:
<?php
namespace App\Logging;
use Monolog\Handler\RotatingFileHandler;
class CustomFilenames
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger) {
foreach ($logger->getHandlers() as $handler) {
if ($handler instanceof RotatingFileHandler) {
$login = $command->acc->login;
$handler->setFilenameFormat("{filename}-$login-{date}", 'Y-m-d');
}
}
}
}
See: https://laravel.com/docs/5.6/logging#advanced-monolog-channel-customization
https://github.com/Seldaek/monolog/blob/master/src/Monolog/Handler/RotatingFileHandler.php
The configuration values work globaly for all sessions, like global variables (
See the exampe here https://laravel.io/forum/how-can-i-set-global-dynamic-variables-in-laravel)
You set the value in the config file always to the last login. Therefore all new logs go in the new named config file.

Laravel 5.2 password reset returns the fatal error: “Call to a member function getEmailForPasswordReset() on null”

There is a very similar question, but I couldn’t comment on it or follow the question author’s advice to fix my problem.
Password reset was already set up and worked, I got back to it to check translation strings, and started getting this error: Fatal error: Call to a member function getEmailForPasswordReset() on null (View: /home/vagrant/code/ecc/resources/views/emails/password.blade.php)
I didn’t use source control for the project, so I couldn’t find out when exactly it stopped working. The only things that I changed in my password controller, were getSendResetLinkEmailSuccessResponse() and getSendResetLinkEmailFailureResponse() to AJAXify my forms. Almost the same exact code runs in another project, but on the same Homestead, and it works. I checked the whole chain of calls, and unsurprisingly, there's nothing wrong with it—a user object becomes null somewhere during view rendering:
8. at Mailer->getView('emails.password', array('token' => 'd81d3190958330e2a4e0552db07a1efc80d1768eddde0447f8efb9e588719ff4', 'user' => object(User), 'message' => object(Message))) in Mailer.php line 323
4. at CompilerEngine->get('/home/vagrant/code/ecc/resources/views/emails/password.blade.php', array('__env' => object(Factory), 'app' => object(Application), 'errors' => object(ViewErrorBag), 'token' => 'd81d3190958330e2a4e0552db07a1efc80d1768eddde0447f8efb9e588719ff4', 'user' => null, 'message' => object(Message), 'locale' => 'en')) in View.php line 149
3. at PhpEngine->evaluatePath('/home/vagrant/code/ecc/storage/framework/views/469f1ba57eac0cc812c6c63e33641df67f64c100.php', array('__env' => object(Factory), 'app' => object(Application), 'errors' => object(ViewErrorBag), 'token' => 'd81d3190958330e2a4e0552db07a1efc80d1768eddde0447f8efb9e588719ff4', 'user' => null, 'message' => object(Message), 'locale' => 'en')) in CompilerEngine.php line 59
So, Mailer gets a user as a retreived object, but then it becomes null during view rendering. I tried reinstalling everything, vagrant destroy, dump-autoload, clear-compiled, removing all compiled views, I’m also not using Laravel Auto Presenter, or anything similar that tries to automatically wrap the user model in a decorator when it's passed into a view.
Can you please advise something? Thanks!
EDIT: Here’s my controller, as requested:
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller
{
use ResetsPasswords;
protected $subject;
protected $redirectTo = '/';
/**
* Create a new password controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
$this->subject = trans('emails.password_reset.subject');
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* #param \Illuminate\Http\Request $request
* #param string|null $token
* #return \Illuminate\Http\Response
*/
public function showResetForm($request, $token = null)
{
if (is_null($token)) {
abort(403, 'Unauthorized action.');
}
$email = $request->input('email');
return view('reset')->with(compact('token', 'email'));
}
/**
* Get the response for after the reset link has been successfully sent.
*
* #param string $response
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function getSendResetLinkEmailSuccessResponse($response)
{
return response()->json([
'success' => true,
]);
}
/**
* Get the response for after the reset link could not be sent.
*
* #param string $response
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function getSendResetLinkEmailFailureResponse($response)
{
return response()->json([
'success' => false,
]);
}
}
I use a view composer class to append a couple of variables to all views, one of them being $user that stores Auth::user(). Needless to say, this variable—which is obviously null, because a user is not logged in when it’s returned during password reset—overwrites a $user variable that Mailer passes further down the chain.
The solution is to either rename a $user variable in a view composer, use a custom password broker to overwrite emailResetLink() and change the variable name there, or pass an array of specific views in ComposerServiceProvider instead of a “*” as a wildcard.

Disable Log::info in production using Laravel

I assumed that by default the Log::info calls wouldn't log in production, but they are still coming in.
Im setting production using my .env file
APP_ENV=production
APP_DEBUG=false
Ive tried these commands as well, but no luck
composer dump-autoload
php artisan cache:clear
php artisan optimize
Am i missing something?
For anyone still finding this thread (8 years later):
Configure your log channels in config/logging.php file
Set "level" parameter for your log channel to a .env variable
Example:
'channels' => [
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Lumen Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'error'),
]
]
Now you can set the LOG_LEVEL variable in your .env file for each environment
Well, I think that it's too late to search for all the Log::info() and do the proposed answer by #jon__o
if (App::environment('local', 'staging')) {
Log::info($error);
}
But you can still do something. You can override the default Laravel logger instance with your own implementation.
Go to your ApplicationServiceProvider and override the log instance with a custom one:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerLogger();
}
/**
* Register the logger instance in the container.
*
* #return MyCustomWriter
*/
protected function registerLogger()
{
$this->app->instance('log', $log = new MyCustomWriter(
new Monolog($this->app->environment()), $app['events'])
);
$log->dontLogInfoOnEnvironmnets(['production', 'staging', 'other']);
return $log;
}
Now you can create your custom writer by just extending the Laravel's Writer and overriding the info() method.
class MyCustomWriter extends \Illuminate\Log\Writer
{
protected $dontInfoOn = [];
/**
* Log an informational message to the logs.
*
* #param string $message
* #param array $context
* #return void
*/
public function info($message, array $context = [])
{
// Since we are providing the app environment to the Monolog instance in out ApplicationServiceProvider
// we can get the environment from the Monolog getName() method
if(!in_array($this->monolog->getName(), $this->dontInfoOn)) {
return parent::info($message, $context);
}
}
/**
* Don't log info() on the supplied environments .
*
* #param array $environments
* #return void
*/
public function dontLogInfoOnEnvironmnets(array $environments)
{
$this->dontInfoOn = $environments;
}
}
This way, you can still keep you Log::info on testing environments without checking every time.
Only the displaying of errors will be suppressed when your application is not in debug mode. The Log::info() function will always log when called.
The simple solution is for you to wrap that Log::info() function in something like this:
if (App::environment('local', 'staging')) {
Log::info($error);
}
Be sure to include the App facade use App; at the top of your file. Alternatively you can use the app() helper to get the environment: $environment = app()->environment();.

Customize Exception Template by Bundle

How to customize exception by bundle?
Example:
I have two bundles: BackendBundle and FrontEndBundle. I want this two bundles to be handled by two different templates when error 404 is thrown.
How can I do that?
I had read http://symfony.com/doc/current/cookbook/controller/error_pages.html but still got no clues.
Like in the cookbook article mentioned, extend the TwigBundle and the Symfony\Bundle\TwigBundle\Controller\ExceptionController:findTemplate. There you can decide (if it's not in debug) which 404 to show.
This example assumes all you backend routes are reachable under /backend. Change it to your needs, or use other things from the request to determine your backend 404s.
namespace Acme\ErrorBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseController;
/**
* ExceptionController.
*/
class ExceptionController extends BaseController
{
/**
* #param Request $request
* #param string $format
* #param integer $code An HTTP response status code
* #param Boolean $debug
*
* #return TemplateReference
*/
protected function findTemplate(Request $request, $format, $code, $debug)
{
// find template for backend 404 errors
if (!$this->debug && 404 == $code && false !== strpos($request->getPathInfo(), '/backend')) {
$template = new TemplateReference('TwigBundle', 'Exception', 'backend404', $format, 'twig');
if ($this->templateExists($template)) {
return $template;
}
}
// the parent method finds the error404.html.twig for the frontend
return parent::findTemplate($request, $format, $code, $debug);
}
}
Also to mention, the ErrorBundle must inherit from the TwigBundle.
You can hook to the KernelEvents::EXCEPTION event and override the response that will be sent to the browser. I wrote a quick gist for you:
https://gist.github.com/bezhermoso/87716a9c72a1d12c5036
However, $event->getRequest()->get('_controller') will return null on 404 errors, obviously. So you have to account for that instance.

Categories