strip ansi escape sequences from docker logs (#1478)

This commit is contained in:
henrygd
2025-11-30 14:36:00 -05:00
parent 86e8a141ea
commit 394c476f2a
2 changed files with 68 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -24,6 +25,10 @@ import (
"github.com/blang/semver" "github.com/blang/semver"
) )
// ansiEscapePattern matches ANSI escape sequences (colors, cursor movement, etc.)
// This includes CSI sequences like \x1b[...m and simple escapes like \x1b[K
var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[@-Z\\-_]`)
const ( const (
// Docker API timeout in milliseconds // Docker API timeout in milliseconds
dockerTimeoutMs = 2100 dockerTimeoutMs = 2100
@@ -692,7 +697,12 @@ func (dm *dockerManager) getLogs(ctx context.Context, containerID string) (strin
return "", err return "", err
} }
return builder.String(), nil // Strip ANSI escape sequences from logs for clean display in web UI
logs := builder.String()
if strings.Contains(logs, "\x1b") {
logs = ansiEscapePattern.ReplaceAllString(logs, "")
}
return logs, nil
} }
func decodeDockerLogStream(reader io.Reader, builder *strings.Builder) error { func decodeDockerLogStream(reader io.Reader, builder *strings.Builder) error {

View File

@@ -1203,3 +1203,60 @@ func TestShouldExcludeContainer(t *testing.T) {
}) })
} }
} }
func TestAnsiEscapePattern(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "no ANSI codes",
input: "Hello, World!",
expected: "Hello, World!",
},
{
name: "simple color code",
input: "\x1b[34mINFO\x1b[0m client mode",
expected: "INFO client mode",
},
{
name: "multiple color codes",
input: "\x1b[31mERROR\x1b[0m: \x1b[33mWarning\x1b[0m message",
expected: "ERROR: Warning message",
},
{
name: "bold and color",
input: "\x1b[1;32mSUCCESS\x1b[0m",
expected: "SUCCESS",
},
{
name: "cursor movement codes",
input: "Line 1\x1b[KLine 2",
expected: "Line 1Line 2",
},
{
name: "256 color code",
input: "\x1b[38;5;196mRed text\x1b[0m",
expected: "Red text",
},
{
name: "RGB/truecolor code",
input: "\x1b[38;2;255;0;0mRed text\x1b[0m",
expected: "Red text",
},
{
name: "mixed content with newlines",
input: "\x1b[34m2024-01-01 12:00:00\x1b[0m INFO Starting\n\x1b[31m2024-01-01 12:00:01\x1b[0m ERROR Failed",
expected: "2024-01-01 12:00:00 INFO Starting\n2024-01-01 12:00:01 ERROR Failed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ansiEscapePattern.ReplaceAllString(tt.input, "")
assert.Equal(t, tt.expected, result)
})
}
}