Diagnostic Middleware Pada Laravel Lumen API

peeking

Photo by Dmitry Ratushny on Unsplash

Pernah tidak sih, kita penasaran dengan seberapa cepat proses eksekusi logic coding kita? Atau benar tidak sih query yang kita maksud dijalankan sesuai harapan? Saya kira sudah pasti ya. Bagi seorang software developer, sepertinya wajib untuk selalu melakukan benchmarking terhadap performance product nya. Apalagi dalam membuat restful API backend, dapat dipastikan kita membutuhkan tambahan informasi mengenai apa saja yang terjadi pada API buatan kita dan berapa besar resource yang digunakan.

Sebut saja istilahnya dengan diagnostic. Kali ini saya akan contohkan bagaimana konfigurasinya di laravel (lumen). Dengan memanfaatkan middleware, kita buat setiap response yang dihasilkan untuk melampirkan log query termasuk binding dan waktu untuk setiap query, juga penggunaan memory nya.


Pertama kita define sebuah constant pada file public/index.php sebelum baris definisi variable $app , sebut saja LUMEN_START

// To calculate your app execution time
define('LUMEN_START', microtime(true));
...
$app = ...
...

LUMEN_START akan menyimpan detik dengan tipe data float, hasil dari fungsi microtime(true) pertama kali request masuk sebelum $app di definisikan, sehingga memang waktu yang direkam betul-betul awal mula sebelum aplikasi kita bekerja.


Untuk mengambil log query apa saja yang dieksekusi selama laravel/lumen berjalan sampai akhir, laravel memiliki fasilitas fungsi bernama getQueryLog(), namun fungsi ini secara default tidak aktif, untuk mengaktifkannya, kita perlu tambahan before middleware. Before middleware yang akan berjalan di awal sebelum request diproses, kita sebut saja HeadMiddleware, isinya sebagai berikut :

public function handle($request, Closure $next)
{
  app('db')->enableQueryLog();

  // Return the response
  return $next($request);
}

Selanjutnya, kita buat middleware yang akan menjadi pemain utama kita kali ini. Kita sebut saja DiagnosticMiddleware.

public function handle($request, Closure $next)
{
  // Get the response
  $response = $next($request);
  
  $content = $response->getContent();
  if($content AND (is_array($content) OR is_object($content)) ){
    // Calculate execution time
    $executionTime = microtime(true) - LUMEN_START;

    $query_diagnostic = app('db')->getQueryLog();
    
    if(!env('APP_DEBUG')){
      foreach($query_diagnostic as $key => $val){
        $query_diagnostic[$key] = $val['time'];
      }
    }

    // I assume you're using valid json in your responses
    // Then I manipulate them below
    if(env('APP_DEBUG')){
      $all_request = $request->all();
      if($all_request){
        $content = ['request'=>$all_request] + $content;
      }
    }
    $content = $content + ['diagnostic'=>[
      'runtime' => round($executionTime, 4),
      'memoryusage' => memory_get_usage(true),
      'query' => $query_diagnostic
    ]];

    // Change the content of your response
    $response->setContent($content);

  }

  // Return the response
  return $response;
}

DiagnosticMiddleware ini merupakan after middleware yang akan berjalan diakhir sebelum response dikirim ke user. Setelah memastikan bahwa response dari aplikasi berupa json yang valid, langsung kita kalkulasi waktu eksekusi diambil dari waktu terakhir dikurangi nilai waktu dari constant LUMEN_START yang telah kita inisiasi di awal tadi (public/index.php). Selanjutnya kita panggil fungsi app('db')->getQueryLog() untuk mengambil log semua query yang dieksekusi. getQueryLog() ini akan mengembalikan sebuah array, di mana setiap elemen array tersebut berisi query, bindings, dan time yang merupakan waktu ekseskusi nya. Tidak lupa kita tambahkan kondisi if(!env('APP_DEBUG')) agar memastikan hanya dalam lingkungan non-production saja diagnostic yang detail diberikan, namun jika dalam lingkungan production kita cukup memberikan waktu eksekusi nya saja. Optionally, kita bisa saja menambahkan data request user untuk dikembalikan sebagai response sebagai perbandingan atau referensi. Terakhir kita gabungkan semua data menjadi sebuah response yang utuh. Dan nantinya response akan memiliki property ‘diagnostic’.

Tidak lupa kita daftarkan kedua middleware ini di bootstrap/app.php

$app->middleware([
  App\Http\Middleware\HeadMiddleware::class,
  App\Http\Middleware\DiagnosticMiddleware::class,
]);

Sampai sini kita sudah bisa mencoba kirim sebuah request ke lumen API. Dan akan menghasilkan contoh response seperti di bawah ini:

{
  "request": {
    ...
  },
  "result": true,
  "data": {
    ...
  },
  "diagnostic":{
    "runtime": 0.0943,
    "memoryusage": "4 mb",
    "query": [
      {
      },
      {
      },
      {
        "query": "select * FROM table where field = ? AND field2 = ?",
        "bindings": [
          "parameter1",
          "parameter2"
        ],
        "time": 32.35
      }
    ]
  }
}

Terlihat property diagnostic berisi 3 element, pertama runtime dengan nilai 0.0943 second, kedua memoryusage, dan ketiga array bernama query. diagnostic.query memiliki 3 item, yang berarti telah dilakukan 3 query dalam memproses request ini, dan contoh query terakhir dengan waktu 32.35 milisecond. runtime merupakan waktu keseluruhan termasuk proses non query, itu kenapa jumlah waktu semua query tidak sama dengan runtime. Dengan adanya waktu untuk setiap query, kita bisa tahu query yang mana yang paling memakan waktu. Untuk format waktu sendiri tentu kita bisa modifikasinya sesuai kebutuhan kita.

jika kita ubah environment APP_DEBUG menjadi false, maka hasilnya akan simple dan tidak mng-expose informasi sensitif, seperti di bawah ini:

{
  "result": true,
  "data": {
      ...
  },
  "diagnostic": {
    "runtime": 0.1027,
    "memoryusage": "4 mb",
    "query": [
      6.25,
      0.5,
      37.62
    ]
  }
}

Mudah bukan? Dengan informasi tambahan pada response API ini, kita dapat mengukur, menganalisa dan dapat menemukan titik lemah ataupun mencari improvement point pada logika kode kita.


Begitulah implementasi diagnostic untuk kebutuhan informasi tambahan pada response API. Bila ada kritik dan saran boleh tuangkan saja di komentar. Bila tulisan ini bermanfaat bagi anda, bantu share ya.

Saya cukupkan sesi tulisan ini. Terima kasih sudah berkunjung.
#staypositive

Ciao!


comments powered by Disqus