server: Strip ANSI escape codes from browser error log

Fixes #13037
This commit is contained in:
Bjørn Erik Pedersen 2024-11-14 09:43:37 +01:00
parent 46e17053c8
commit ce9cf882a5
3 changed files with 59 additions and 9 deletions

View file

@ -18,18 +18,19 @@ package loggers
import ( import (
"fmt" "fmt"
"io" "io"
"regexp"
"strings" "strings"
"sync" "sync"
"github.com/bep/logg" "github.com/bep/logg"
) )
// newNoColoursHandler creates a new NoColoursHandler // newNoAnsiEscapeHandler creates a new noAnsiEscapeHandler
func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler { func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noAnsiEscapeHandler {
if predicate == nil { if predicate == nil {
predicate = func(e *logg.Entry) bool { return true } predicate = func(e *logg.Entry) bool { return true }
} }
return &noColoursHandler{ return &noAnsiEscapeHandler{
noLevelPrefix: noLevelPrefix, noLevelPrefix: noLevelPrefix,
outWriter: outWriter, outWriter: outWriter,
errWriter: errWriter, errWriter: errWriter,
@ -37,7 +38,7 @@ func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, pre
} }
} }
type noColoursHandler struct { type noAnsiEscapeHandler struct {
mu sync.Mutex mu sync.Mutex
outWriter io.Writer // Defaults to os.Stdout. outWriter io.Writer // Defaults to os.Stdout.
errWriter io.Writer // Defaults to os.Stderr. errWriter io.Writer // Defaults to os.Stderr.
@ -45,7 +46,7 @@ type noColoursHandler struct {
noLevelPrefix bool noLevelPrefix bool
} }
func (h *noColoursHandler) HandleLog(e *logg.Entry) error { func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
if !h.predicate(e) { if !h.predicate(e) {
return nil return nil
} }
@ -71,10 +72,12 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
prefix = prefix + ": " prefix = prefix + ": "
} }
msg := stripANSI(e.Message)
if h.noLevelPrefix { if h.noLevelPrefix {
fmt.Fprintf(w, "%s%s", prefix, e.Message) fmt.Fprintf(w, "%s%s", prefix, msg)
} else { } else {
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message) fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, msg)
} }
for _, field := range e.Fields { for _, field := range e.Fields {
@ -88,3 +91,10 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
return nil return nil
} }
var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
// stripANSI removes ANSI escape codes from s.
func stripANSI(s string) string {
return ansiRe.ReplaceAllString(s, "")
}

View file

@ -0,0 +1,40 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// 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 loggers
import (
"bytes"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/terminal"
)
func TestNoAnsiEscapeHandler(t *testing.T) {
c := qt.New(t)
test := func(s string) {
c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
}
test(`error in "file.md:1:2"`)
var buf bytes.Buffer
h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
}

View file

@ -62,7 +62,7 @@ func New(opts Options) Logger {
if terminal.PrintANSIColors(os.Stdout) { if terminal.PrintANSIColors(os.Stdout) {
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr) logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
} else { } else {
logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil) logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil)
} }
errorsw := &strings.Builder{} errorsw := &strings.Builder{}
@ -95,7 +95,7 @@ func New(opts Options) Logger {
} }
if opts.StoreErrors { if opts.StoreErrors {
h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool { h := newNoAnsiEscapeHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
return e.Level >= logg.LevelError return e.Level >= logg.LevelError
}) })