Add StepRunnerStream writer that detects pre-stamped lines

What does this MR do?

Adds a StepRunnerStream writer to the build logger that detects whether step-runner output is already pre-stamped and routes it accordingly, preventing double-timestamping of log lines.

Problem

step-runner already emits log lines in the runner's timestamper wire format (RFC 3339-nano UTC timestamp prefix). When these pre-formatted lines flow through the build logger's normal wrap chain (sync → masker → URL sanitizer → token sanitizer → timestamper), the timestamper prepends a second timestamp, producing garbled output like:

2026-05-05T12:00:00.000000Z 2026-05-05T12:00:00.000000Z actual log line

Solution

Introduce StepRunnerStream(streamID, streamType) on Logger, which returns a stepRunnerStream writer. On its first write, the writer inspects byte 26 of the payload. The runner's timestamper always produces a fixed-width 28-byte header (YYYY-MM-DDTHH:MM:SS.nnnnnnZ ) where byte 26 is the trailing Z of the UTC timestamp. This single-byte check determines the routing for the lifetime of the stream:

  • Pre-stamped (p[26] == 'Z'): data passes through directly to the base trace via a synchronized writer, bypassing the full wrap chain (timestamper, masker, sanitizers). step-runner is expected to have already applied its own masking.
  • Not pre-stamped: data flows through the full wrap chain as usual, getting timestamped, masked, and sanitized.

The choice is made exactly once via sync.Once and persists for all subsequent writes on that stream. If Close() is called without any prior write, the wrapped writer is selected as a safe default.

Key design decisions

  • Single entry point: StepRunnerStream is the only hook for step-runner-specific stream behavior, keeping the surface area small and extensible.
  • Passthrough skips masking: pre-stamped output bypasses the masker intentionally. step-runner runs its own masking pipeline, so re-masking at the runner level is redundant.
  • Bounds-safe: payloads shorter than 27 bytes fall through to the wrapped writer, so the byte-26 check cannot panic on short writes.

Files changed

  • common/buildlogger/build_logger.go: adds StepRunnerStream method and the private stepRunnerStream type with Write/Close implementing the detection logic.
  • common/buildlogger/build_logger_step_runner_test.go (new): table of 7 test cases covering pre-stamped passthrough, plain data wrapping, masking behavior on both paths, short-write safety, choice persistence across writes, and close-without-write.

How to test

Run the new unit tests:

go test -race -run TestStepRunnerStream ./common/buildlogger/ -v -count=1

Why was this MR needed?

Eliminates double-timestamped log output when step-runner is used, ensuring clean, readable job logs. This is a prerequisite for routing step-runner output through the build logger without corrupting the log format.

Edited by Arran Walker

Merge request reports

Loading