From ce9cf882a54e9c55dcc7970398ae79c3b251f79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 14 Nov 2024 09:43:37 +0100 Subject: [PATCH] server: Strip ANSI escape codes from browser error log Fixes #13037 --- common/loggers/handlerterminal.go | 24 +++++++++++----- common/loggers/handlerterminal_test.go | 40 ++++++++++++++++++++++++++ common/loggers/logger.go | 4 +-- 3 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 common/loggers/handlerterminal_test.go diff --git a/common/loggers/handlerterminal.go b/common/loggers/handlerterminal.go index 53f6e41da..c5f8fcce8 100644 --- a/common/loggers/handlerterminal.go +++ b/common/loggers/handlerterminal.go @@ -18,18 +18,19 @@ package loggers import ( "fmt" "io" + "regexp" "strings" "sync" "github.com/bep/logg" ) -// newNoColoursHandler creates a new NoColoursHandler -func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler { +// newNoAnsiEscapeHandler creates a new noAnsiEscapeHandler +func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noAnsiEscapeHandler { if predicate == nil { predicate = func(e *logg.Entry) bool { return true } } - return &noColoursHandler{ + return &noAnsiEscapeHandler{ noLevelPrefix: noLevelPrefix, outWriter: outWriter, errWriter: errWriter, @@ -37,7 +38,7 @@ func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, pre } } -type noColoursHandler struct { +type noAnsiEscapeHandler struct { mu sync.Mutex outWriter io.Writer // Defaults to os.Stdout. errWriter io.Writer // Defaults to os.Stderr. @@ -45,7 +46,7 @@ type noColoursHandler struct { noLevelPrefix bool } -func (h *noColoursHandler) HandleLog(e *logg.Entry) error { +func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error { if !h.predicate(e) { return nil } @@ -71,10 +72,12 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error { prefix = prefix + ": " } + msg := stripANSI(e.Message) + if h.noLevelPrefix { - fmt.Fprintf(w, "%s%s", prefix, e.Message) + fmt.Fprintf(w, "%s%s", prefix, msg) } 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 { @@ -88,3 +91,10 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error { 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, "") +} diff --git a/common/loggers/handlerterminal_test.go b/common/loggers/handlerterminal_test.go new file mode 100644 index 000000000..f45ce80df --- /dev/null +++ b/common/loggers/handlerterminal_test.go @@ -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") +} diff --git a/common/loggers/logger.go b/common/loggers/logger.go index 4e2f3ab21..75c8102c7 100644 --- a/common/loggers/logger.go +++ b/common/loggers/logger.go @@ -62,7 +62,7 @@ func New(opts Options) Logger { if terminal.PrintANSIColors(os.Stdout) { logHandler = newDefaultHandler(opts.Stdout, opts.Stderr) } else { - logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil) + logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil) } errorsw := &strings.Builder{} @@ -95,7 +95,7 @@ func New(opts Options) Logger { } 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 })