I want to log 404 errors in Laravel 5.7, but I don't understand how to turn this on. Additional to logging 404 errors, I'd like to log the URL that was requested. Other errors are logged correctly.
.env
APP_DEBUG=true
LOG_CHANNEL=stack
config/logging.php
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
],
Per the Error Handling documentation:
The $dontReport property of the exception handler contains an array of
exception types that will not be logged. For example, exceptions
resulting from 404 errors, as well as several other types of errors,
are not written to your log files. You may add other exception types
to this array as needed:
In app/Exceptions/Handler the $dontReport array is empty.
I have customized the 404 view by having a Blade file resources/views/errors/404.blade.php
Based on this answer I've tried this code in app/Exceptions/Handler, but nothing shows up in the logs:
public function report(Exception $exception)
{
if ($this->isHttpException($exception)) {
if ($exception instanceof NotFoundHttpException) {
Log::warning($message);
return response()->view('error.404', [], 404);
}
return $this->renderHttpException($exception);
}
parent::report($exception);
}
UPDATE after accepting Mozammil's answer which works fine.
I've shortened his answer to the below. Don't forget to add use Illuminate\Support\Facades\Log to the Handler file.
public function render($request, Exception $exception)
{
if ($exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
Log::warning('404: ' . $request->url());
return response()->view('errors.404', [], 404);
}
return parent::render($request, $exception);
}
I have a similar requirement. Here's how I achieved it.
I have a helper method to determine if it's a 404.
private function is404($exception)
{
return $exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException
|| $exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
}
I also have another method to actually log the 404.
private function log404($request)
{
$error = [
'url' => $request->url(),
'method' => $request->method(),
'data' => $request->all(),
];
$message = '404: ' . $error['url'] . "\n" . json_encode($error, JSON_PRETTY_PRINT);
Log::debug($message);
}
Then, to log the error, I just do something like this in the render() method:
public function render($request, Exception $exception)
{
if($this->is404($exception)) {
$this->log404($request);
}
return parent::render($request, $exception);
}
I didn't know about the $internalDontReport. However, in all cases, my implementation worked for me :)
I use Telescope
Laravel Telescope
Laravel Telescope is an elegant debug assistant for the Laravel framework. Telescope provides insight into the requests coming into your application, exceptions, log entries, database queries, queued jobs, mail, notifications, cache operations, scheduled tasks, variable dumps and more.
https://laravel.com/docs/5.7/telescope
I attend to catch all kind of 4xx errors so, I edit in the app/Exceptions/Handler.php file by adding the below code in the render function
if($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException ||
$exception instanceof \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException ||
$exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException){
$error = [
'message'=> $exception->getMessage(),
'type' => \get_class($exception),
'url' => $request->url(),
'method' => $request->method(),
'data' => $request->all(),
];
$message = $exception->getStatusCode().' : ' . $error['url'] . "\n" . \json_encode($error, JSON_PRETTY_PRINT);
//Store the object in DB or log file
\Log::debug($message);
}
this Code will catch Exception for [ModelNotFoundException, MethodNotAllowedHttpException, NotFoundHttpException] - in short words this will catch 404 error, model not found in DB and bad method - and create an object called $error and you will be able to store it in what ever you want.
So the app/Exceptions/Handler.php will be like
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
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 \Throwable $exception
* #return void
*
* #throws \Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Throwable
*/
public function render($request, Throwable $exception)
{
if($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException ||
$exception instanceof \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException ||
$exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException){
$error = [
'message'=> $exception->getMessage(),
'type' => \get_class($exception),
'url' => $request->url(),
'method' => $request->method(),
'data' => $request->all(),
];
$message = $exception->getStatusCode().' : ' . $error['url'] . "\n" . \json_encode($error, JSON_PRETTY_PRINT);
\Log::debug($message);
}
return parent::render($request, $exception);
}
}
P.S. I am using laravel 8 but I am sure it will work in most popular versions.
Related
i'm new to laravel , hope someone could help me with this problem ,
i've created a request class to validate my inputs . But when the validation fails it doesn't return any error messages instead showing a 404 error.
my request class , recoverIdRequest
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class recoverIdRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'dob' => 'required',
'email' => 'required',
];
}
}
and here's my controller : testController
class testController extends Controller
{
/**
*
* #param \App\Http\Requests\recoverIdRequest $request
* #return Illuminate\Http\Response
*/
public function test(recoverIdRequest $request)
{
$validated = $request->validated();
$dob = $request->input('dob');
$new_dob = Carbon::parse($dob)->format('Y-m-d');
$email = $request->input('email');
$exist =Appl_students::where('email', $email)
->whereBetween('dob', [$new_dob, $new_dob])
->select('id', 'first_name')
->first();
if ($exist == null) {
return response()->json(['data' => $exist, 'success' => false, 'message' => "User not found"]);
} else {
$date_of_birth = Carbon::parse($dob)->format('d-m-Y');
$institute =Master::select(
'institution_name', 'website_url', 'institution_place',
'institution_post', 'institution_district', 'institution_pin',
'institution_state', 'institution_phone', 'institution_email')
->first();
return $institute;
Mail::to($email)->send(new RecoverApplicationId($exist->id, $date_of_birth, $exist->first_name, $institute->institution_name));
return response()->json(['data' => $exist, 'success' => true, 'message' => "Application ID has seen sent to registered mail"]);
}
}
}
and this is the response in postman when the validation fails :
routes/api.php
Route::group([ 'prefix'=>'modelTesting', ], function() {
Route::post('test/{id}' [testController::class,'test'])->middleware(['auth:api', 'scope:admin']);
});
Resolved
it was a problem with postman headers,i was able to fix the issue using the following headers :
Accept: application/json
X-Requested-With: XMLHttpRequest
You should follow the naming convention first in all your classes.
As the information not very much clear but it should return 422 error status. It might be the problem that when validation is failed then it is trying to redirect none existence URL. Please check the type of method you are using in postman to call the api. If it not resolved please paste the request data from the postman. And the routes.php
It does not give 404, if the validation process is not successful, validation redirects back to the previous page, in your case it gives 404 because there is no rest API and a previous page ... let's agree here
it's very natural and you just have to write a small validation method for it
try it, add this method to your form request class(recoverIdRequest) and try again
/**
* Returns validations errors.
*
* #param Validator $validator
* #throws HttpResponseException
*/
protected function failedValidation(Validator $validator)
{
// you can debug with dd() in this method
if ($this->wantsJson() || $this->ajax()) {
throw new HttpResponseException(response()->json($validator->errors(), 422));
}
parent::failedValidation($validator);
}
second step you should change handler
app/exceptions/Handler.php
public function render($request, Exception $e)
{
if ($request->ajax() || $request->wantsJson())
{
$json = [
'success' => false,
'error' => [
'code' => $e->getCode(),
'message' => $e->getMessage(),
],
];
return response()->json($json, 400);
}
return parent::render($request, $e);
}
My goal:
We ar developing an API and we need to customize error message not only to send custom string, but also to send cusotm code.
E.g.: custom invalid email error message should look like this:
error[
'code' => 102,
'message' => 'invalid email'
]
I could set these custom error messages, to be arrays, but I have a problem with emails.
I get:
"Array to string conversion" at Illuminate\Support\MessageBag at line 248.
The reason of it, is because it is expecting a string and now I have an array.
protected function transform($messages, $format, $messageKey)
{
return collect((array) $messages)
->map(function ($message) use ($format, $messageKey) {
// We will simply spin through the given messages and transform each one
// replacing the :message place holder with the real message allowing
// the messages to be easily formatted to each developer's desires.
return str_replace([':message', ':key'], [$message, $messageKey], $format);
})->all();
}
I would like to override (bind) this method with:
protected function transform($messages, $format, $messageKey)
{
return collect((array) $messages)
->map(function ($message) use ($format, $messageKey) {
if(is_array($message)){
$message = json_encode($message);
}
// We will simply spin through the given messages and transform each one
// replacing the :message place holder with the real message allowing
// the messages to be easily formatted to each developer's desires.
return str_replace([':message', ':key'], [$message, $messageKey], $format);
})->all();
}
I have daone the following steps.
I have created Libraries/Extensions/MessagesBag folder and plced the followint files there.
MessageBagServiceProvider.php
namespace App\Libraries\Extensions\MessageBag;
use Illuminate\Support\ServiceProvider;
class MessageBagServiceProvider extends ServiceProvider{
/**
* Indicates if loading of the provider is deferred.
*
* #var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->bind('Illuminate\Support\MessageBag', 'App\Libraries\Extensions\MessageBag\YcoMessageBag');
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return array('messagebag');
}
}
MessageBagFacade.php
namespace App\Libraries\Extensions\MessageBag;
use Illuminate\Support\Facades\Facade as IlluminateFacade;
class MessageBagFacade extends IlluminateFacade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'messagebag'; }
}
YcoMessagebag.php
namespace App\Libraries\Extensions\MessageBag;
use Illuminate\Support\MessageBag as OriginalMessageBag;
class YcoMessageBag extends OriginalMessageBag{
/**
* Format an array of messages.
*
* #param array $messages
* #param string $format
* #param string $messageKey
* #return array
*/
protected function transform($messages, $format, $messageKey)
{
return collect((array) $messages)
->map(function ($message) use ($format, $messageKey) {
if(is_array($message)){
$message = json_encode($message);
}
// We will simply spin through the given messages and transform each one
// replacing the :message place holder with the real message allowing
// the messages to be easily formatted to each developer's desires.
return str_replace([':message', ':key'], [$message, $messageKey], $format);
})->all();
}
}
I have registered my MessageBagServiceprodider.php in config/app.php
App\Libraries\Extensions\MessageBag\MessageBagServiceProvider::class,
When I have died and dumped in MessageBagServiceProvider's register method, it worked, the code died and dumped "hello".
But the MessageBag class is not overriding, still the original class is loaded.
I have tried to play with
$this->app->bind('Illuminate\Support\MessageBag', 'App\Libraries\Extensions\MessageBag\YcoMessageBag');
I have tried with: $this->app->singleton,
I have tried to reach the original class with \Illuminate\Support\MessageBag also tried with '\App\Libraries\Extensions\MessageBag\YcoMessageBag', but no success.
Can I override this class? What can be the solution?
Thank you!
I have figured it out, here is my solution:
I have created Exteptions/Handler.php
class Handler extends ExceptionHandler
{
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
*
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($exception, $request);
} elseif ($exception instanceof ModelNotFoundException) {
$modelName = strtolower(class_basename($exception->getModel()));
return $this->errorResponse(
'Does not exists any ' . $modelName . ' with this id',
Response::HTTP_NOT_FOUND
);
} elseif ($exception instanceof AuthenticationException) {
return $this->unauthenticated($request, $exception);
} elseif ($exception instanceof AuthorizationException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_FORBIDDEN);
} elseif ($exception instanceof MethodNotAllowedHttpException) {
return $this->errorResponse('The specified request is invalid!', Response::HTTP_METHOD_NOT_ALLOWED);
} elseif ($exception instanceof NotFoundHttpException) {
return $this->errorResponse('The specified url cannot be found!', Response::HTTP_NOT_FOUND);
} elseif ($exception instanceof HttpException) {
$message = $exception->getMessage();
$status = $exception->getStatusCode();
$httpStatusCodes = collect(Response::$statusTexts);
if (!$httpStatusCodes->has($status)) {
$status = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if ($message == "") {
$message = "An error occured when processing request!";
}
return $this->errorResponse($message, $status);
}
return parent::render($request, $exception);
}
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $this->errorResponse('Unauthenticated', Response::HTTP_UNAUTHORIZED);
}
/**
* Create a response object from the given validation exception.
*
* #param \Illuminate\Validation\ValidationException $e
* #param \Illuminate\Http\Request $request
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
$errors = $e->validator->errors()->getMessages();
$errors = $this->convertValidationErrors($errors);
return response()->json([
'success' => false,
'error' => $errors,
'status' => 422,
'message' => $e->getMessage()
], 422);
}
protected function convertValidationErrors($errors){
$codes = [
101 => [
'code'=> 101,
'message'=> 'A mező kitöltése kötelező'
],
106=>[
'code'=> 106,
'message'=> 'A mező tartalma nem elég hosszú'
],
102 =>[
'code'=> 102,
'message'=> 'A mező tartalma nem elég hosszú'
],
103 => [
'code'=> 103,
'message'=> 'A mező tartalma túl hosszú'
],
105 => [
'code'=> 105,
'message'=> ' A mező tartalma nem szöveg'
],
107 => [
'code'=> 107,
'message'=> 'Formátum nem megfelelő'
],
104 => [
'code'=> 104,
'message'=> 'Az email formátuma nem megfelelő'
],
];
foreach($errors as $key => $code){
if(isset($codes[$code[0]])) {
unset($errors[$key][0]);
$errors[$key]['code'] = $codes[$code[0]]['code'];
if(env('APP_ENV') != 'local') {
$errors[$key]['message'] = $codes[$code[0]]['message'];
}
}
}
return $errors;
}
}
Maybe not the most beautiful solution, but it works.
I am trying to create a RESTful API using laravel, I'm trying to fetch a resource with an invalid ID, and the result is 404 since it is not found, but my problem is the response is not in JSON format, but a View 404 (by default) with HTML. Is there any way to convert the response into JSON? For this situation, I use Homestead.
I try to include a fallback route, but it does not seem to fit this case.
Route::fallback(function () {
return response()->json(['message' => 'Not Found.'], 404);
});
I try to modify the Handler (App\Exceptions), but nothing change.
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
if ($request->ajax()) {
return response()->toJson([
'message' => 'Not Found.',
], 404);
}
}
return parent::render($request, $e);
}
For Laravel 9
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
You'll need to send the correct Accept header in your request: 'Accept':'application/json'.
Then Illuminate\Foundation\Exceptions\Handler will care of the formatting in the render method in your response:
return $request->expectsJson()
? $this->prepareJsonResponse($request, $e)
: $this->prepareResponse($request, $e);
if your project is only a RESTful API and no views, you could add a new middleware which add ['accept' => 'application/json'] header to all request. this will ensure that all response will return a json, instead of the views
<?php
namespace App\Http\Middleware;
use Closure;
class AddAjaxHeader
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->add(['accept' => 'application/json']);
return $next($request);
}
}
and add it into Kernel.php
You need to set APP_DEBUG in you .env file to false.
or, if you use a phpunit, like follows
config()->set('app.debug', false);
In Laravel 9, as per Official Documentation ModelNotFoundException is directly forwarded to NotFoundHttpException (which is a part of Symfony Component) that used by Laravel and will ultimately triggers a 404 HTTP response.
so, we need to checking Previous Exception using $e->getPrevious() just check previous exception is instanceof ModelNotFoundException or not
see below my code
// app/Exceptions/Handler.php file
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
if ($e->getPrevious() instanceof ModelNotFoundException) {
/** #var ModelNotFoundException $modelNotFound */
$modelNotFound = $e->getPrevious();
if($modelNotFound->getModel() === Product::class) {
return response()->json([
'message' => 'Product not found.'
], 404);
}
}
return response()->json([
'message' => 'not found.'
], 404);
}
});
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Throwable
*/
public function render($request, Throwable $exception)
{
switch (class_basename($exception)) {
case 'NotFoundHttpException':
case 'ModelNotFoundException':
$exception = new NotFoundHttpException('Not found');
break;
}
return parent::render($request, $exception);
}
I am using Laravel v 5.7.15.
I wrote a validation helper which validates an API request - this works successfully, and prior I was using a try/catch to surround it.
I have moved on to handling the exception in the handler, however I cannot get the function 'render' to run - it goes straight into 'report' and throws in the exception in my tinker console.
Handler: (full class as requested)
<?php
namespace App\Exceptions;
use Illuminate\Validation\ValidationException;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Log;
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',
];
/**
* #param Exception $exception
* #return mixed|void
* #throws Exception
*/
public function report(Exception $exception)
{
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)
{
dd($exception);
$log = new Log();
$log->status = 2;
// Validate API incoming data
if ($exception instanceOf ValidationException) {
foreach ($exception->errors() as $error) {
// collect multiple validation errors
$message[] = implode('', $error);
}
$message = implode('', $message);
$log->message = $message;
$log->save();
$response = [
'message' => $message,
'status' => 400,
];
} else {
$response = [
'message' => $exception->getMessage(),
'status' => '500',
];
}
return parent::render($request, $exception);
}
}
This fails to die and dump, however I can dd in the report function and this works fine. The rest of this file has been left untouched, save for the includes at the top of the file.
This is how I call my validator in my controller:
$this->validate($request, BlueparkValidatorArrays::$getOrders);
If anybody could point me in the right direction, I would be most grateful.
This may be caused by a problem in your log configuration.
The call to parent::report($exception); runs the following from the laravel source code:
public function report(Exception $e)
{
...
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception $ex) {
throw $e; // throw the original exception
}
...
}
Note throw $e not throw $ex. So if creating the logger implementation fails, the original exception that was being processed is thrown.
To test this, comment out parent::report($exception); in your report function and see if render() is called as expected.
If it is, your log configuration is not working. Make sure you have the correct permissions on your log location and that your .env file doesn't override any of laravel's logging settings. See How to debug Laravel error 500 with no logs, no information
Why is it not rendered in a given way? Other types of exceptions are working well except AccessDeniedHttpException
App/Exceptions/Handler.php
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException as AccessDeniedHttpException;
...
/**
* 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)
...
// 403 Forbidden
if ($exception instanceof AccessDeniedHttpException)
{
return response()->json([
'code' => 403,
'message' => 'This action is unauthorized1.',
],403);
}
// 401 Unauthorized
if ($exception instanceof AuthenticationException)
{
return response()->json([
'code' => 401,
'message' => 'Unauthenticated error.',
], 401);
}
The 401 is working like a charm, but the 403 do the original rendering.
Any solutions?
Try and include this in your App\Exceptions\Handler.php. Be sure to add use Illuminate\Auth\Access\AuthorizationException; at the top of your Hander.php:
protected $dontReport = [
\Illuminate\Auth\Access\AuthorizationException::class,
];
Apparently, AccessDeniedHttpException is an instance of AuthorizationException.
public function render($request, Exception $exception)
{
//Useful since some methods cannot be accessed in certain URL extensions
if ($exception instanceof AuthorizationException) {
return response()->view('errors.404', [], 404);
}
return parent::render($request, $exception);
}
class CouponStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;//please change the authorize return to true in request file.
}