builder/build/cnb/phase_logger.go
Rama main · réplica de lectura del repositorio original.
package cnb
import (
"bytes"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/goodrain/rainbond/event"
)
var cnbANSISequence = regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~]`)
// cnbPhaseWriter preserves build output and reports observed lifecycle phase
// durations. The final phase includes the wait for job and log completion;
// parallel app/cache export is reported as one EXPORTING phase.
type cnbPhaseWriter struct {
mu sync.Mutex
writer io.Writer
logger event.Logger
now func() time.Time
pending []byte
phase string
started time.Time
finished bool
}
func newCNBPhaseWriter(writer io.Writer, logger event.Logger) *cnbPhaseWriter {
return &cnbPhaseWriter{writer: writer, logger: logger, now: time.Now}
}
func (w *cnbPhaseWriter) Write(data []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
n, err := w.writer.Write(data)
if !w.finished {
w.pending = append(w.pending, data[:n]...)
for {
end := bytes.IndexByte(w.pending, '\n')
if end < 0 {
break
}
w.observeLine(string(w.pending[:end]))
w.pending = w.pending[end+1:]
}
if len(w.pending) == 0 {
w.pending = nil
}
}
return n, err
}
func (w *cnbPhaseWriter) observeLine(line string) {
var phase string
// Lifecycle colors phase markers even in captured logs. Normalize only the
// detection text; the original output, including its colors, is preserved.
switch strings.TrimSpace(cnbANSISequence.ReplaceAllString(line, "")) {
case "===> ANALYZING":
phase = "ANALYZING"
case "===> DETECTING":
phase = "DETECTING"
case "===> RESTORING":
phase = "RESTORING"
case "===> BUILDING":
phase = "BUILDING"
case "===> EXPORTING":
phase = "EXPORTING"
default:
return
}
if phase == w.phase {
return
}
now := w.now()
w.report(now)
w.phase, w.started = phase, now
}
func (w *cnbPhaseWriter) report(now time.Time) {
if w.phase == "" {
return
}
elapsed := now.Sub(w.started).Round(time.Millisecond)
w.logger.Info(fmt.Sprintf("CNB phase %s observed elapsed: %s", w.phase, elapsed),
map[string]string{
"step": "build-timing",
"phase": w.phase,
"duration_ms": strconv.FormatInt(elapsed.Milliseconds(), 10),
})
}
func (w *cnbPhaseWriter) finish() {
w.mu.Lock()
defer w.mu.Unlock()
if w.finished {
return
}
if len(w.pending) > 0 {
w.observeLine(string(w.pending))
w.pending = nil
}
w.report(w.now())
w.finished = true
}