Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API profiling #252

Closed
qstyler opened this issue Jan 6, 2015 · 35 comments
Closed

REST API profiling #252

qstyler opened this issue Jan 6, 2015 · 35 comments
Labels

Comments

@qstyler
Copy link

qstyler commented Jan 6, 2015

Hi! I'm having an API built with laravel. And I need to do some profiling on it.
It's the classic REST API that returns json.
How can I profile it with debugbar?

@barryvdh
Copy link
Owner

barryvdh commented Jan 6, 2015

I'm not sure if you can profile real-time, unless you also send the API requests with the same app.

You could use the browse button to view previous requests, which should include the API requests.

@barryvdh
Copy link
Owner

barryvdh commented Jan 6, 2015

That is at least, assuming your app is the one that returns the JSON. Or is your app making the API requests and you want to log Guzzle or something?

@jonagoldman
Copy link

jonagoldman commented Mar 23, 2016

@barryvdh I see this kind of issue all over the repo. Would also like to see a way to profile apis built with Laravel. Exactly how Debug Bar works now, just a way to pull the data so it can be attached to a JSON response.

@plasticbrain
Copy link

plasticbrain commented Apr 22, 2016

+1.. this would be incredibly useful.

@zakiaziz
Copy link

zakiaziz commented May 4, 2016

+1 Lots of people would find this helpful

@Xplouder
Copy link

Xplouder commented Aug 1, 2016

+1

@abellion
Copy link

abellion commented Aug 4, 2016

+1

Do you have any workaround for now ?

@ElfSundae
Copy link

ElfSundae commented Sep 20, 2016

Same here. I am googling a built-in way which can send the profiling data to the RESTful API response, and it seems no that way.

My current solution is attaching a middleware that adding the profiling data to the response, then enable this middleware for the Api routes group.

For example:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\JsonResponse;

class ProfileJsonResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (
            $response instanceof JsonResponse &&
            app()->bound('debugbar') &&
            app('debugbar')->isEnabled() &&
            is_object($response->getData())
        ) {
            $response->setData($response->getData(true) + [
                '_debugbar' => app('debugbar')->getData(),
            ]);
        }

        return $response;
    }
}

@dirkpostma
Copy link

+1

@ElfSundae
Copy link

I have updated the previous example code.

@thinkstylestudio
Copy link

The middleware solution works PERFECTLY. would be nice though if this was integrated in.

@vijaythecoder
Copy link

Is there a way to generate the debugbar on SPA? @ElfSundae solution returns all the json the data with _debugbar attached, but how do we add the debugbar on the front end that is entirely in different folder(SPA using Vuejs) ?

@kishanmd
Copy link

kishanmd commented Mar 15, 2017

I have adjusted @ElfSundae solution to also work when using dingo/api to handle your API responses (using the Dingo response builders):

<?php

namespace App\Http\Middleware;

use Closure;
use Dingo\Api\Http\Response;

class ProfileDingoHttpResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (
            $response instanceof Response &&
            app()->bound('debugbar') &&
            app('debugbar')->isEnabled()
        ) {
            $response->setContent(json_decode($response->morph()->getContent(), true) + [
                '_debugbar' => app('debugbar')->getData(),
            ]);
        }

        return $response;
    }
}

@anorgan
Copy link

anorgan commented May 12, 2017

@kishanmd Sometimes output of getContent() can not be decoded. I have modified it to be:

...
$content = json_decode($response->morph()->getContent(), true) ?? [];
$response->setContent($content + [
    '_debugbar' => app('debugbar')->getData(),
]);
...

@yangliuyu
Copy link

yangliuyu commented Jul 6, 2017

this works out of box on laravel-debugbar version 2.4.0

Route::get('/abc', function () {
    User::whereName('abc')->get();
    return json_encode(['a'=>1]);
});

screen shot 2017-07-07 at 5 27 44 pm

@blue7wings
Copy link

if you use Postman to test your api, add X-Requested-With: XMLHttpRequest in your request's header. otherwise, configure option 'capture_ajax' => true will never work.

@jpleveille-mv
Copy link

Maybe that's unrelated, but I'm developing an API using Laravel and we have another team working on a second Laravel application that happens to consume the API served by the former. When they want to profile their application as well as our underlying API they consume, they simply have both applications storing their JSON dumps in the same folder. Both application have config/debugbar.php:

<?php
return [
    'storage' => [
        'enabled' => (bool)env('DEBUGBAR_STORAGE_ENABLED'),
        'path' => env('DEBUGBAR_STORAGE_PATH')
    ]
];

Pretty straight forward from there. The bar in the Web application prompts dumps from both the Web and the API applications. If your environment doesn't define DEBUGBAR_STORAGE_ENABLED and DEBUGBAR_STORAGE_PATH, debugbar simply defaults to the regular storage.

@mkrell
Copy link

mkrell commented May 3, 2018

I'm using the middleware solution above. Works perfectly

@giovannipds
Copy link

@yangliuyu indeed, though it'd be nice and easier to let Laravel handle the return:

Route::get('/abc', function () {
    $user = User::whereName('abc')->get();
    return $user;
});

@zlanich
Copy link

zlanich commented May 18, 2018

