diff --git a/internal/api/v1/utils/error.go b/internal/api/v1/utils/error.go
index 0b88689..574193e 100644
--- a/internal/api/v1/utils/error.go
+++ b/internal/api/v1/utils/error.go
@@ -4,6 +4,7 @@ import (
"net/http"
E "github.com/yusing/go-proxy/internal/error"
+ "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/utils/strutils/ansi"
)
@@ -30,8 +31,16 @@ func RespondError(w http.ResponseWriter, err error, code ...int) {
if len(code) == 0 {
code = []int{http.StatusBadRequest}
}
- // strip ANSI color codes added from Error.WithSubject
- http.Error(w, ansi.StripANSI(err.Error()), code[0])
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ buf := make([]byte, 0, 100)
+ errMsg := err.Error()
+ buf, err = logging.FormatMessageToHTMLBytes(errMsg, buf)
+ if err != nil {
+ http.Error(w, ansi.StripANSI(errMsg), code[0])
+ return
+ }
+ w.WriteHeader(code[0])
+ _, _ = w.Write(buf)
}
func ErrMissingKey(k string) error {
diff --git a/internal/logging/html.go b/internal/logging/html.go
index 3ad69dc..e8b70b4 100644
--- a/internal/logging/html.go
+++ b/internal/logging/html.go
@@ -1,99 +1,14 @@
package logging
import (
- "bytes"
+ "errors"
+ "fmt"
"time"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
- ansiPkg "github.com/yusing/go-proxy/internal/utils/strutils/ansi"
)
-func fmtMessageToHTMLBytes(msg string, buf []byte) []byte {
- buf = append(buf, []byte(`TRC `),
[]byte(` DBG `),
@@ -104,12 +19,124 @@ var levelHTMLFormats = [][]byte{
[]byte(` PAN `),
}
-var symbolMapping = map[rune][]byte{
- '•': []byte("·"),
- '>': []byte(">"),
- '<': []byte("<"),
- '\t': []byte(" "),
- '\n': []byte("
"),
+var colorToClass = map[string]string{
+ "1": "log-bold",
+ "3": "log-italic",
+ "4": "log-underline",
+ "30": "log-black",
+ "31": "log-red",
+ "32": "log-green",
+ "33": "log-yellow",
+ "34": "log-blue",
+ "35": "log-magenta",
+ "36": "log-cyan",
+ "37": "log-white",
+ "90": "log-bright-black",
+ "91": "log-red",
+ "92": "log-bright-green",
+ "93": "log-bright-yellow",
+ "94": "log-bright-blue",
+ "95": "log-bright-magenta",
+ "96": "log-bright-cyan",
+ "97": "log-bright-white",
+}
+
+// FormatMessageToHTMLBytes converts text with ANSI color codes to HTML with class names.
+// ANSI codes are mapped to classes via a static map, and reset codes ([0m) close all spans.
+// Time complexity is O(n) with minimal allocations.
+func FormatMessageToHTMLBytes(msg string, buf []byte) ([]byte, error) {
+ buf = append(buf, " "...)
+ }
+ stack = stack[:0]
+ } else {
+ className, ok := colorToClass[part]
+ if !ok {
+ return nil, fmt.Errorf("invalid ANSI code: %s", part)
+ }
+ stack = append(stack, className)
+ buf = append(buf, ``...)
+ }
+ startPart = j + 1
+ }
+ }
+ } else {
+ i++
+ }
+ }
+
+ if lastPos < len(msg) {
+ escapeAndAppend(msg[lastPos:], &buf)
+ }
+
+ for range stack {
+ buf = append(buf, ""...)
+ }
+
+ buf = append(buf, ""...)
+ return buf, nil
+}
+
+func isANSICodeChar(c byte) bool {
+ return (c >= '0' && c <= '9') || c == ';'
+}
+
+func escapeAndAppend(s string, buf *[]byte) {
+ for i, r := range s {
+ switch r {
+ case '•':
+ *buf = append(*buf, "·"...)
+ case '&':
+ *buf = append(*buf, "&"...)
+ case '<':
+ *buf = append(*buf, "<"...)
+ case '>':
+ *buf = append(*buf, ">"...)
+ case '\t':
+ *buf = append(*buf, " "...)
+ case '\n':
+ *buf = append(*buf, "
"...)
+ default:
+ *buf = append(*buf, s[i])
+ }
+ }
}
func timeNowHTML() []byte {
@@ -125,7 +152,7 @@ func FormatLogEntryHTML(level zerolog.Level, message string, buf []byte) []byte
if level < zerolog.NoLevel {
buf = append(buf, levelHTMLFormats[level+1]...)
}
- buf = fmtMessageToHTMLBytes(message, buf)
+ buf, _ = FormatMessageToHTMLBytes(message, buf)
buf = append(buf, []byte("")...)
return buf
}
diff --git a/internal/logging/html_test.go b/internal/logging/html_test.go
index 8f67164..9c6b578 100644
--- a/internal/logging/html_test.go
+++ b/internal/logging/html_test.go
@@ -16,14 +16,14 @@ func TestFormatHTML(t *testing.T) {
func TestFormatHTMLANSI(t *testing.T) {
buf := make([]byte, 0, 100)
buf = FormatLogEntryHTML(zerolog.InfoLevel, "This is \x1b[91m\x1b[1ma test.\x1b[0mOK!.", buf)
- ExpectEqual(t, string(buf), `
01-01 01:01 INF`) + ExpectEqual(t, string(buf), `
01-01 01:01 INF`) buf = buf[:0] buf = FormatLogEntryHTML(zerolog.InfoLevel, "This is \x1b[91ma \x1b[1mtest.\x1b[0mOK!.", buf) - ExpectEqual(t, string(buf), `
01-01 01:01 INF`) + ExpectEqual(t, string(buf), `
01-01 01:01 INF`) } func BenchmarkFormatLogEntryHTML(b *testing.B) { - buf := make([]byte, 0, 100) + buf := make([]byte, 0, 250) for range b.N { FormatLogEntryHTML(zerolog.InfoLevel, "This is \x1b[91ma \x1b[1mtest.\x1b[0mOK!.", buf) }