Let's Go تمرین‌های راهنما › افزودن حالت دیباگ
قبلی · فهرست · بعدی
فصل ۱۶.۲.

افزودن حالت دیباگ

اگر از frameworkهای وب برای زبان‌های دیگر استفاده کرده‌اید، مثل Django یا Laravel، ممکن است با مفهوم یک حالت دیباگ آشنا باشید که در آن خطاهای دقیق به کاربر در یک پاسخ HTTP نمایش داده می‌شوند به جای یک پیام عمومی "Internal Server Error".

هدف شما در این تمرین راه‌اندازی یک حالت دیباگ مشابه برای برنامه ما است، که می‌تواند با استفاده از flag -debug به این شکل فعال شود:

$ go run ./cmd/web -debug

هنگام اجرا در حالت دیباگ، هر خطای دقیق و stack trace باید در مرورگر مشابه این نمایش داده شود:

16.02-01.png

مرحله 1

یک flag خط فرمان جدید با نام debug و مقدار پیش‌فرض false ایجاد کنید. سپس مقدار این flag خط فرمان را از طریق struct application در دسترس handlerهای خود قرار دهید.

نکته: تابع flag.Bool() برای این کار مناسب‌ترین است.

نمایش کد پیشنهادی

مرحله 2

به فایل cmd/web/helpers.go بروید و helper serverError() را به‌روزرسانی کنید تا یک پیام خطای دقیق و stack trace را در یک پاسخ HTTP render کند اگر — و فقط اگر — flag debug تنظیم شده باشد. در غیر این صورت یک پیام خطای عمومی را به طور عادی ارسال کنید. می‌توانید stack trace را با استفاده از تابع debug.Stack() دریافت کنید.

نمایش کد پیشنهادی

مرحله 3

تغییر را امتحان کنید. برنامه را اجرا کنید و یک خطای runtime را با استفاده از یک DSN بدون پارامتر parseTime=true ایجاد کنید:

$ go run ./cmd/web/ -debug -dsn=web:pass@/snippetbox

بازدید از https://localhost:4000/ باید منجر به یک پاسخ مشابه این شود:

16.02-01.png

اجرای برنامه دوباره بدون flag -debug باید منجر به یک پیام عمومی "Internal Server Error" شود.

کد پیشنهادی

کد پیشنهادی برای مرحله 1

File: cmd/web/main.go
package main

...

type application struct {
    debug          bool // Add a new debug field.
    logger        *slog.Logger
    snippets       models.SnippetModelInterface
    users          models.UserModelInterface
    templateCache  map[string]*template.Template
    formDecoder    *form.Decoder
    sessionManager *scs.SessionManager
}

func main() {
    addr := flag.String("addr", ":4000", "HTTP network address")
    dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name")
    // Create a new debug flag with the default value of false.
    debug := flag.Bool("debug", false, "Enable debug mode")
    flag.Parse()

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

    db, err := openDB(*dsn)
    if err != nil {
        logger.Error(err.Error())
        os.Exit(1)
    }
    defer db.Close()

    templateCache, err := newTemplateCache()
    if err != nil {
        logger.Error(err.Error())
        os.Exit(1)
    }

    formDecoder := form.NewDecoder()

    sessionManager := scs.New()
    sessionManager.Store = mysqlstore.New(db)
    sessionManager.Lifetime = 12 * time.Hour
    sessionManager.Cookie.Secure = true

    app := &application{
        debug:          *debug, // Add the debug flag value to the application struct.
        logger:         logger,
        snippets:       &models.SnippetModel{DB: db},
        users:          &models.UserModel{DB: db},
        templateCache:  templateCache,
        formDecoder:    formDecoder,
        sessionManager: sessionManager,
    }

    tlsConfig := &tls.Config{
        CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
    }

    srv := &http.Server{
        Addr:         *addr,
        Handler:      app.routes(),
        ErrorLog:     slog.NewLogLogger(logger.Handler(), slog.LevelError),
        TLSConfig:    tlsConfig,
        IdleTimeout:  time.Minute,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

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

    err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
    logger.Error(err.Error())
    os.Exit(1)
}

...

کد پیشنهادی برای مرحله 2

File: cmd/web/helpers.go
...

func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) {
    var (
        method = r.Method
        uri    = r.URL.RequestURI()
        trace  = string(debug.Stack())
    )

    app.logger.Error(err.Error(), "method", method, "uri", uri)

    if app.debug {
        body := fmt.Sprintf("%s\n%s", err, trace)
        http.Error(w, body, http.StatusInternalServerError)
        return
    }

    http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

...