feat(builder): log build failure output as separate lines #3
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.2.2"
|
const version = "0.2.3"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := &cli.Command{
|
app := &cli.Command{
|
||||||
|
|||||||
@@ -19,6 +19,23 @@ import (
|
|||||||
// Allows: alphanumeric, dashes, underscores, dots.
|
// Allows: alphanumeric, dashes, underscores, dots.
|
||||||
var hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
var hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||||
|
|
||||||
|
// truncateOutputLines truncates output to the first and last N lines if it exceeds 2*N lines,
|
||||||
|
// returning the result as a slice of strings.
|
||||||
|
func truncateOutputLines(output string, keepLines int) []string {
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
if len(lines) <= keepLines*2 {
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
head := lines[:keepLines]
|
||||||
|
tail := lines[len(lines)-keepLines:]
|
||||||
|
omitted := len(lines) - keepLines*2
|
||||||
|
result := make([]string, 0, keepLines*2+1)
|
||||||
|
result = append(result, head...)
|
||||||
|
result = append(result, fmt.Sprintf("... (%d lines omitted) ...", omitted))
|
||||||
|
result = append(result, tail...)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// truncateOutput truncates output to the first and last N lines if it exceeds 2*N lines.
|
// truncateOutput truncates output to the first and last N lines if it exceeds 2*N lines.
|
||||||
func truncateOutput(output string, keepLines int) string {
|
func truncateOutput(output string, keepLines int) string {
|
||||||
lines := strings.Split(output, "\n")
|
lines := strings.Split(output, "\n")
|
||||||
@@ -256,6 +273,8 @@ func (b *Builder) handleBuildRequest(subject string, data []byte) {
|
|||||||
hostStart := time.Now()
|
hostStart := time.Now()
|
||||||
b.logger.Info("building host",
|
b.logger.Info("building host",
|
||||||
"host", host,
|
"host", host,
|
||||||
|
"repo", req.Repo,
|
||||||
|
"rev", branch,
|
||||||
"progress", fmt.Sprintf("%d/%d", i+1, len(hosts)),
|
"progress", fmt.Sprintf("%d/%d", i+1, len(hosts)),
|
||||||
"command", b.executor.BuildCommand(repo.URL, branch, host),
|
"command", b.executor.BuildCommand(repo.URL, branch, host),
|
||||||
)
|
)
|
||||||
@@ -280,13 +299,18 @@ func (b *Builder) handleBuildRequest(subject string, data []byte) {
|
|||||||
|
|
||||||
if result.Success {
|
if result.Success {
|
||||||
succeeded++
|
succeeded++
|
||||||
b.logger.Info("host build succeeded", "host", host, "duration", hostDuration)
|
b.logger.Info("host build succeeded", "host", host, "repo", req.Repo, "rev", branch, "duration", hostDuration)
|
||||||
if b.metrics != nil {
|
if b.metrics != nil {
|
||||||
b.metrics.RecordHostBuildSuccess(req.Repo, host, hostDuration)
|
b.metrics.RecordHostBuildSuccess(req.Repo, host, hostDuration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
failed++
|
failed++
|
||||||
b.logger.Error("host build failed", "host", host, "error", hostResult.Error, "output", hostResult.Output)
|
b.logger.Error("host build failed", "host", host, "repo", req.Repo, "rev", branch, "error", hostResult.Error)
|
||||||
|
if result.Stderr != "" {
|
||||||
|
for _, line := range truncateOutputLines(result.Stderr, 50) {
|
||||||
|
b.logger.Warn("build output", "host", host, "repo", req.Repo, "line", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
if b.metrics != nil {
|
if b.metrics != nil {
|
||||||
b.metrics.RecordHostBuildFailure(req.Repo, host, hostDuration)
|
b.metrics.RecordHostBuildFailure(req.Repo, host, hostDuration)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -83,6 +84,54 @@ func makeLines(n int) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTruncateOutputLines(t *testing.T) {
|
||||||
|
t.Run("short output returns all lines", func(t *testing.T) {
|
||||||
|
input := "line1\nline2\nline3"
|
||||||
|
got := truncateOutputLines(input, 50)
|
||||||
|
if len(got) != 3 {
|
||||||
|
t.Errorf("got %d lines, want 3", len(got))
|
||||||
|
}
|
||||||
|
if got[0] != "line1" || got[1] != "line2" || got[2] != "line3" {
|
||||||
|
t.Errorf("unexpected lines: %v", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("over threshold returns head + marker + tail", func(t *testing.T) {
|
||||||
|
lines := makeLines(200)
|
||||||
|
input := strings.Join(lines, "\n")
|
||||||
|
got := truncateOutputLines(input, 50)
|
||||||
|
|
||||||
|
// Should be 50 head + 1 marker + 50 tail = 101
|
||||||
|
if len(got) != 101 {
|
||||||
|
t.Errorf("got %d lines, want 101", len(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check first and last lines preserved
|
||||||
|
if got[0] != lines[0] {
|
||||||
|
t.Errorf("first line = %q, want %q", got[0], lines[0])
|
||||||
|
}
|
||||||
|
if got[len(got)-1] != lines[len(lines)-1] {
|
||||||
|
t.Errorf("last line = %q, want %q", got[len(got)-1], lines[len(lines)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check omitted marker
|
||||||
|
marker := got[50]
|
||||||
|
expected := fmt.Sprintf("... (%d lines omitted) ...", 100)
|
||||||
|
if marker != expected {
|
||||||
|
t.Errorf("marker = %q, want %q", marker, expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("exactly at threshold returns all lines", func(t *testing.T) {
|
||||||
|
lines := makeLines(100)
|
||||||
|
input := strings.Join(lines, "\n")
|
||||||
|
got := truncateOutputLines(input, 50)
|
||||||
|
if len(got) != 100 {
|
||||||
|
t.Errorf("got %d lines, want 100", len(got))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestTruncateOutputPreservesContent(t *testing.T) {
|
func TestTruncateOutputPreservesContent(t *testing.T) {
|
||||||
// Create input with distinct first and last lines
|
// Create input with distinct first and last lines
|
||||||
lines := make([]string, 200)
|
lines := make([]string, 200)
|
||||||
|
|||||||
Reference in New Issue
Block a user