commands: Show server error info in browser

The main item in this commit is showing of errors with a file context when running `hugo server`.

This can be turned off: `hugo server --disableBrowserError` (can also be set in `config.toml`).

But to get there, the error handling in Hugo needed a revision. There are some items left TODO for commits soon to follow, most notable errors in content and config files.

Fixes #5284
Fixes #5290
See #5325
See #5324
This commit is contained in:
Bjørn Erik Pedersen
2018-10-03 14:58:09 +02:00
parent 3a3089121b
commit 35fbfb19a1
73 changed files with 1914 additions and 668 deletions

View File

@@ -15,7 +15,6 @@ package hugolib
import (
"context"
"errors"
"fmt"
"html/template"
"io"
@@ -29,6 +28,9 @@ import (
"strings"
"time"
_errors "github.com/pkg/errors"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/publisher"
"github.com/gohugoio/hugo/resource"
@@ -754,8 +756,6 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) {
return whatChanged{}, err
}
s.TemplateHandler().PrintErrors()
for i := 1; i < len(sites); i++ {
site := sites[i]
var err error
@@ -861,7 +861,7 @@ func (s *Site) handleDataFile(r source.ReadableFile) error {
f, err := r.Open()
if err != nil {
return fmt.Errorf("Failed to open data file %q: %s", r.LogicalName(), err)
return _errors.Wrapf(err, "Failed to open data file %q:", r.LogicalName())
}
defer f.Close()
@@ -942,7 +942,7 @@ func (s *Site) handleDataFile(r source.ReadableFile) error {
func (s *Site) readData(f source.ReadableFile) (interface{}, error) {
file, err := f.Open()
if err != nil {
return nil, fmt.Errorf("readData: failed to open data file: %s", err)
return nil, _errors.Wrap(err, "readData: failed to open data file")
}
defer file.Close()
content := helpers.ReaderToBytes(file)
@@ -1558,26 +1558,52 @@ func (s *Site) preparePages() error {
}
}
if len(errors) != 0 {
return fmt.Errorf("Prepare pages failed: %.100q…", errors)
}
return nil
return s.pickOneAndLogTheRest(errors)
}
func errorCollator(results <-chan error, errs chan<- error) {
errMsgs := []string{}
for err := range results {
if err != nil {
errMsgs = append(errMsgs, err.Error())
func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
var errors []error
for e := range results {
errors = append(errors, e)
}
errs <- s.pickOneAndLogTheRest(errors)
close(errs)
}
func (s *Site) pickOneAndLogTheRest(errors []error) error {
if len(errors) == 0 {
return nil
}
var i int
for j, err := range errors {
// If this is in server mode, we want to return an error to the client
// with a file context, if possible.
if herrors.UnwrapErrorWithFileContext(err) != nil {
i = j
break
}
}
if len(errMsgs) == 0 {
errs <- nil
} else {
errs <- errors.New(strings.Join(errMsgs, "\n"))
// Log the rest, but add a threshold to avoid flooding the log.
const errLogThreshold = 5
for j, err := range errors {
if j == i {
continue
}
if j >= errLogThreshold {
break
}
s.Log.ERROR.Println(err)
}
close(errs)
return errors[i]
}
func (s *Site) appendThemeTemplates(in []string) []string {
@@ -1650,8 +1676,7 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st
renderBuffer.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n")
if err := s.renderForLayouts(name, d, renderBuffer, layouts...); err != nil {
helpers.DistinctWarnLog.Println(err)
return nil
return err
}
var path string
@@ -1684,8 +1709,8 @@ func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath s
defer bp.PutBuffer(renderBuffer)
if err := s.renderForLayouts(p.Kind, p, renderBuffer, layouts...); err != nil {
helpers.DistinctWarnLog.Println(err)
return nil
return err
}
if renderBuffer.Len() == 0 {
@@ -1735,46 +1760,18 @@ func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath s
func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) (err error) {
var templ tpl.Template
defer func() {
if r := recover(); r != nil {
templName := ""
if templ != nil {
templName = templ.Name()
}
s.DistinctErrorLog.Printf("Failed to render %q: %s", templName, r)
s.DistinctErrorLog.Printf("Stack Trace:\n%s", stackTrace(1200))
// TOD(bep) we really need to fix this. Also see below.
if !s.running() && !testMode {
os.Exit(-1)
}
}
}()
templ = s.findFirstTemplate(layouts...)
if templ == nil {
return fmt.Errorf("[%s] Unable to locate layout for %q: %s\n", s.Language.Lang, name, layouts)
s.Log.WARN.Printf("[%s] Unable to locate layout for %q: %s\n", s.Language.Lang, name, layouts)
return nil
}
if err = templ.Execute(w, d); err != nil {
// Behavior here should be dependent on if running in server or watch mode.
if p, ok := d.(*PageOutput); ok {
if p.File != nil {
s.DistinctErrorLog.Printf("Error while rendering %q in %q: %s", name, p.File.Dir(), err)
} else {
s.DistinctErrorLog.Printf("Error while rendering %q: %s", name, err)
}
} else {
s.DistinctErrorLog.Printf("Error while rendering %q: %s", name, err)
}
if !s.running() && !testMode {
// TODO(bep) check if this can be propagated
os.Exit(-1)
} else if testMode {
return
return p.errorf(err, "render of %q failed", name)
}
return _errors.Wrapf(err, "render of %q failed", name)
}
return
}