Let's Go بهبودهای سرور و امنیت › گزارش خطای سرور
قبلی · فهرست · بعدی
فصل ۹.۲.

گزارش خطای سرور (Server Error Log)

در این بخش، نحوه پیکربندی گزارش خطای سرور (Server Error Log) را بررسی می‌کنیم. این شامل ثبت خطاها (Error Logging)، مدیریت خطا (Error Handling) و گزارش‌دهی (Reporting) می‌شود.

برای شروع، بیایید یک لاگر ساختاریافته (Structured Logger) برای ثبت خطاها (Error Logging) ایجاد کنیم:

مهم است که بدانید http.Server در Go ممکن است ورودی‌های لاگ خود را در رابطه با مواردی مانند پانیک‌های بازیابی نشده یا مشکلات پذیرش یا نوشتن در اتصالات HTTP بنویسد.

به طور پیش‌فرض، این ورودی‌ها را با استفاده از لاگر استاندارد می‌نویسد — که به این معنی است که آنها به جریان خطای استاندارد نوشته می‌شوند (به جای خروجی استاندارد مانند سایر ورودی‌های لاگ ما)، و آنها در همان قالب ورودی‌های لاگ ساختاریافته ما نخواهند بود.

برای نشان دادن این موضوع، بیایید یک خطای عمدی به برنامه خود اضافه کنیم و یک هدر Content-Length با یک مقدار نامعتبر در پاسخ‌های خود تنظیم کنیم. به روزرسانی render() به این صورت انجام دهید:

File: cmd/web/helpers.go
package main

...

func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
    ts, ok := app.templateCache[page]
    if !ok {
        err := fmt.Errorf("the template %s does not exist", page)
        app.serverError(w, r, err)
        return
    }

    buf := new(bytes.Buffer)

    err := ts.ExecuteTemplate(buf, "base", data)
    if err != nil {
        app.serverError(w, r, err)
        return
    }
    
    w.WriteHeader(status)

    buf.WriteTo(w)
}

...

سپس برنامه را اجرا کنید، یک درخواست به http://localhost:4000 ارسال کنید و لاگ برنامه را بررسی کنید. باید ببینید که به این صورت است:

$ go run ./cmd/web/
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="received request" ip=127.0.0.1:60824 proto=HTTP/1.1 method=GET uri=/
2024/03/18 11:29:23 http: invalid Content-Length of "this isn't an integer!"
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="received request" ip=127.0.0.1:60824 proto=HTTP/1.1 method=GET uri=/static/css/main.css
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="received request" ip=127.0.0.1:60830 proto=HTTP/1.1 method=GET uri=/static/img/logo.png

می‌توانیم ببینیم که ورودی سوم لاگ که ما را از مشکل Content-Length مطلع می‌کند، در قالبی بسیار متفاوت از دیگران است. این خوب نیست، به خصوص اگر بخواهید لاگ‌های خود را برای جستجوی خطاها فیلتر کنید یا از یک سرویس خارجی برای نظارت بر آنها و ارسال هشدارها استفاده کنید.

متأسفانه، امکان پیکربندی http.Server برای استفاده مستقیم از لاگر ساختاریافته جدید ما وجود ندارد. در عوض، باید لاگر ساختاریافته handler خود را به یک *log.Logger تبدیل کنیم که ورودی‌های لاگ را در یک سطح ثابت خاص می‌نویسد و سپس آن را با http.Server ثبت کنیم. می‌توانیم این تبدیل را با استفاده از تابع slog.NewLogLogger() انجام دهیم، به این صورت:

File: cmd/web/main.go
package main

...

func main() {
    addr := flag.String("addr", ":4000", "HTTP network address")
    dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name")
    flag.Parse()

    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

    ...

    srv := &http.Server{
        Addr:     *addr,
        Handler:  app.routes(),
        // Create a *log.Logger from our structured logger handler, which writes
        // log entries at Error level, and assign it to the ErrorLog field. If 
        // you would prefer to log the server errors at Warn level instead, you
        // could pass slog.LevelWarn as the final parameter.
        ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
    }

    logger.Info("starting server", "addr", srv.Addr)

    err = srv.ListenAndServe()
    logger.Error(err.Error())
    os.Exit(1)
}

...

با این کار، هر پیام لاگی که http.Server به طور خودکار می‌نویسد، اکنون با استفاده از لاگر ساختاریافته ما در سطح Error نوشته می‌شود. اگر برنامه را مجدداً راه‌اندازی کنید و یک درخواست دیگر به http://localhost:4000 ارسال کنید، باید ببینید که ورودی‌های لاگ اکنون به این صورت هستند:

$ go run ./cmd/web/
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="received request" ip=127.0.0.1:40854 proto=HTTP/1.1 method=GET uri=/
time=2024-03-18T11:29:23.000+00:00 level=ERROR msg="http: invalid Content-Length of \"this isn't an integer!\""

قبل از اینکه بیشتر پیش برویم، به فایل cmd/web/helpers.go خود برگردید و خطای عمدی را از متد render() حذف کنید:

File: cmd/web/helpers.go
package main

...

func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
    ts, ok := app.templateCache[page]
    if !ok {
        err := fmt.Errorf("the template %s does not exist", page)
        app.serverError(w, r, err)
        return
    }

    buf := new(bytes.Buffer)

    err := ts.ExecuteTemplate(buf, "base", data)
    if err != nil {
        app.serverError(w, r, err)
        return
    }
    
    w.WriteHeader(status)

    buf.WriteTo(w)
}

...

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
گزارش خطای سرور Server Error Log ثبت و نگهداری خطاهای سرور
ثبت خطاها Error Logging ذخیره و ثبت خطاها
مدیریت خطا Error Handling نحوه برخورد با خطاها
گزارش‌دهی Reporting ارائه گزارش از خطاها
لاگر ساختاریافته Structured Logger سیستم ثبت خطای سازمان‌یافته
سطح خطا Error Level درجه اهمیت خطا
فرمت خطا Error Format قالب پیام خطا
خروجی خطا Error Output محل نمایش خطاها
پیام خطا Error Message متن توضیح خطا
ردیابی خطا Error Trace مسیر وقوع خطا