package logging import ( "errors" "io" "os" "path/filepath" "code.icb4dc0.de/buildr/buildr/internal/ioutils" ) type OutputSource string const ( OutputSourceStdout OutputSource = "stdout" OutputSourceStderr OutputSource = "stderr" ) type LogFileNameFormatter interface { LogFileName(src OutputSource) string } type LogFileNameFormatterFunc func(src OutputSource) string func (f LogFileNameFormatterFunc) LogFileName(src OutputSource) string { return f(src) } func NewTaskOutputSink(logsDirectory string, logToStdErr bool, formatter LogFileNameFormatter) (sink TaskOutputSink, err error) { fileSink := fileOutputSink{ logToStdErr: logToStdErr, } if logToStdErr { if fileSink.stdOut, err = os.CreateTemp(os.TempDir(), "buildr-*.stdout.log"); err != nil { return nil, err } if fileSink.stdErr, err = os.CreateTemp(os.TempDir(), "buildr-*.stderr.log"); err != nil { return nil, err } } else { if fileSink.stdOut, err = os.Create(filepath.Join(logsDirectory, formatter.LogFileName(OutputSourceStdout))); err != nil { return nil, err } if fileSink.stdErr, err = os.Create(filepath.Join(logsDirectory, formatter.LogFileName(OutputSourceStderr))); err != nil { return nil, err } } return fileSink, nil } var _ TaskOutputSink = (*fileOutputSink)(nil) type file interface { Name() string io.ReadWriter io.Seeker io.Closer } type fileOutputSink struct { stdOut file stdErr file logToStdErr bool } func (f fileOutputSink) StdOut() io.Writer { return f.stdOut } func (f fileOutputSink) StdErr() io.Writer { return f.stdErr } func (f fileOutputSink) Close() error { if f.logToStdErr { return errors.Join( f.copyToStdErrAndClose(f.stdOut), f.copyToStdErrAndClose(f.stdErr), ) } return errors.Join( f.stdOut.Close(), f.stdErr.Close(), ) } func (f fileOutputSink) copyToStdErrAndClose(src file) error { return AcquireStdErr(func(stdErr io.Writer) error { if _, err := src.Seek(0, 0); err != nil { return err } if _, err := ioutils.CopyWithPooledBuffer(stdErr, src); err != nil { return err } return errors.Join(src.Close(), os.Remove(src.Name())) }) }