Home > other >  Find out what controller and method executed helper function (from within the helper function itself
Find out what controller and method executed helper function (from within the helper function itself

Time:11-04

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.

  • Related