How to use report() function with a specific channel in Laravel? - laravel

You can define your log channels in config/logging.php as I have already done.
Then you can send a log to your new channel like this:
Log::channel('my-channel')->error('message');
Now, when I want to catch an exception without throwing it, I usually use the report() helper function like this:
try{
throw new Exception('message');
}catch(Exception $e){
report($e);
}
That is very useful for continuing a script after an exception is thrown, but it always logs the exception to my default logging channel.
I want to use the report() function and specify that it should be reported to my-channel.
I played around with app/Exceptions/Handler.php without any luck.

So far I decided to just make a simple class to emulate report() instead of trying to use it directly:
<?php
namespace App\Exceptions;
use Log;
use Throwable;
class Reporter
{
/**
* Reports an exception to a specific channel.
*
* #param Throwable $e
* #param string $channel
* #param array $context
*/
public static function report(Throwable $e, $channel = 'daily', $context = [])
{
Log::channel($channel)->error(
$e->getMessage(),
array_merge(
Context::getContext(),
$context,
['exception' => $e],
)
);
}
}
And you would use it like this:
try{
throw new Exception('message');
}catch(Exception $e){
Reporter::report($e, 'my-channel');
}
You can also pass extra context to the log message as an array.

Related

Nexmo Handling Errors in Laravel

How I can handle nexmo errors, I use try{}catch(){} but it not work, and I got this error Nexmo \ Client \ Exception \ Request (29)
Non White-listed Destination - rejected i know this error but i need handle it.
this is a code:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\NexmoMessage;
//use App\admin\Course;
class ConfirmedCourse extends Notification
{
use Queueable;
protected $course;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($course)
{
$this->course = $course;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['nexmo'];
}
public function toNexmo($notifiable)
{
try {
$message = new NexmoMessage();
$message->content($this->course)
->unicode();
return $message;
}catch (\Exception $e) {
$e->getMessage();
}
}
}
I fixed this in the past by wrapping the call to notify() in the try catch:
try {
$variableToCatch = $YourModel->notify(new ConfirmedCourse($data));
} catch (\Exception $e) {
// Do what you want here...
// Log::error('nexmo failed...');
// echo 'Caught exception: ', $e->getMessage(), "\n";
// Log::error($e->getMessage());
// dd($e->getMessage())
}
If you post the line where you are calling notify() I can update to your use exact needed statement.
Remove the try catch from ConfirmedCourse class and put it around the call in the method calling it.
Here is how I handled a slack notification failure:
try {
$slackNotification = $user->notify(new SlackNotification($slackData));
} catch (\Exception $e) {
Log::error('slack notification failed.');
}

Automatic dependency injection handle exception laravel

Here is my case
I have cron job(console command)
/**
* Execute the console command.
*
* #return mixed
*/
public function handle(OrdersMagentoService $magentoService)
{
try {
$orders = $magentoService->getRemoteOrders();
print_r($orders);
} catch (SoapFault $e) {
$this->line('Error connect to soap api, error: ' . $e->getMessage());
die;
} catch (\Throwable | \Exception $e) {
print_r($e->getMessage());
die;
}
}
In handle method i automaticaly inject OrdersMagentoService, this service do connect to magento soap api and exctend from BaseMagentoService
class OrdersMagentoService extends BaseMagentoService
{
public function getRemoteOrders()
{
$complex = [
'complex_filter' => [
[
'key' => 'status',
'value' =>
[
'key' => 'in',
'value' => 'closed,canceled,holded,processing,complete'
]
],
[
'key' => 'updated_at',
'value' => [
'key' => 'from',
'value' => now()->subDays(200),
]
]
]
];
return $this->salesOrderList($complex);
}
}
class BaseMagentoService
{
/**
* #var
*/
private $client;
/**
* #var
*/
private $session;
/**
* #var \Illuminate\Config\Repository|mixed
*/
protected $config;
/**
* BaseMagentoService constructor.
*/
public function __construct()
{
$this->config = config('services.magento');
$this->connect();
}
/**
* Do connect to soap api v2
*
* #throws \SoapFault
*/
private function connect()
{
$this->client = new SoapClient($this->getApiUrl());
$this->session = $this->client->login($this->config['user_name'], $this->config['password']);
}
public function __call($resource, $arguments)
{
return $this->client->$resource($this->session, ...$arguments);
}
}
In BaseMagentoService constructor i create connection to magento soap api. But if connection throw error(for example wrong username and pass) then i can't handle this in cron job file. I understand that laravel at first create OrdersMagentoService, it throw error and try catch in handle function not works, but i dont know how to fix this.
I can add in handle method
try {
$magentoService = resolve(OrdersMagentoService::class)
$orders = $magentoService->getRemoteOrders();
print_r($orders);
} catch (SoapFault $e) {
$this->line('Error connect to soap api, error: ' . $e->getMessage());
die;
} catch (\Throwable | \Exception $e) {
print_r($e->getMessage());
die;
}
And remove automatic DI, and tit should work well, but i dont want to do it.
Also if i add some try catch in connect method of BaseMagentoService then i cant log this error in my cron job.
What is best way to handle this?
First I would switch the handling from the artisan command to a Job or an Event that might be more suited to the problem. Then your artisan command simply fires off this new job or event each time is executed.
This is also somehow stated in the docs as a best practice:
For greater code reuse, it is good practice to keep your console commands light and let them defer to application services to accomplish their tasks.
Then if you look at the documentation about jobs, there is a section about error handling for failed jobs where you are told that you can define a method that gets triggered once he job has failed. Example excerpt taken from the docs:
class YourJob implements ShouldQueue
{
/**
* Execute the job.
*
* #param AudioProcessor $processor
* #return void
*/
public function handle(AudioProcessor $processor)
{
// Process uploaded podcast...
}
/**
* The job failed to process.
*
* #param Exception $exception
* #return void
*/
public function failed(Exception $exception)
{
// Send user notification of failure, etc...
}
}
You can read more in the documentation. There is also a paragraph about global jobs failure handling that might suit your use case