This seems to work very well with Postman for me. I just turn Debug Bar on, make the API call with Postman, then go to the Folder icon and choose the latest request: http://zsl.io/pwIN9o. You'll need at least one web route so you can view Debug Bar, but no need to refresh that page or anything between requests. Clicking the Folder icon pulls the requests from the storage folder via. ajax.

@acepsaepudin
Copy link

nice info! thanks @zlanich .

@PaddyLock
Copy link

I can't get the debug toolbar to display in Postman app. The json files are written to storage/debugtoolbar but postman I think does not allow javascript in preview. So the question remains - how to profile API requests, when they have header info or post params that needs to be sent, and therefore can't be tested in a normal browser. My workaround is to use the history feature on a normal web page in same project.

@giovannipds
Copy link

@PaddyLock see @blue7wings's answer above.

@mkrell
Copy link

mkrell commented Oct 30, 2018

There's an alternative specifically for monitoring APIs which our company is using now: itsgoingd/clockwork

@barryvdh
Copy link
Owner

Yeah and https://github.com/laravel/telescope ofcourse now.

@PaddyLock
Copy link

@PaddyLock see @blue7wings's answer above.

I tried that but postman does still not show it

@PaddyLock
Copy link

Yeah and https://github.com/laravel/telescope ofcourse now.

Trying telescope now. Works a treat. Same as debug toolbar history really though for requests, and the way it is used on a separate web page, rather than at the bottom of the response.

@vntrungld
Copy link

vntrungld commented Dec 11, 2018

<?php

namespace App\Http\Middleware;

use Closure;
use Dingo\Api\Http\Response;

class ProfileJsonHttpResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (
            $response instanceof Response &&
            app()->bound('debugbar') &&
            app('debugbar')->isEnabled()
        ) {
            $queries_data = $this->sqlFilter(app('debugbar')->getData());

            $response->setContent(json_decode($response->morph()->getContent(), true) + [
                '_debugbar' => [
                    'total_queries' => count($queries_data),
                    'queries' => $queries_data,
                ]
            ]);
        }

        return $response;
    }

    /**
     * Get only sql and each duration
     *
     * @param $debugbar_data
     * @return array
     */
    protected function sqlFilter($debugbar_data) {
        $result = array_get($debugbar_data, 'queries.statements');

        return array_map(function ($item) {
            return [
                'sql' => array_get($item, 'sql'),
                'duration' => array_get($item, 'duration_str'),
            ];
        }, $result);
    }
}

Thanks for @ElfSundae and @kishanmd, I've just put the sql filter to get only sql and each duration. Thanks @barryvdh for this awesome package.

@mgolshan
Copy link

Yeah and https://github.com/laravel/telescope ofcourse now.

Whereas Telescope is a useful tool, it does not provide performance information.

@stale
Copy link

stale bot commented Jul 29, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If this issue is still present on the latest version of this library on supported Laravel versions, please let us know by replying to this issue so we can investigate further.
Thank you for your contribution! Apologies for any delayed response on our side.

@stale stale bot added the stale label Jul 29, 2020
@stale stale bot closed this as completed Aug 7, 2020
@kirkbushell
Copy link

+1

@Yuriy-Svetlov
Copy link

+1
I don't want to offend Laravel, but in Yii2 everything is much easier and better. But maybe I haven't gotten used to Laravel yet.

@akalongman
Copy link

+1 for SPA

@jhormantasayco
Copy link

jhormantasayco commented Mar 3, 2022

I just have updated middleware for php 8

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;

class ProfileJsonHttpResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        if (
            $response instanceof JsonResponse &&
            app()->bound('debugbar') &&
            app('debugbar')->isEnabled() &&
            env('API_DEBUG', false)
        ) {
            $queriesData = $this->sqlFilter(app('debugbar')->getData());

            $responseData = json_decode($response->getContent(), true) + [
                '_debugbar' => [
                    'total_queries' => count($queriesData),
                    'duplicates' => collect($queriesData)
                        ->groupBy('sql')
                        ->map(fn ($rows) => $rows->count())
                        ->map(fn ($value, $index) => [
                            'sql'   => $index,
                            'total' => $value
                        ])
                        ->sortByDesc('total')
                        ->values()
                        ->toArray(),
                    'queries' => $queriesData,
                ]
            ];

            $response = $response->setContent(json_encode($responseData));
        }

        return $response;
    }

    /**
     * Get sql queries information.
     *
     * @param array $debugbarData
     * @return array
     */
    protected function sqlFilter(array $debugbarData): array
    {
        $result = Arr::get($debugbarData, 'queries.statements');

        return array_map(fn ($item) => [
            'sql' => Arr::get($item, 'sql'),
            'duration' => Arr::get($item, 'duration_str'),
            'stmt_id' => Arr::get($item, 'stmt_id'),
            'backtrace' => collect(Arr::get($item, 'backtrace'))->mapWithKeys(fn ($item) => [
                $item->index => "{$item->name}:{$item->line}"
            ]),
        ], $result);
    }
}

@annelbco
Copy link

For anyone else still struggling, I was able to make it work out of the box following @zlanich's post
(sry for the crappy gif due to size limit here, but hope everyone can get the gist of it)

debugbar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests