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.
CodePudding user response:
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
CodePudding user response:
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.