hugo/tpl/debug/debug.go
Bjørn Erik Pedersen 38e05bd3c7 Fix panic with debug.Dump with Page when running the server
This replaces the current implementation with `json.MarshalIndent` which doesn't produce the same output, but at least it doesn't crash.

There's a bug in the upstream `litter` library. This can probably be fixed, but that needs to wait.

I have tested `go-spew` which does not crash, but it is very data racy in this context.

FIxes #12309
2024-03-26 20:41:30 +01:00

187 lines
4.6 KiB
Go

// Copyright 2020 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 debug provides template functions to help debugging templates.
package debug
import (
"encoding/json"
"sort"
"sync"
"time"
"github.com/bep/logg"
"github.com/spf13/cast"
"github.com/yuin/goldmark/util"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/deps"
)
// New returns a new instance of the debug-namespaced template functions.
func New(d *deps.Deps) *Namespace {
var timers map[string][]*timer
if d.Log.Level() <= logg.LevelInfo {
timers = make(map[string][]*timer)
}
ns := &Namespace{
timers: timers,
}
if ns.timers == nil {
return ns
}
l := d.Log.InfoCommand("timer")
d.BuildEndListeners.Add(func() {
type data struct {
Name string
Count int
Average time.Duration
Median time.Duration
Duration time.Duration
}
var timersSorted []data
for k, v := range timers {
var total time.Duration
var median time.Duration
sort.Slice(v, func(i, j int) bool {
return v[i].elapsed < v[j].elapsed
})
if len(v) > 0 {
median = v[len(v)/2].elapsed
}
for _, t := range v {
// Stop any running timers.
t.Stop()
total += t.elapsed
}
average := total / time.Duration(len(v))
timersSorted = append(timersSorted, data{k, len(v), average, median, total})
}
sort.Slice(timersSorted, func(i, j int) bool {
// Sort it so the slowest gets printed last.
return timersSorted[i].Duration < timersSorted[j].Duration
})
for _, t := range timersSorted {
l.WithField("name", t.Name).WithField("count", t.Count).
WithField("duration", t.Duration).
WithField("average", t.Average).
WithField("median", t.Median).Logf("")
}
ns.timers = make(map[string][]*timer)
})
return ns
}
// Namespace provides template functions for the "debug" namespace.
type Namespace struct {
timersMu sync.Mutex
timers map[string][]*timer
}
// Dump returns a object dump of val as a string.
// Note that not every value passed to Dump will print so nicely, but
// we'll improve on that.
//
// We recommend using the "go" Chroma lexer to format the output
// nicely.
//
// Also note that the output from Dump may change from Hugo version to the next,
// so don't depend on a specific output.
func (ns *Namespace) Dump(val any) string {
b, err := json.MarshalIndent(val, "", " ")
if err != nil {
return ""
}
return string(b)
}
// VisualizeSpaces returns a string with spaces replaced by a visible string.
func (ns *Namespace) VisualizeSpaces(val any) string {
s := cast.ToString(val)
return string(util.VisualizeSpaces([]byte(s)))
}
func (ns *Namespace) Timer(name string) Timer {
if ns.timers == nil {
return nopTimer
}
ns.timersMu.Lock()
defer ns.timersMu.Unlock()
t := &timer{start: time.Now()}
ns.timers[name] = append(ns.timers[name], t)
return t
}
var nopTimer = nopTimerImpl{}
type nopTimerImpl struct{}
func (nopTimerImpl) Stop() string {
return ""
}
// Timer is a timer that can be stopped.
type Timer interface {
// Stop stops the timer and returns an empty string.
// Stop can be called multiple times, but only the first call will stop the timer.
// If Stop is not called, the timer will be stopped when the build ends.
Stop() string
}
type timer struct {
start time.Time
elapsed time.Duration
stopOnce sync.Once
}
func (t *timer) Stop() string {
t.stopOnce.Do(func() {
t.elapsed = time.Since(t.start)
})
// This is used in templates, we need to return something.
return ""
}
// Internal template func, used in tests only.
func (ns *Namespace) TestDeprecationInfo(item, alternative string) string {
v := hugo.CurrentVersion
hugo.Deprecate(item, alternative, v.String())
return ""
}
// Internal template func, used in tests only.
func (ns *Namespace) TestDeprecationWarn(item, alternative string) string {
v := hugo.CurrentVersion
v.Minor -= 6
hugo.Deprecate(item, alternative, v.String())
return ""
}
// Internal template func, used in tests only.
func (ns *Namespace) TestDeprecationErr(item, alternative string) string {
v := hugo.CurrentVersion
v.Minor -= 12
hugo.Deprecate(item, alternative, v.String())
return ""
}