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:

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.
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.
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:

Running the application again without the -debug
flag should result in a generic "Internal Server Error"
message.
Suggested code
Suggested code for step 1
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
... 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) } ...