podman logs honor stderr correctly

Make the ContainerLogsOptions support two io.Writers,
one for stdout and the other for stderr. The logline already
includes the information to which Writer it has to be written.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger
2020-12-10 17:39:57 +01:00
parent 2bb149034b
commit ba545c49a2
8 changed files with 58 additions and 14 deletions

View File

@ -122,6 +122,7 @@ func logs(_ *cobra.Command, args []string) error {
}
logsOptions.Since = since
}
logsOptions.Writer = os.Stdout
logsOptions.StdoutWriter = os.Stdout
logsOptions.StderrWriter = os.Stderr
return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions)
}

View File

@ -210,3 +210,19 @@ func NewLogLine(line string) (*LogLine, error) {
func (l *LogLine) Partial() bool {
return l.ParseLogType == PartialLogType
}
func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) {
switch l.Device {
case "stdout":
if stdout != nil {
fmt.Fprintln(stdout, l.String(logOpts))
}
case "stderr":
if stderr != nil {
fmt.Fprintln(stderr, l.String(logOpts))
}
default:
// Warn the user if the device type does not match. Most likely the file is corrupted.
logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID)
}
}

View File

@ -227,8 +227,10 @@ type ContainerLogsOptions struct {
Tail int64
// Show timestamps in the logs.
Timestamps bool
// Write the logs to Writer.
Writer io.Writer
// Write the stdout to this Writer.
StdoutWriter io.Writer
// Write the stderr to this Writer.
StderrWriter io.Writer
}
// ExecOptions describes the cli values to exec into

View File

@ -925,7 +925,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
}
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
if options.Writer == nil {
if options.StdoutWriter == nil && options.StderrWriter == nil {
return errors.New("no io.Writer set for container logs")
}
@ -963,7 +963,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
}()
for line := range logChannel {
fmt.Fprintln(options.Writer, line.String(logOpts))
line.Write(options.StdoutWriter, options.StderrWriter, logOpts)
}
return nil

View File

@ -360,11 +360,12 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, options entities.ContainerLogsOptions) error {
since := options.Since.Format(time.RFC3339)
tail := strconv.FormatInt(options.Tail, 10)
stdout := options.Writer != nil
stdout := options.StdoutWriter != nil
stderr := options.StderrWriter != nil
opts := containers.LogOptions{
Follow: &options.Follow,
Since: &since,
Stderr: &stdout,
Stderr: &stderr,
Stdout: &stdout,
Tail: &tail,
Timestamps: &options.Timestamps,
@ -372,10 +373,11 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
}
var err error
outCh := make(chan string)
stdoutCh := make(chan string)
stderrCh := make(chan string)
ctx, cancel := context.WithCancel(context.Background())
go func() {
err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, outCh, outCh)
err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, stdoutCh, stderrCh)
cancel()
}()
@ -383,8 +385,14 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
select {
case <-ctx.Done():
return err
case line := <-outCh:
_, _ = io.WriteString(options.Writer, line+"\n")
case line := <-stdoutCh:
if options.StdoutWriter != nil {
_, _ = io.WriteString(options.StdoutWriter, line+"\n")
}
case line := <-stderrCh:
if options.StderrWriter != nil {
_, _ = io.WriteString(options.StderrWriter, line+"\n")
}
}
}
}

View File

@ -355,4 +355,21 @@ var _ = Describe("Podman logs", func() {
Expect(outlines[0]).To(Equal("1\r"))
Expect(outlines[1]).To(Equal("2\r"))
})
It("podman logs test stdout and stderr", func() {
cname := "log-test"
logc := podmanTest.Podman([]string{"run", "--name", cname, ALPINE, "sh", "-c", "echo stdout; echo stderr >&2"})
logc.WaitWithDefaultTimeout()
Expect(logc).To(Exit(0))
wait := podmanTest.Podman([]string{"wait", cname})
wait.WaitWithDefaultTimeout()
Expect(wait).To(Exit(0))
results := podmanTest.Podman([]string{"logs", cname})
results.WaitWithDefaultTimeout()
Expect(results).To(Exit(0))
Expect(results.OutputToString()).To(Equal("stdout"))
Expect(results.ErrorToString()).To(Equal("stderr"))
})
})

View File

@ -1072,7 +1072,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube seccomp pod level", func() {
@ -1099,7 +1099,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube with pull policy of never should be 125", func() {

View File

@ -239,7 +239,7 @@ var _ = Describe("Toolbox-specific testing", func() {
session = podmanTest.Podman([]string{"logs", "test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(expectedOutput))
Expect(session.ErrorToString()).To(ContainSubstring(expectedOutput))
})
It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() {