Skip to content

Commit 9b371fb

Browse files
committed
feat(telemetry): attach failed task's output tail as gitte.error_tail span attribute
1 parent e3fbae7 commit 9b371fb

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

actions/features_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package actions
22

33
import (
44
"reflect"
5+
"strings"
56
"testing"
67

78
"github.com/cego/gitte/config"
@@ -46,3 +47,23 @@ func TestInjectedEnv(t *testing.T) {
4647
t.Fatalf("injectedEnv = %v, want PROJ_VAR=x and FEAT_VAR=1", got)
4748
}
4849
}
50+
51+
func TestOutputTail(t *testing.T) {
52+
// stderr preferred when non-empty
53+
if got := outputTail([]byte("the real error"), []byte("noise")); got != "the real error" {
54+
t.Fatalf("stderr-preferred: got %q", got)
55+
}
56+
// falls back to stdout when stderr is blank (e.g. gitlab-ci-local prints to stdout)
57+
if got := outputTail([]byte(" \n"), []byte("stdout failure")); got != "stdout failure" {
58+
t.Fatalf("stdout-fallback: got %q", got)
59+
}
60+
// capped to the last errorTailBytes
61+
big := []byte(strings.Repeat("x", errorTailBytes+500))
62+
if got := outputTail(big, nil); len(got) != errorTailBytes {
63+
t.Fatalf("cap: len=%d, want %d", len(got), errorTailBytes)
64+
}
65+
// empty when no output
66+
if got := outputTail(nil, nil); got != "" {
67+
t.Fatalf("empty: got %q", got)
68+
}
69+
}

actions/runner.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,36 @@ func runGroupTask(
347347
span.SetAttributes(attribute.Int("gitte.exit_code", res.ExitCode))
348348

349349
if res.ExitCode != 0 {
350+
if tail := outputTail(res.Stderr, res.Stdout); tail != "" {
351+
span.SetAttributes(attribute.String("gitte.error_tail", tail))
352+
}
350353
return fmt.Errorf("command exited with code %d", res.ExitCode)
351354
}
352355

353356
return nil
354357
}
355358

359+
// errorTailBytes caps how much trailing command output is attached to a failed
360+
// task span via the gitte.error_tail attribute.
361+
const errorTailBytes = 4096
362+
363+
// outputTail returns the last errorTailBytes of a failed command's output for
364+
// the gitte.error_tail span attribute — stderr preferred, falling back to
365+
// stdout (tools like gitlab-ci-local print their failures to stdout).
366+
func outputTail(stderr, stdout []byte) string {
367+
if tail := tailString(stderr); tail != "" {
368+
return tail
369+
}
370+
return tailString(stdout)
371+
}
372+
373+
func tailString(b []byte) string {
374+
if len(b) > errorTailBytes {
375+
b = b[len(b)-errorTailBytes:]
376+
}
377+
return strings.TrimSpace(string(b))
378+
}
379+
356380
// emitTaskPreamble writes a short header to the task log showing the working
357381
// directory, command, and any env vars injected by gitte (project env, env_when,
358382
// feature gates). It is emitted before the command starts so the log is

0 commit comments

Comments
 (0)