How to delegate exception to global exception in Laravel?

There is typical code in controller Laravel:
public function create(CreateInvoiceRequest $request)
{
try {
$invoice = Invoice::create(['']);
return response()->json($model);
} catch (\Exception $e) {
return \Response::json(["errors" => $e->getMessage()], 400);
}
}
In exception case I catch it and show message, how to delagate (move) this in global exception Laravel? Need I do something like this?
try { } } catch (\Exception $e) { throw new Exception($e); }
Laravel has a nice solution for this. In the documentation we are told to do this kind of exception handeling in App\Exceptions\Handler.
A very simple example could be the following:
// Your controller.
try {
$invoice = Invoice::create(['']);
return response()->json($model);
} catch (\Exception $e) {
throw new CustomException('Invoice creation failed.');
}
// app\Exceptions\Handler.php
public function render($request, Exception $exception)
{
if ($exception instanceof CustomException) {
return response()->view('errors.custom', [], 500);
}
return parent::render($request, $exception);
}
I tried to find out if create would throw a specific exception. Unfortunately I could not find it out so quickly. If this was the case, you could remove the try catch and just listen to this specific exception in the render method.
Update
(not tested)
In addition, you can also overwrite the save method to prevent from having to wrap (all) database writing method calls with a try and catch.
We will need a BaseModel class:
<?php
namespace App\Models;
use App\Exceptions\ModelSaveException;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
/**
* Save the model to the database.
*
* #param array $options
* #return bool
* #throws \App\Exceptions\ModelSaveException
*/
public function save(array $options = [])
{
try {
return parent::save($options);
} catch(\Exception $e) {
throw new ModelSaveException($this);
}
}
}
Your controller will look cleaner without try catch:
$invoice = Invoice::create(['']);
return response()->json($model);
As an extra we can check if our model was being created of updated by making is of the exists property.
<?php
namespace App\Exceptions;
class ModelSaveException extends \Exception
{
/**
* ModelSaveException constructor.
*
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function __construct($model)
{
if ($model->exists) {
parent::__construct('Failed updating model.');
} else {
parent::__construct('Failed creating model.');
}
}
}
Ofcourse don't forget to extend from you newly created model:
use App\Models\Model;
class Invoice extends Model
You can create a custom renderable exception.
try {
// Your code...
} catch (\Exception $e) {
throw new \App\Exceptions\CustomException('Your message');
}
Instead of type-checking exceptions in the exception handler's report and render methods, you may define report and render methods directly on your custom exception. When these methods exist, they will be called automatically by the framework:
/**
* Report the exception.
*
* #return void
*/
public function report()
{
//
}
/**
* Render the exception into an HTTP response.
*
* #param \Illuminate\Http\Request
* #return \Illuminate\Http\Response
*/
public function render($request)
{
return response(...);
}
For more information: https://laravel.com/docs/5.8/errors#renderable-exceptions

Exceptions will not render Laravel 5.7

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

How to catch errors and best practices?

I'm performing various tasks on an eloquent model.
e.g.
Flight Repository
function store($request){
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
This method is called from a controller:
public function store(FlightRepository $flight, $request){
$flight->store($request);
}
How should one approach potential errors? try/catch? Where should it be placed in the controller or repository? What would I catch anyway, what exception type?
According to Laravel 5.0 and above,
All the Exceptions thrown in any part of the Laravel App, the exceptions are catched inside the report() method of Exception/Handler.php file, like this:
UPDATED
Your Repo should throw an Exception like this:
class CustomRepository extends Repository
{
public function repoMethod($id)
{
$model = Model::find($id);
// Throw your custom exception here ...
if(!$model) {
throw new CustomException("My Custom Message");
}
}
}
And your Handler should handle the CustomException like this:
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
// Handle your exceptions here...
if($exception instanceof CustomException)
return view('your_desired_view')->with('message' => $exception->getMessage());
parent::report($exception);
}
Hope this helps!

Resources