Let's Go Guided exercises › Add a debug mode
Previous · Contents · Next
Chapter 17.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:

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

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:

17.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.
    errorLog       *log.Logger
    infoLog        *log.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()

    infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
    errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)

    db, err := openDB(*dsn)
    if err != nil {
        errorLog.Fatal(err)
    }
    defer db.Close()

    templateCache, err := newTemplateCache()
    if err != nil {
        errorLog.Fatal(err)
    }

    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.
        errorLog:       errorLog,
        infoLog:        infoLog,
        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,
        ErrorLog:     errorLog,
        Handler:      app.routes(),
        TLSConfig:    tlsConfig,
        IdleTimeout:  time.Minute,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    infoLog.Printf("Starting server on %s", *addr)

    err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
    errorLog.Fatal(err)
}

...

Suggested code for step 2

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

func (app *application) serverError(w http.ResponseWriter, err error) {
    trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
    app.errorLog.Output(2, trace)

    if app.debug {
        http.Error(w, trace, http.StatusInternalServerError)
        return
    }

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

...