Skip to content

Commit

Permalink
commands: Show server error info in browser
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Oct 5, 2018
1 parent fae48d7 commit 4e16cb2
Show file tree
Hide file tree
Showing 24 changed files with 353 additions and 114 deletions.
85 changes: 82 additions & 3 deletions commands/commandeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,27 @@
package commands

import (
"errors"
"fmt"

"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"

"github.com/gohugoio/hugo/livereload"

"github.com/gohugoio/hugo/config"

"github.com/spf13/cobra"

"github.com/spf13/afero"

"github.com/gohugoio/hugo/hugolib"
"github.com/spf13/afero"

"github.com/bep/debounce"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
Expand Down Expand Up @@ -72,6 +77,78 @@ type commandeer struct {

configured bool
paused bool

errorHandler *errorHandler
}

type errorHandler struct {
logger *loggers.Logger
sync.RWMutex
err *buildError
}

type buildError struct {
logger *loggers.Logger
What string
Err error
}

func (b buildError) log() {
b.logger.ERROR.Println(b.Error())
}

func (b buildError) Error() string {
return fmt.Sprintf("%s: %s", b.What, b.Err)
}

func (c *commandeer) getError() *buildError {
h := c.errorHandler
h.RLock()
defer h.RUnlock()
return h.err
}

func (c *commandeer) getErrorWithContext() interface{} {
h := c.errorHandler
h.RLock()
defer h.RUnlock()
if h.err == nil && h.logger.ErrorCounter.Count() == 0 {
return nil
}
m := make(map[string]interface{})

var err error

if h.logger.ErrorCounter.Count() > 0 {
err = &buildError{What: "Errors(s) in log", Err: errors.New(h.logger.Errors.String())}
} else {
err = h.err
}

m["Error"] = err
m["Version"] = hugoVersionString()

return m
}

func (c *commandeer) handleError(what string, err error) error {
h := c.errorHandler
h.Lock()
defer h.Unlock()

if err == nil {
h.err = nil
return nil
}

h.err = &buildError{logger: h.logger, What: what, Err: err}
h.err.log()

// TODO(bep) server error config
// Need to refresh the browser to show the error page.
livereload.ForceRefresh()

return h.err
}

func (c *commandeer) Set(key string, value interface{}) {
Expand Down Expand Up @@ -244,11 +321,13 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
}

logger, err := c.createLogger(config)
logger, err := c.createLogger(config, running)
if err != nil {
return err
}

c.errorHandler = &errorHandler{logger: logger}

cfg.Logger = logger

createMemFs := config.GetBool("renderToMemory")
Expand Down
28 changes: 2 additions & 26 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
package commands

import (
"os"

"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"

"github.com/spf13/nitro"
)
Expand Down Expand Up @@ -242,7 +240,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
}

