Middleware va logging asoslari
API’ga kelgan har bir so‘rov uchun bir xil ishlarni bajarish kerak bo‘ladi: so‘rovni log qilish, foydalanuvchini tekshirish, javob vaqtini o‘lchash, panicni ushlash. Bu kodni har bir handlerga yozib chiqish noqulay va xatolarga olib keladi. Bitta handlerda unutib qo‘yilsa, butun tizimda teshik paydo bo‘ladi.
Bu muammoni middleware hal qiladi. Middleware — handlerni o‘rab oladigan va so‘rov handlerga yetib borishidan oldin yoki keyin qo‘shimcha ish bajaradigan qatlam.
Aeroportni tasavvur qiling. Samolyotga chiqishdan oldin har bir yo‘lovchi bir necha nazoratdan o‘tadi: ro‘yxatdan o‘tish, xavfsizlik tekshiruvi, pasport nazorati. Har bir nazorat o‘z ishini bajaradi va yo‘lovchini keyingisiga o‘tkazadi. Muammo bo‘lsa, yo‘lovchini to‘xtatadi. Middleware’lar ham xuddi shunday ketma-ket turgan nazorat punktlari.
Middleware qanday ko‘rinadi?
Go’da middleware — http.Handler qabul qilib, yangi http.Handler qaytaradigan funksiya:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. handlerdan OLDIN bajariladigan kod
next.ServeHTTP(w, r) // 2. keyingi handlerni chaqirish
// 3. handlerdan KEYIN bajariladigan kod
})
}
next— o‘ralayotgan handler. Bu oxirgi handler ham, boshqa middleware ham bo‘lishi mumkin.next.ServeHTTP(w, r)chaqirilganda boshqaruv keyingi qatlamga o‘tadi.- Middleware
nextni chaqirmasa, so‘rov shu yerda to‘xtaydi. Masalan, foydalanuvchi tizimga kirmagan bo‘lsa, auth middleware401qaytaradi va handlerga yetkazmaydi.
Middleware ham http.Handler qaytargani uchun natijani yana boshqa middleware bilan o‘rash mumkin. Shu tariqa zanjir hosil bo‘ladi.
Birinchi middleware
Eng oddiy middleware so‘rov kelganini konsolga yozadi:
Server terminalida:
2026/09/30 10:00:00 --> GET /hello
2026/09/30 10:00:00 handler ishlayapti
2026/09/30 10:00:00 <-- GET /hello
Kod qanday ishladi:
logRequest(mux)butun router’ni o‘rab oldi. Endi har qanday so‘rov avval middleware’dan o‘tadi.-->qatorinext.ServeHTTPdan oldin,<--qatori esa handler ishini tugatgandan keyin yozildi.ListenAndServegamuxemas, o‘ralganhandlerberildi.
Middleware zanjiri
Bir nechta middleware’ni ketma-ket o‘rash mumkin:
Zanjirni piyoz qatlamlariga o‘xshatish mumkin. So‘rov tashqi qatlamdan ichkariga kiradi, javob esa ichkaridan tashqariga chiqadi:
first(second(mux)) yozuvida eng chapdagi middleware eng tashqi qatlam bo‘ladi va birinchi ishlaydi.
Tartib muhim
Middleware’lar tartibi natijaga ta’sir qiladi. Masalan:
panicni ushlaydigan middleware eng tashqarida turishi kerak. Shunda ichkaridagi barcha qatlamlardagipanicushlanadi.- Logging middleware auth middleware’dan tashqarida tursa, rad etilgan so‘rovlar ham logga tushadi.
- Auth middleware ruxsatlarni tekshiradigan middleware’dan oldin ishlashi kerak. Aks holda foydalanuvchi kimligini bilmay turib, uning ruxsatini tekshirib bo‘lmaydi.
Zanjirni qulay yozish
Middleware’lar ko‘paysa, a(b(c(d(mux)))) ko‘rinishidagi yozuvni o‘qish qiyinlashadi. Kichik yordamchi funksiya bu muammoni hal qiladi:
type Middleware func(http.Handler) http.Handler
// chain middleware'larni yozilgan tartibda qo‘llaydi:
// chain(h, a, b, c) == a(b(c(h)))
func chain(h http.Handler, mws ...Middleware) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
Endi zanjir shunday yoziladi:
Middleware’lar chapdan o‘ngga, xuddi so‘rov o‘tadigan tartibda o‘qiladi.
Faqat ayrim yo‘llar uchun middleware
Middleware butun router’ga emas, bitta handlerga ham qo‘llanishi mumkin:
mux.Handle("GET /admin/stats", requireAdmin(http.HandlerFunc(stats)))
mux.HandleFunc("GET /books", listBooks) // middleware'siz
requireAdmin faqat /admin/stats uchun ishlaydi. http.HandlerFunc(stats) oddiy funksiyani http.Handlerga aylantiradi, chunki middleware http.Handler kutadi.
Middleware’ga parametr berish
Ba’zan middleware sozlamaga muhtoj bo‘ladi: logger, ruxsat etilgan rollar, limit va hokazo. Bunday holatda middleware’ni qaytaradigan funksiya yoziladi:
func withHeader(key, value string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(key, value)
next.ServeHTTP(w, r)
})
}
}
handler := withHeader("X-App-Version", "1.4.0")(mux)
withHeader("X-App-Version", "1.4.0") middleware qaytaradi. Keyin u muxga qo‘llanadi.
Go’da logging asoslari
Middleware’ning eng keng tarqalgan qo‘llanilishi — logging. Shuning uchun logging vositalari bilan ham tanishib chiqamiz.
Log — dastur ishlashi davomida yoziladigan yozuvlar. Server ishlab turganda uning ichida nima bo‘layotganini ko‘rish imkoni yo‘q. Loglar esa qaysi so‘rov qachon kelgani, qancha vaqt olgani va qanday xato yuz berganini ko‘rsatadi. Muammo yuz berganda birinchi navbatda loglarga qaraladi.
Go standart kutubxonasida ikkita paket bor: log va log/slog.
log paketi
log.Println("Server ishga tushdi")
log.Printf("Port: %d", 8080)
log.Fatal("Tuzatib bo‘lmaydigan xato") // yozadi va os.Exit(1)
log oddiy va qulay. Lekin u erkin matn yozadi. Minglab qatorli logdan "/books endpointiga kelgan va 500 ms dan ko‘p vaqt olgan so‘rovlarni" topish qiyin bo‘ladi.
log/slog — tuzilgan loglar
Go 1.21 versiyasida log/slog paketi qo‘shildi. U tuzilgan loglar (structured logging) yozadi: har bir yozuv kalit-qiymat juftliklaridan iborat bo‘ladi.
time=2026-09-30T10:00:00.000+05:00 level=INFO msg="so‘rov keldi" method=GET path=/books status=200
{"time":"2026-09-30T10:00:00.000+05:00","level":"INFO","msg":"so‘rov keldi","method":"GET","path":"/books","status":200}
{"time":"2026-09-30T10:00:00.000+05:00","level":"ERROR","msg":"bazaga ulanib bo‘lmadi","err":"connection refused"}
JSON formatidagi loglarni Grafana Loki, Elasticsearch yoki Datadog kabi tizimlar oson o‘qiydi. Ularda status=500 yoki path=/books bo‘yicha qidirish mumkin.
Log darajalari
| Daraja | Qachon ishlatiladi |
|---|---|
Debug |
Ishlab chiqish paytidagi batafsil ma’lumot |
Info |
Oddiy hodisalar: server ishga tushdi, so‘rov bajarildi |
Warn |
Kutilmagan, lekin xavfli bo‘lmagan holat |
Error |
Xato yuz berdi, e’tibor talab qilinadi |
Standartda Info va undan yuqori darajalar chiqariladi. Debug loglarini ko‘rish uchun darajani sozlash kerak:
Standart logger sifatida o‘rnatish
slog.SetDefault chaqirilgach, slog.Info, slog.Error kabi paket funksiyalari shu logger’dan foydalanadi.
Warning
Logga parol, token, karta raqami yoki shaxsiy ma’lumotlarni yozmang. Loglar ko‘p odam kira oladigan tizimlarda uzoq saqlanadi. U yerdan maxfiy ma’lumot sizib chiqishi ko‘p uchraydigan xavfsizlik muammosi.
slog bilan logging middleware
Endi ikkalasini birlashtiramiz:
{"time":"...","level":"INFO","msg":"server ishga tushdi","addr":":8080"}
{"time":"...","level":"INFO","msg":"so‘rov","method":"GET","path":"/books","duration":20312451}
Logger middleware’ga parametr sifatida berildi. Bu uni testda boshqa logger bilan almashtirish imkonini beradi.
Bu middleware’da bitta muhim narsa yetishmaydi: javob status kodi. Handler 404 yoki 500 qaytarganini bilmaymiz, chunki ResponseWriter yozilgan status kodni saqlamaydi. Bu muammoni keyingi darsda hal qilamiz.
Xulosa
- Middleware —
http.Handlerqabul qilib,http.Handlerqaytaradigan funksiya. next.ServeHTTP(w, r)dan oldingi kod so‘rov handlerga yetguncha, keyingi kod esa handler tugagach ishlaydi.- Zanjirda eng tashqi middleware birinchi ishlaydi. Tartib mantiqqa ta’sir qiladi.
- Middleware butun router’ga ham, alohida handlerga ham qo‘llanishi mumkin.
log/slogkalit-qiymat ko‘rinishidagi tuzilgan loglar yozadi. Ularni qidirish va tahlil qilish oson.
Keyingi darsda status kod, javob hajmi va request ID’ni ham yozadigan to‘liq logging middleware yozamiz.