-
Notifications
You must be signed in to change notification settings - Fork 300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add option to timestamp lines #430
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"sync" | ||
"time" | ||
|
||
|
@@ -59,13 +60,20 @@ func (r JobRunner) Create() (runner *JobRunner, err error) { | |
// the Buildkite Agent API | ||
runner.logStreamer = LogStreamer{MaxChunkSizeBytes: r.Job.ChunksMaxSizeBytes, Callback: r.onUploadChunk}.New() | ||
|
||
timestamp, err := strconv.ParseBool(os.Getenv("BUILDKITE_TIMESTAMP_LINES")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should check this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I can move it there! |
||
if err != nil { | ||
timestamp = false | ||
} | ||
|
||
// The process that will run the bootstrap script | ||
runner.process = process.Process{ | ||
Script: r.AgentConfiguration.BootstrapScript, | ||
Env: r.createEnvironment(), | ||
PTY: r.AgentConfiguration.RunInPty, | ||
StartCallback: r.onProcessStartCallback, | ||
LineCallback: runner.headerTimesStreamer.Scan, | ||
Script: r.AgentConfiguration.BootstrapScript, | ||
Env: r.createEnvironment(), | ||
PTY: r.AgentConfiguration.RunInPty, | ||
Timestamp: timestamp, | ||
StartCallback: r.onProcessStartCallback, | ||
LineCallback: runner.headerTimesStreamer.Scan, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohhh right right, I see how this works now. Took a while for it to click :) So essentially, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's exactly the idea! The ugly bit of coupling is that lines are not timestamped if there is any callback handler. However, the only callback handler that exists right now is used for the header timestamps. |
||
LineCallbackFilter: runner.headerTimesStreamer.LineIsHeader, | ||
}.Create() | ||
|
||
return | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ import ( | |
type Process struct { | ||
Pid int | ||
PTY bool | ||
Timestamp bool | ||
Script string | ||
Env []string | ||
ExitStatus string | ||
|
@@ -35,8 +36,9 @@ type Process struct { | |
StartCallback func() | ||
|
||
// For every line in the process output, this callback will be called | ||
// with the contents of the line | ||
LineCallback func(string) | ||
// with the contents of the line if its filter returns true. | ||
LineCallback func(string) | ||
LineCallbackFilter func(string) bool | ||
|
||
// Running is stored as an int32 so we can use atomic operations to | ||
// set/get it (it's accessed by multiple goroutines) | ||
|
@@ -67,7 +69,12 @@ func (p *Process) Start() error { | |
|
||
lineReaderPipe, lineWriterPipe := io.Pipe() | ||
|
||
multiWriter := io.MultiWriter(&p.buffer, lineWriterPipe) | ||
var multiWriter io.Writer | ||
if p.Timestamp { | ||
multiWriter = io.MultiWriter(lineWriterPipe) | ||
} else { | ||
multiWriter = io.MultiWriter(&p.buffer, lineWriterPipe) | ||
} | ||
|
||
logger.Info("Starting to run script: %s", p.command.Path) | ||
|
||
|
@@ -171,11 +178,37 @@ func (p *Process) Start() error { | |
} | ||
} | ||
|
||
lineCallbackWaitGroup.Add(1) | ||
go func(line string) { | ||
defer lineCallbackWaitGroup.Done() | ||
p.LineCallback(line) | ||
}(string(line)) | ||
// If we're timestamping this main thread will take | ||
// the hit of running the regex so we can build up | ||
// the timestamped buffer without breaking headers, | ||
// otherwise we let the goroutines take the perf hit. | ||
|
||
checkedForCallback := false | ||
lineHasCallback := false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bit here has me a little confused. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that variable is not strictly needed but I left it for clarity. The idea was to use a short circuit evaluation to prevent doing the check twice in case the timestamping option is enabled: https://github.com/buildkite/agent/pull/430/files#diff-14ee62401fee1966227df2f4ab955c3cR207 I admit it's not super elegant. |
||
lineString := string(line) | ||
|
||
// Create the prefixed buffer | ||
if p.Timestamp { | ||
lineHasCallback = p.LineCallbackFilter(lineString) | ||
checkedForCallback = true | ||
if lineHasCallback { | ||
// Don't timestamp special lines (e.g. header) | ||
p.buffer.WriteString(fmt.Sprintf("%s\n", line)) | ||
} else { | ||
currentTime := time.Now().UTC().Format(time.RFC3339) | ||
p.buffer.WriteString(fmt.Sprintf("[%s] %s\n", currentTime, line)) | ||
} | ||
} | ||
|
||
if lineHasCallback || !checkedForCallback { | ||
lineCallbackWaitGroup.Add(1) | ||
go func(line string) { | ||
defer lineCallbackWaitGroup.Done() | ||
if (checkedForCallback && lineHasCallback) || p.LineCallbackFilter(lineString) { | ||
p.LineCallback(line) | ||
} | ||
}(lineString) | ||
} | ||
} | ||
|
||
// We need to make sure all the line callbacks have finish before | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably missing something, but I think this should still be there? Otherwise each line is probably considered a
---header
right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to move that check out of
header_times_streamer.go
, it introduces some more coupling but I didn't want the check to happen twice. Unfortunately I have to know if something is a header before I can timestamp it, so it's done here now:https://github.com/buildkite/agent/pull/430/files#diff-14ee62401fee1966227df2f4ab955c3cR192