func checkErr(logger *jww.Notepad, err error, s ...string) {
func checkErr(logger *loggers.Logger, err error, s ...string) {
if err == nil {
return
}
Expand All @@ -255,25 +253,3 @@ func checkErr(logger *jww.Notepad, err error, s ...string) {
}
logger.ERROR.Println(err)
}

func stopOnErr(logger *jww.Notepad, err error, s ...string) {
if err == nil {
return
}

defer os.Exit(-1)

if len(s) == 0 {
newMessage := err.Error()
// Printing an empty string results in a error with
// no message, no bueno.
if newMessage != "" {
logger.CRITICAL.Println(newMessage)
}
}
for _, message := range s {
if message != "" {
logger.CRITICAL.Println(message)
}
}
}
26 changes: 15 additions & 11 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ import (
"os/signal"
"sort"
"sync/atomic"

"github.com/gohugoio/hugo/common/loggers"

"syscall"

"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/pkg/errors"

"golang.org/x/sync/errgroup"

"log"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -85,7 +88,7 @@ func Execute(args []string) Response {
}

if err == nil {
errCount := int(jww.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError))
errCount := int(loggers.GlobalErrorCounter.Count())
if errCount > 0 {
err = fmt.Errorf("logged %d errors", errCount)
} else if resp.Result != nil {
Expand Down Expand Up @@ -118,7 +121,7 @@ func initializeConfig(mustHaveConfigFile, running bool,

}

func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
func (c *commandeer) createLogger(cfg config.Provider, running bool) (*loggers.Logger, error) {
var (
logHandle = ioutil.Discard
logThreshold = jww.LevelWarn
Expand Down Expand Up @@ -161,7 +164,7 @@ func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
jww.SetStdoutThreshold(stdoutThreshold)
helpers.InitLoggers()

return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil
return loggers.NewLogger(stdoutThreshold, logThreshold, outHandle, logHandle, running), nil
}

func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
Expand Down Expand Up @@ -637,17 +640,17 @@ func (c *commandeer) fullRebuild() {
c.commandeerHugoState = &commandeerHugoState{}
err := c.loadConfig(true, true)
if err != nil {
jww.ERROR.Println("Failed to reload config:", err)
c.handleError("Failed to reload config", errors.WithStack(err))
// Set the processing on pause until the state is recovered.
c.paused = true
} else {
c.paused = false
}

if !c.paused {
if err := c.buildSites(); err != nil {
jww.ERROR.Println(err)
} else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
err := c.buildSites()
c.handleError("Build failed", errors.WithStack(err))
if err == nil && !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
livereload.ForceRefresh()
}
}
Expand Down Expand Up @@ -839,11 +842,12 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
c.Logger.FEEDBACK.Printf("Syncing all static files\n")
_, err := c.copyStatic()
if err != nil {
stopOnErr(c.Logger, err, "Error copying static files to publish dir")
c.handleError("Error copying static files to publish dir", errors.WithStack(err))
break
}
} else {
if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
c.Logger.ERROR.Println(err)
c.handleError("Error syncing static files to publish dir", errors.WithStack(err))
continue
}
}
Expand Down Expand Up @@ -917,7 +921,7 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
}
case err := <-watcher.Errors:
if err != nil {
c.Logger.ERROR.Println(err)
c.handleError("Error while watching", errors.WithStack(err))
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro

decorate := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// First check the error state
err := f.c.getErrorWithContext()
if err != nil {
errTemplate := defaultBuildErrorTemplate
w.WriteHeader(errTemplate.statusCode) // TODO(bep) error server improve
errTemplate.buildErrorPage(err, w)
return
}

if f.s.noHTTPCache {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
w.Header().Set("Pragma", "no-cache")
Expand Down
86 changes: 86 additions & 0 deletions commands/server_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package commands

import (
"html/template"
"io"
)

type errorTemplate struct {
// To text template to produce a nice looking error page in the browser.
templ string

// The default HTTP status code shown for this error.
statusCode int
}

var defaultBuildErrorTemplate = errorTemplate{
templ: `<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>Error</title>
<style type="text/css">
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
}
main {
margin: auto;
max-width: 700px;
padding: 1rem;
}
.stack {
overflow: scroll;
background-color: #eee;
padding: 0.5rem;
}
.version {
color: lightgray;
padding-bottom: 1rem;
}
a {
color: blue;
text-decoration: none;
}
a:hover {
color: black;
}
</style>
</head>
<body>
<main>
<h1>{{ .Error.What }}</h1>
<p><b>Error:</b> {{ .Error.Err }}</p>
<div class="stack">
<!-<pre>{{/* .Error.Err.StackTrace */}}</pre>-->
</div>
<p class="version">{{ .Version }}</p>
<a href="">Reload Page</a>
</main>
</body>
</html>
`,
statusCode: 500,
}

func (templ errorTemplate) buildErrorPage(data interface{}, w io.Writer) error {
t, err := template.New("").Parse(templ.templ)
if err != nil {
return err
}

return t.Execute(w, data)
}
Loading

0 comments on commit 4e16cb2

Please sign in to comment.