Strip ANSI escapes from managed ComfyUI output in the log; spawn child with NO_COLOR/TERM=dumb
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -185,6 +186,9 @@ func (p *Process) EnsureRunning() error {
|
|||||||
p.external = false
|
p.external = false
|
||||||
cmd := exec.Command(p.argv[0], p.argv[1:]...)
|
cmd := exec.Command(p.argv[0], p.argv[1:]...)
|
||||||
cmd.Dir = p.dir
|
cmd.Dir = p.dir
|
||||||
|
// Ask the child not to colorize (ComfyUI ignores this and colors
|
||||||
|
// anyway, so pipeLog also strips escape sequences).
|
||||||
|
cmd.Env = append(os.Environ(), "NO_COLOR=1", "TERM=dumb")
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -278,7 +282,9 @@ func (p *Process) WatchIdle(ctx context.Context, idleTimeout time.Duration, gpuI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pipeLog forwards one child output stream to the log at INFO, line by
|
// pipeLog forwards one child output stream to the log at INFO, line by
|
||||||
// line, prefixed with the process name.
|
// line, prefixed with the process name. ANSI escape sequences are
|
||||||
|
// stripped: ComfyUI colorizes unconditionally, and the escapes only
|
||||||
|
// render as garbage in a log file.
|
||||||
func (p *Process) pipeLog(r io.Reader) {
|
func (p *Process) pipeLog(r io.Reader) {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
var line string
|
var line string
|
||||||
@@ -290,18 +296,25 @@ func (p *Process) pipeLog(r io.Reader) {
|
|||||||
if i < 0 {
|
if i < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
p.log.Info(p.name + ": " + strings.TrimRight(line[:i], "\r"))
|
p.log.Info(p.name + ": " + stripANSI(strings.TrimRight(line[:i], "\r")))
|
||||||
line = line[i+1:]
|
line = line[i+1:]
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.TrimSpace(line) != "" {
|
if strings.TrimSpace(line) != "" {
|
||||||
p.log.Info(p.name + ": " + line)
|
p.log.Info(p.name + ": " + stripANSI(line))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ansiPattern matches CSI escape sequences (colors, cursor moves, …).
|
||||||
|
var ansiPattern = regexp.MustCompile("\x1b\\[[0-9;?]*[a-zA-Z]")
|
||||||
|
|
||||||
|
func stripANSI(s string) string {
|
||||||
|
return ansiPattern.ReplaceAllString(s, "")
|
||||||
|
}
|
||||||
|
|
||||||
// stopTree kills cmd's process, including its children on Windows (python
|
// stopTree kills cmd's process, including its children on Windows (python
|
||||||
// launchers tend to spawn some). The Wait goroutine reaps it.
|
// launchers tend to spawn some). The Wait goroutine reaps it.
|
||||||
func stopTree(cmd *exec.Cmd) {
|
func stopTree(cmd *exec.Cmd) {
|
||||||
|
|||||||
@@ -239,3 +239,17 @@ func TestComfyLayoutAndDefaultCommand(t *testing.T) {
|
|||||||
t.Errorf("missing-layout script = %s", script)
|
t.Errorf("missing-layout script = %s", script)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStripANSI(t *testing.T) {
|
||||||
|
cases := map[string]string{
|
||||||
|
"\x1b[32m[INFO]\x1b[0m Starting server": "[INFO] Starting server",
|
||||||
|
"\x1b[1m\x1b[31m[ERROR]\x1b[0m boom": "[ERROR] boom",
|
||||||
|
"plain line": "plain line",
|
||||||
|
"\x1b[33mWARN\x1b[0m: \x1b[1mbold\x1b[0m": "WARN: bold",
|
||||||
|
}
|
||||||
|
for in, want := range cases {
|
||||||
|
if got := stripANSI(in); got != want {
|
||||||
|
t.Errorf("stripANSI(%q) = %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user