Lompat ke konten
dokumentasi.id
Commit ecba1e8
v2
Star2,8 rb

8

Plugin dan Request Logger

heimdall.Plugin adalah satu interface berisi tiga hook yang dipanggil klien pada titik-titik tertentu di siklus satu request: plugin.go:7-13. Plugin didaftarkan lewat AddPlugin, yang hanya menambahkan nilai ke slice plugins pada Client; setelah terdaftar, setiap hook dijalankan untuk semua plugin sesuai urutan pendaftaran httpclient/client.go:51-54. AddPlugin juga menjadi bagian interface heimdall.Client, sehingga httpclient.Client dan hystrix.Client menerima plugin dengan cara yang sama client.go:14-23. Klien hystrix tidak menyimpan daftar plugin sendiri, ia meneruskan panggilan ke klien http di dalamnya hystrix/hystrix_client.go:243-246.

Tiga Hook Lifecycle

Ketiga method pada interface hanya menerima pointer request, dan dua di antaranya menerima hasil akhir request. Perhatikan bahwa tidak ada nilai balik: plugin tidak dapat membatalkan request atau mengubah keputusan retry.

HookSignatureDipanggil saat
OnRequestStartfunc(*http.Request)tepat sebelum request dikirim ke Doer
OnRequestEndfunc(*http.Request, *http.Response)c.client.Do selesai tanpa error, membawa response
OnErrorfunc(*http.Request, error)c.client.Do mengembalikan error, membawa err

Ketiga helper reportRequestStart, reportError, dan reportRequestEnd hanya melakukan loop atas c.plugins tanpa logika lain httpclient/client.go:204-220. Helper itu dipanggil di dalam loop retry, sehingga OnRequestStart dan salah satu dari OnRequestEnd atau OnError dapat terjadi berkali-kali untuk satu panggilan Do httpclient/client.go:165-178. Diagram berikut memperlihatkan urutannya dalam satu attempt.

sequenceDiagram
    autonumber
    participant Do as httpclient.Client.Do
    participant P as heimdall.Plugin
    participant RL as requestLogger
    participant H as http.Client

    Do->>P: OnRequestStart req
    P->>RL: simpan time.Now ke context reqTime
    Do->>H: c.client.Do req
    alt err == nil
        H-->>Do: response
        Do->>P: OnRequestEnd req res
        P->>RL: Fprintf rl.out dengan status dan durasi
    else err != nil
        H-->>Do: err
        Do->>P: OnError req err
        P->>RL: Fprintf rl.errOut dengan ERROR
    end
%% fig: Tiga hook Plugin dalam satu attempt
%% desc: Do memanggil OnRequestStart sebelum request dikirim, lalu memilih OnRequestEnd atau OnError bergantung pada hasil c.client.Do.
Fig. 1: Tiga hook Plugin dalam satu attemptDo memanggil OnRequestStart sebelum request dikirim, lalu memilih OnRequestEnd atau OnError bergantung pada hasil c.client.Do.

Satu nuansa penting: OnRequestEnd dipanggil segera setelah c.client.Do tidak menghasilkan error, yaitu sebelum pemeriksaan status code yang memicu retry. Response 500 yang akan di-retry tetap dilaporkan sebagai OnRequestEnd, bukan OnError httpclient/client.go:177-185. Rincian perulangan dan skipRetry ada di Siklus Request dan Retry.

Request Logger Bawaan

plugins.NewRequestLogger(out, errOut) mengembalikan heimdall.Plugin dan menerima dua io.Writer: out untuk log standar dan errOut untuk log error. Nilai nil diganti os.Stdout dan os.Stderr plugins/request_logger.go:23-38. Implementasinya adalah struct requestLogger dengan dua field writer tersebut plugins/request_logger.go:14-21.

OnRequestStart menyimpan waktu mulai ke dalam context request dengan key privat reqTime bertipe ctxKey, lalu menimpa request secara in place lewat *req = *(req.WithContext(ctx)) plugins/request_logger.go:40-43. Kedua hook akhir membaca kembali nilai itu dari req.Context() untuk menghitung durasi:

graph LR
    S["OnRequestStart<br/>context.WithValue reqTime time.Now"] --> G
    E["OnRequestEnd / OnError<br/>req.Context"] --> G["getRequestDuration<br/>now.Sub startTime"]
    G --> MS["reqDuration / time.Millisecond"]
    MS --> OK["fmt.Fprintf rl.out"]
    MS --> FAIL["fmt.Fprintf rl.errOut"]
    style S fill:#f59e0b,color:#000
    style G fill:#8b5cf6,color:#fff
    style MS fill:#8b5cf6,color:#fff
    style OK fill:#2d7d46,color:#fff
    style FAIL fill:#dc2626,color:#fff
%% fig: Dari time.Now ke baris log
%% desc: Waktu mulai disimpan di context, dibaca lagi oleh getRequestDuration, lalu dipakai untuk menulis baris log sukses atau error.
Fig. 2: Dari time.Now ke baris logWaktu mulai disimpan di context, dibaca lagi oleh getRequestDuration, lalu dipakai untuk menulis baris log sukses atau error.

getRequestDuration bersifat defensif: bila nilai context tidak ada atau bukan time.Time, fungsi mengembalikan 0, sehingga logger tetap menulis baris log dengan durasi 0ms plugins/request_logger.go:60-71. Format tiap baris mengikuti time.Now().Format("02/Jan/2006 03:04:05"):

HookWriterFormat stringContoh baris
OnRequestEndrl.out%s %s %s %d [%dms]\n23/Jun/2018 12:48:04 GET http://google.com 200 [412ms]
OnErrorrl.errOut%s %s %s [%dms] ERROR: %v\ndiakhiri ERROR: <pesan error>

Baris sukses memuat timestamp, method, URL, status code, dan durasi dalam milidetik plugins/request_logger.go:45-51. Baris error memuat timestamp, method, URL, dan durasi, lalu diakhiri ERROR: beserta nilai error plugins/request_logger.go:53-58. Contoh pemakaian di README mendaftarkan logger ke klien dengan client.AddPlugin(requestLogger) setelah httpclient.NewClient README.md:304-326.

⚠️ Durasi yang dicatat adalah latensi satu attempt, bukan total waktu Do. Pada attempt kedua dan seterusnya, Do meng-clone request lebih dahulu, lalu memanggil reportRequestStart pada request hasil clone itu, sehingga OnRequestStart menulis ulang waktu mulai httpclient/client.go:149-166. Nilai [%dms] pada baris log terakhir karena itu bukan akumulasi semua percobaan.

Menguji dan Menulis Plugin Sendiri

Untuk pengujian, httpclient.MockPlugin membungkus mock.Mock dan meneruskan setiap hook ke m.Called(...) dengan argumen yang sama seperti interface aslinya httpclient/plugin_mock.go:9-27. Pola ini dipakai oleh TestPluginMethodsCalled, yang menetapkan ekspektasi OnRequestStart dan OnRequestEnd, lalu memastikan masing-masing dipanggil tepat satu kali dan membaca Method, URL, serta StatusCode dari argumen yang diterima plugin httpclient/client_test.go:496-527. TestPluginErrorMethodCalled memakai pola yang sama untuk OnError dan memeriksa isi pesan error yang diteruskan httpclient/client_test.go:529-552.

Plugin buatan sendiri cukup mengimplementasikan ketiga method tersebut. Karena hook dijalankan berurutan atas slice plugins, plugin yang didaftarkan lebih dahulu selalu menerima event lebih dahulu httpclient/client.go:204-220. Langkah pengujian lain, termasuk menjalankan go test, dibahas di Pengembangan dan Pengujian.