Let's Go Guided exercises › Add a debug mode
Previous · Contents · Next
Chapter 16.2.

Add a debug mode

If you’ve used web frameworks for other languages, like Django or Laravel, then you might be familiar with the idea of a ‘debug’ mode where detailed errors are displayed to the user in a HTTP response instead of a generic "Internal Server Error" message.

You goal in this exercise is to set up a similar ‘debug mode’ for our application, which can be enabled by using the -debug flag like so:

$ go run ./cmd/web -debug

When running in debug mode, any detailed errors and stack traces should be displayed in the browser similar to this:

16.02-01.png

Step 1

Create a new command line flag with the name debug and a default value of false. Then make the value from this command-line flag available to your handlers via the application struct.

Hint: The flag.Bool() function is the most appropriate for this task.

Show suggested code

Step 2

Go to the cmd/web/helpers.go file and update the serverError() helper so that it renders a detailed error message and stack trace in a HTTP response if — and only if — the debug flag has been set. Otherwise send a generic error message as normal. You can get the stack trace using the debug.Stack() function.

Show suggested code

Step 3

Try out the change. Run the application and force a runtime error by using a DSN without the parseTime=true parameter:

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

Visiting https://localhost:4000/ should result in a response like this:

16.02-01.png

Running the application again without the -debug flag should result in a generic "Internal Server Error" message.

Suggested code

Suggested code for step 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)
}

...

Suggested code for step 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)
}

...