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-131. 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-542. AddPlugin juga menjadi bagian interface heimdall.Client, sehingga httpclient.Client dan hystrix.Client menerima plugin dengan cara yang sama client.go:14-233. Klien hystrix tidak menyimpan daftar plugin sendiri, ia meneruskan panggilan ke klien http di dalamnya hystrix/hystrix_client.go:243-2464.
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.
| Hook | Signature | Dipanggil saat |
|---|---|---|
OnRequestStart | func(*http.Request) | tepat sebelum request dikirim ke Doer |
OnRequestEnd | func(*http.Request, *http.Response) | c.client.Do selesai tanpa error, membawa response |
OnError | func(*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-2205. 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-1786. 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.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-1857. 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-388. Implementasinya adalah struct requestLogger dengan dua field writer tersebut plugins/request_logger.go:14-219.
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-4310. 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.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-7111. Format tiap baris mengikuti time.Now().Format("02/Jan/2006 03:04:05"):
| Hook | Writer | Format string | Contoh baris |
|---|---|---|---|
OnRequestEnd | rl.out | %s %s %s %d [%dms]\n | 23/Jun/2018 12:48:04 GET http://google.com 200 [412ms] |
OnError | rl.errOut | %s %s %s [%dms] ERROR: %v\n | diakhiri ERROR: <pesan error> |
Baris sukses memuat timestamp, method, URL, status code, dan durasi dalam milidetik plugins/request_logger.go:45-5112. Baris error memuat timestamp, method, URL, dan durasi, lalu diakhiri ERROR: beserta nilai error plugins/request_logger.go:53-5813. Contoh pemakaian di README mendaftarkan logger ke klien dengan client.AddPlugin(requestLogger) setelah httpclient.NewClient README.md:304-32614.
⚠️ Durasi yang dicatat adalah latensi satu attempt, bukan total waktu
Do. Pada attempt kedua dan seterusnya,Domeng-clone request lebih dahulu, lalu memanggilreportRequestStartpada request hasil clone itu, sehinggaOnRequestStartmenulis ulang waktu mulai httpclient/client.go:149-16615. 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-2716. 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-52717. TestPluginErrorMethodCalled memakai pola yang sama untuk OnError dan memeriksa isi pesan error yang diteruskan httpclient/client_test.go:529-55218.
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-2205. Langkah pengujian lain, termasuk menjalankan go test, dibahas di Pengembangan dan Pengujian.