mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 02:36:17 +01:00
strip ansi escape sequences from docker logs (#1478)
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user