Tarkibga o'tish

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 middleware 401 qaytaradi 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:

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func logRequest(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("--> %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("<-- %s %s", r.Method, r.URL.Path)
    })
}

func hello(w http.ResponseWriter, r *http.Request) {
    log.Println("    handler ishlayapti")
    fmt.Fprintln(w, "Salom!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /hello", hello)

    handler := logRequest(mux)

    log.Fatal(http.ListenAndServe(":8080", handler))
}
curl http://localhost:8080/hello

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.
  • --> qatori next.ServeHTTPdan oldin, <-- qatori esa handler ishini tugatgandan keyin yozildi.
  • ListenAndServega mux emas, o‘ralgan handler berildi.

Middleware zanjiri

Bir nechta middleware’ni ketma-ket o‘rash mumkin:

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func first(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("first: oldin")
        next.ServeHTTP(w, r)
        log.Println("first: keyin")
    })
}

func second(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("second: oldin")
        next.ServeHTTP(w, r)
        log.Println("second: keyin")
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
        log.Println("handler")
        fmt.Fprintln(w, "OK")
    })

    handler := first(second(mux))

    log.Fatal(http.ListenAndServe(":8080", handler))
}
first: oldin
second: oldin
handler
second: keyin
first: keyin

Zanjirni piyoz qatlamlariga o‘xshatish mumkin. So‘rov tashqi qatlamdan ichkariga kiradi, javob esa ichkaridan tashqariga chiqadi:

so‘rov → first → second → handler
javob  ← first ← second ← handler

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 qatlamlardagi panic ushlanadi.
  • 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:

handler := chain(mux, recoverer, logRequest, auth)

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)
2026/09/30 10:00:00 Server ishga tushdi
2026/09/30 10:00:00 Port: 8080

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.

main.go
package main

import (
    "log/slog"
    "os"
)

func main() {
    // Matn formatidagi logger
    textLog := slog.New(slog.NewTextHandler(os.Stdout, nil))
    textLog.Info("so‘rov keldi", "method", "GET", "path", "/books", "status", 200)

    // JSON formatidagi logger
    jsonLog := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    jsonLog.Info("so‘rov keldi", "method", "GET", "path", "/books", "status", 200)
    jsonLog.Error("bazaga ulanib bo‘lmadi", "err", "connection refused")
}
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:

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

Standart logger sifatida o‘rnatish

slog.SetDefault(logger)
slog.Info("endi global slog shu logger orqali ishlaydi")

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:

main.go
package main

import (
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "time"
)

func logging(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()

            next.ServeHTTP(w, r)

            logger.Info("so‘rov",
                "method", r.Method,
                "path", r.URL.Path,
                "duration", time.Since(start),
            )
        })
    }
}

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    mux := http.NewServeMux()
    mux.HandleFunc("GET /books", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(20 * time.Millisecond)
        fmt.Fprintln(w, "kitoblar")
    })

    handler := logging(logger)(mux)

    logger.Info("server ishga tushdi", "addr", ":8080")
    if err := http.ListenAndServe(":8080", handler); err != nil {
        logger.Error("server to‘xtadi", "err", err)
        os.Exit(1)
    }
}
{"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.Handler qabul qilib, http.Handler qaytaradigan 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/slog kalit-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.