Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions cmd/workflow/logs/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,20 +287,26 @@ func (h *handler) printErrors(ctx context.Context, client *graphqlclient.Client,

for _, ev := range resp.WorkflowExecutionEvents.Data {
if ev.Status == "failure" && len(ev.Errors) > 0 {
errMsg := ev.Errors[0].Error
if len(errMsg) > 120 {
tail := errMsg[len(errMsg)-len(errMsg)*2/5:] // last 40%
head := 120 - len(tail) - 3
if head < 0 {
head = 0
}
errMsg = errMsg[:head] + "..." + tail
}
errMsg := truncateError(ev.Errors[0].Error, 120)
fmt.Printf(" -> %s: %s\n", ev.CapabilityID, errMsg)
}
}
}

// truncateError shortens an error message to maxLen, preserving the head and
// the last 40% (the diagnostic tail). The middle is replaced with "...".
func truncateError(msg string, maxLen int) string {
if len(msg) <= maxLen {
return msg
}
tail := msg[len(msg)-len(msg)*2/5:] // last 40%
head := maxLen - len(tail) - 3 // 3 for "..."
if head < 0 {
head = 0
}
return msg[:head] + "..." + tail
}

func formatDuration(d time.Duration) string {
if d < time.Second {
return fmt.Sprintf("%dms", d.Milliseconds())
Expand Down
81 changes: 81 additions & 0 deletions cmd/workflow/logs/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,87 @@ func TestExecute(t *testing.T) {
})
}

func TestTruncateError(t *testing.T) {
t.Parallel()

t.Run("short message unchanged", func(t *testing.T) {
t.Parallel()
msg := "connection refused"
got := truncateError(msg, 120)
assert.Equal(t, msg, got)
})

t.Run("exactly at limit unchanged", func(t *testing.T) {
t.Parallel()
msg := strings.Repeat("x", 120)
got := truncateError(msg, 120)
assert.Equal(t, msg, got)
})

t.Run("head is preserved", func(t *testing.T) {
t.Parallel()
msg := "failed to execute enclave request. enclave ID: abc123, error: " +
strings.Repeat("m", 60) +
"attestation validation failed for ExecuteBatch: expected PCR0 deadbeef, got cafebabe"
got := truncateError(msg, 120)
assert.True(t, strings.HasPrefix(got, "failed to execute enclave"), "head should start with original prefix, got: %s", got)
})

t.Run("tail is preserved", func(t *testing.T) {
t.Parallel()
msg := "failed to execute enclave request. enclave ID: abc123, error: " +
strings.Repeat("m", 60) +
"attestation validation failed for ExecuteBatch: expected PCR0 deadbeef, got cafebabe"
got := truncateError(msg, 120)
assert.True(t, strings.HasSuffix(got, "expected PCR0 deadbeef, got cafebabe"), "tail should end with original suffix, got: %s", got)
})

t.Run("middle content is removed", func(t *testing.T) {
t.Parallel()
// Build a message well over 120 chars with a unique middle marker.
head := strings.Repeat("h", 60)
middle := "UNIQUE_MIDDLE_MARKER"
tail := strings.Repeat("t", 100)
msg := head + middle + tail // 260 chars total
got := truncateError(msg, 120)
assert.NotContains(t, got, "UNIQUE_MIDDLE_MARKER", "middle content should be elided, got: %s", got)
})

t.Run("ellipsis is present", func(t *testing.T) {
t.Parallel()
msg := strings.Repeat("a", 200)
got := truncateError(msg, 120)
assert.Contains(t, got, "...")
})

t.Run("result does not exceed maxLen", func(t *testing.T) {
t.Parallel()
msg := strings.Repeat("x", 500)
got := truncateError(msg, 120)
assert.LessOrEqual(t, len(got), 120+len(got[strings.Index(got, "...")+3:]),
"total length should be bounded")
// More direct check: head + "..." + tail where tail is last 40%
tail := msg[len(msg)-len(msg)*2/5:]
headLen := 120 - len(tail) - 3
if headLen < 0 {
headLen = 0
}
expected := msg[:headLen] + "..." + tail
assert.Equal(t, expected, got)
})

t.Run("tail is approximately 40 percent of original", func(t *testing.T) {
t.Parallel()
msg := strings.Repeat("x", 300)
got := truncateError(msg, 120)
parts := strings.SplitN(got, "...", 2)
require.Len(t, parts, 2, "should have head...tail")
tailLen := len(parts[1])
expectedTailLen := len(msg) * 2 / 5 // 40% of 300 = 120
assert.Equal(t, expectedTailLen, tailLen, "tail should be 40%% of original length")
})
}

// Test helpers

type mockConfig struct {
Expand Down