Reputation: 51
Let's say we have a helper function, logDatabaseError($exception) that logs QueryExceptions to a special log.
helpers.php
function logDatabaseError ($exception) {
$controller = ????;
$function = ????;
$log_string = "TIME: ".now().PHP_EOL;
$log_string.= "User ID: ".Auth::user()->id.PHP_EOL;
$log_string.= "Controller->Action:".$controller."->".$function.PHP_EOL;
$log_string.= $exception.PHP_EOL;
Storage::disk('logs')->append('database.log', $log_string);
}
This function is called from multiple controllers and multiple functions within those controllers.
Whenever something needs to be written to the database, in the catch part, we call this logDatabaseError function and pass to it the \Illuminate\Database\QueryException as $exception.
BestControllerEverController.php
class BestControllerEver extends Controller
{
function writeStuffToDatabase (Request $request) {
try {
DB::does-its-thing
}
catch(\Illuminate\Database\QueryException $exception) {
logDatabaseError($exception)
}
}
}
Is it possible for the logDatabaseError function to get both Controller name and function name without passing them as function parameters?
In this particular example case, $controller and $function variables in logDatabaseError function would be set to BestControllerEver and writeStuffToDatabase, respectively.
I know this is logged in the stack trace, but their location in $exception object is not always the same and extracting it from there is not reliable, at least from my limited experience.
Upvotes: 2
Views: 276
Reputation: 6351
You can use php debug_backtrace function to trace the error frames. Since spatie/backtrace is using debug_backtrace behind the scenes You can use the package
Install the package into application by running
composer require spatie/backtrace
Put that in your controller:
try {
\Illuminate\Support\Facades\DB::table('myunavialbetable')->get();
}
catch(\Illuminate\Database\QueryException $exception) {
logDatabaseError($exception);
}
Inside your helper file
function logDatabaseError ($exception) {
$backtrace = Spatie\Backtrace\Backtrace::create();
$controllerResponsible = collect($backtrace->frames())
->filter(function(Spatie\Backtrace\Frame $frame){
return ($frame->class);
})
->filter(function(Spatie\Backtrace\Frame $frame){
return is_subclass_of($frame->class, App\Http\Controllers\Controller::class);
})
->first();
$log_string = "TIME: " . now() . PHP_EOL;
$log_string .= "User ID: " . auth()->id() . PHP_EOL;
if ($controllerResponsible){
$log_string .= "Controller->Action:" . $controllerResponsible->class . "->" . $controllerResponsible->method . PHP_EOL;
}
$log_string .= $exception . PHP_EOL;
\Illuminate\Support\Facades\Storage::disk('logs')->append('database.log', $log_string);
// if you want to use on-demand log feature you can uncomment this
//This feature is available from Laravel v8.66.0
// Illuminate\Support\Facades\Log::build([
// 'driver' => 'single',
// 'path' => storage_path('logs/database.log'),
// ])->info($log_string);
}
NOTE:CONTROLLER MUST EXTEND
App\Http\Controllers\Controller
ADVANCE SOLUTION
Steps to be followed:
app/Exceptions/Handler.php
to below contentclass Handler extends ExceptionHandler
{
public $controllerResponsible = null;
/**
* 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 = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
$backtraceInstance = SpatieBacktrace::createForThrowable($e);
$controllerResponsible = collect($backtraceInstance->frames())
->filter(function (SpatieBacktraceFrame $frame) {
return ($frame->class);
})
->filter(function (SpatieBacktraceFrame $frame) {
return is_subclass_of($frame->class, \App\Http\Controllers\Controller::class);
})
->first();
$this->controllerResponsible = $controllerResponsible;
});
}
/**
* Get the default context variables for logging.
*
* @return array
*/
protected function context()
{
$extraContext = [];
if ($this->controllerResponsible instanceof SpatieBacktraceFrame) {
$extraContext['controller'] = $this->controllerResponsible->class;
$extraContext['method'] = $this->controllerResponsible->method;
$extraContext['controller@method'] = $this->controllerResponsible->class . '@' . $this->controllerResponsible->method;
}
return array_merge(parent::context(), $extraContext);
}
}```
So here is what happens.
By default you can add exta [content][3] by overriding context method inside `Handler.php`. And you dont need any other custom log. It will be logged by default logging.
Upvotes: 1
Reputation: 51
As per @waterloomatt's comment, the Route facade seems to provide a close enough solution:
using
$route_action = Route::currentRouteAction();
we can get output
App\Http\Controllers\BestControllerEver@writeStuffToDatabase
written into the log.
While this really only returns functions that are registered as routes, knowing where users encounter an error is enough; combined with the fact a full stack trace is also included in the $exception that gets passed to logDatabaseError.
P.S.: If you use this solution, remember to set
$route_action = (Route::currentRouteAction()) ? Route::currentRouteAction() : "Not registered as route!"
just in case.
Upvotes: 0