Best practice of log custom exceptions in PHP
Asked Answered
I

2

7

I have a custom exception (which is further extended in may other custom exceptions). My project requires log all the customExceptions (and all of its decendents) that occurs. I have a logger that can log customException (and anything else). One of the way of doing so is explicitly log the exception whenever it is being handled as follows.

try{
    //some exception occur
}
catch(customeException $e)
{
     $log->logException($e);
     $e->showMessage(); // or do anything that we have to do with the error.
}

Since we are logging all the customExceptions, the other way I can think of, updating the customException constructor and log the exception right there inside the constructor. In that way, it ensure that all the customException is logged. However, if we go down to this path, my questions is:

  1. How to inject the logger to the customException?
  2. Will it be against the SRP principle?
  3. Will it be consider bad practice in OOP sense or what is the best practice on this?
Intrust answered 28/2, 2018 at 20:52 Comment(0)
S
5

I think that injecting logger into the CustomException is not right, cause (as you pointed) it breaks SRP and increase the complexity of your exceptions classes.

I'll suggest you to separate Exception from ExceptionHandler. Exception class should only contain information about "what (and where) went wrong". ExceptionHandler is responsible for logging exception (and doing some other work with exception if needed).

So you can setup one global ExceptionHandler (using set_exception_handler and set_error_handler or some framework-based exception handling mechanism like symfony's ExceptionListener), that will catch all unhandled exceptions.

<?php

class ExceptionHandler {
  /**
   * @var Logger
   */
  private $logger;

  public function __construct(Logger $logger)
  {
    $this->logger = $logger;
  }

  public function handle(Throwable $e)
  {
    $this->logger->logException($e);
  }
} 

In application code you can still throw and catch exceptions. I think there are 4 common situations.

Fully recoverable exceptions

This is general way for handling recoverable exceptions – such situations when you don't want to fail at all, but you need to do something when such exception occurs.

<?php

try {
  $methodThatThrowsException();
}
catch (DoesNotMatterException $e) {
  // do some stuff and continue the execution
  // note, that this exception won't be logged 
}

Recoverable logged exception

The same as previous, but you want to log this exception.

<?php

try {
  $methodThatThrowsException();
}
catch (NonCriticalExceptionThatShouldBeLogged $e) {
  $this->exceptionHandler->handle($e); // log exception
  // do some stuff and continue the execution
}

Non-recoverable exceptions with "finalizer"

You want to execute some specific business logic and then fail. You can catch the exception, process it and then throw it again. Global exception handler will handle this exception and log it.

<?php 

try {
  $methodThatThrowsException();
}
catch (CriticalException $e) {
  // do some stuff like cleanup/transaction rollback
  throw $e;
}    

Non-recoverable exceptions

If you want to just log the exception and fail, you can just throw this exception and global exception handler will catch and log it.

<?php

$methodThatThrowsException();

// ExceptionHandler::handle will be executed
Seesaw answered 1/3, 2018 at 9:52 Comment(0)
R
0

Services can be injected into other services as long as they are relevant and maintain SRP or do something general systemy like error logging. However, an exception isn't really a service, and it should only be concerned with managing the exception data based on being throwable.

I think logging in an exception class is also a problem due to:

  1. No control over which log type to use, warning, error, critical, etc. Unless you make it convoluted/complex
  2. No control over whether to log or not. There are scenarios where an exception can happen but it's a known scenario that needs no error handling etc
  3. You'd have to pass various other logging data in to the exception, which is possibly a bit of a smell depending on the data you want to log

At the point of catching an exception, log it, then throw a new one so the caller can also catch, log, throw. Repeat this throughout your app so every thinga method calls that needs to catch a potential exception, logs and throws it's own exception named relevant to that class.

When you get to the top, e.g. a controller, don't throw but render the view with a nice message.

Ramiform answered 8/10, 2020 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.