skip to Main Content

Since I started logging exceptions on a production site I’m noticing a lot of them, especially 404s, more than I would expect for a site with barely any traffic, and I’d like to get to the bottom of whether they’re genuine users or just bots. To help with this, I want to capture the URL that the user was trying to visit before being redirected to the 404 route, so I can keep track of which non-existent routes are being mistakenly hit. I think I’m correct in assuming this URL should be available in the request, and that I just need to store the request and pass it through to the exception.

What’s the best way to do this in Laravel 8 onwards?

2

Answers


  1. Chosen as BEST ANSWER

    EDIT: just a few hours of using this solution in production, with the benefit of the newly-added request URLs, has confirmed for me just how many of these HTTP 4xx errors are junk - mostly automated bots and maybe a few script kiddies trying common routes. For this reason I've added some logic to ignore 404 and 405 errors, and may still add others that mostly contribute noise to the logfile.


    This was harder than it should have been, but this is the solution I'm currently using to log the request with all exceptions. It's probably not the cleanest way to do it, but it works perfectly for my needs. Thanks to John Lobo's answer for pointing me in the right direction.

    It works by inspecting each instance of the Exception class and using PHP's instanceof to check whether it's a HTTP exception or not. If it is, it gets logged with the request URL and returns a view with a status code. If it's a generic non-HTTP exception, it gets logged with the request URL and returns another view with no status code (or you can keep the default exception behaviour by removing the return block, which renders a blank screen in production).

    public function register()
    {
    
    $this->renderable(function (Exception $exception, $request) {
    
      $url = $request->fullUrl();
    
      if ($exception instanceof HttpException) {
      
      $status = $exception->getStatusCode();
    
      // Do not log HTTP 404 and 405s errors for reasons that will 
      // become apparent after a few hours of logging 404s and 405s
      if(($status !== 404) && ($status !== 405)) {
        Log::warning("Error $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage()); 
      }
    
      return response()->view("errors.error", [
      "exception" => $exception,
      "status" => $status
      ],
      $status
      );
    
    } else {
    
      $status = $exception->getCode();
    
      Log::warning("Exception $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage()); 
    
      return response()->view("errors.exception", [
      "exception" => $exception,
      "status" => $status
    
      ]);
      }
    });
    
    // Optionally suppress all Laravel's default logging for exceptions, so only your own logs go to the logfile 
    $this->reportable(function (Exception $e) {
    })->stop();
    
    }
    

  2. Catch 404 exceptions in Handler(AppExceptionsHandler).

    If you see Rendering Exceptions

    By default, the Laravel exception handler will convert exceptions into
    an HTTP response for you. However, you are free to register a custom
    rendering closure for exceptions of a given type. You may accomplish
    this via the renderable method of your exception handler.

    The closure passed to the renderable method should return an instance
    of IlluminateHttpResponse, which may be generated via the response
    helper. Laravel will deduce what type of exception the closure renders
    by examining the type-hint of the closure:

    so in the register method,call renderable

    public function register()
    {
    
        $this->renderable(function (NotFoundHttpException $e, $request) {
                
            Log::alert("404",[
                "fullUrl"=>$request->fullUrl(),
                "path"=>$request->path(),
                "message" =>$e->getMessage()
            ]);
    
          return response()->view('errors.404', [], $e->getStatusCode());    
        });
    }
    

    Also, don’t forget to import and use

    SymfonyComponentHttpKernelExceptionNotFoundHttpException;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search