Skip to content

Commit 70bc4cd

Browse files
Merge pull request #2034 from masters-of-cats/pr-child-logging
Support for logging from children processes
2 parents dae70e8 + a146081 commit 70bc4cd

38 files changed

+1423
-595
lines changed

exec.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ func execProcess(context *cli.Context) (int, error) {
136136
if err != nil {
137137
return -1, err
138138
}
139+
140+
logLevel := "info"
141+
if context.GlobalBool("debug") {
142+
logLevel = "debug"
143+
}
144+
139145
r := &runner{
140146
enableSubreaper: false,
141147
shouldDestroy: false,
@@ -146,6 +152,7 @@ func execProcess(context *cli.Context) (int, error) {
146152
action: CT_ACT_RUN,
147153
init: false,
148154
preserveFDs: context.Int("preserve-fds"),
155+
logLevel: logLevel,
149156
}
150157
return r.run(p)
151158
}

init.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
package main
22

33
import (
4+
"fmt"
45
"os"
56
"runtime"
67

78
"github.com/opencontainers/runc/libcontainer"
9+
"github.com/opencontainers/runc/libcontainer/logs"
810
_ "github.com/opencontainers/runc/libcontainer/nsenter"
11+
"github.com/sirupsen/logrus"
912
"github.com/urfave/cli"
1013
)
1114

1215
func init() {
1316
if len(os.Args) > 1 && os.Args[1] == "init" {
1417
runtime.GOMAXPROCS(1)
1518
runtime.LockOSThread()
19+
20+
level := os.Getenv("_LIBCONTAINER_LOGLEVEL")
21+
logLevel, err := logrus.ParseLevel(level)
22+
if err != nil {
23+
panic(fmt.Sprintf("libcontainer: failed to parse log level: %q: %v", level, err))
24+
}
25+
26+
err = logs.ConfigureLogging(logs.Config{
27+
LogPipeFd: os.Getenv("_LIBCONTAINER_LOGPIPE"),
28+
LogFormat: "json",
29+
LogLevel: logLevel,
30+
})
31+
if err != nil {
32+
panic(fmt.Sprintf("libcontainer: failed to configure logging: %v", err))
33+
}
34+
logrus.Debug("child process in init()")
1635
}
1736
}
1837

libcontainer/container_linux.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ func (c *linuxContainer) start(process *Process) error {
337337
if err != nil {
338338
return newSystemErrorWithCause(err, "creating new parent process")
339339
}
340+
parent.forwardChildLogs()
340341
if err := parent.start(); err != nil {
341342
// terminate the process to ensure that it properly is reaped.
342343
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
@@ -438,16 +439,24 @@ func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error {
438439
}
439440

440441
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
441-
parentPipe, childPipe, err := utils.NewSockPair("init")
442+
parentInitPipe, childInitPipe, err := utils.NewSockPair("init")
442443
if err != nil {
443444
return nil, newSystemErrorWithCause(err, "creating new init pipe")
444445
}
445-
cmd, err := c.commandTemplate(p, childPipe)
446+
messageSockPair := filePair{parentInitPipe, childInitPipe}
447+
448+
parentLogPipe, childLogPipe, err := os.Pipe()
449+
if err != nil {
450+
return nil, fmt.Errorf("Unable to create the log pipe: %s", err)
451+
}
452+
logFilePair := filePair{parentLogPipe, childLogPipe}
453+
454+
cmd, err := c.commandTemplate(p, childInitPipe, childLogPipe)
446455
if err != nil {
447456
return nil, newSystemErrorWithCause(err, "creating new command template")
448457
}
449458
if !p.Init {
450-
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
459+
return c.newSetnsProcess(p, cmd, messageSockPair, logFilePair)
451460
}
452461

453462
// We only set up fifoFd if we're not doing a `runc exec`. The historic
@@ -458,10 +467,10 @@ func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
458467
if err := c.includeExecFifo(cmd); err != nil {
459468
return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup")
460469
}
461-
return c.newInitProcess(p, cmd, parentPipe, childPipe)
470+
return c.newInitProcess(p, cmd, messageSockPair, logFilePair)
462471
}
463472

464-
func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) {
473+
func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) (*exec.Cmd, error) {
465474
cmd := exec.Command(c.initPath, c.initArgs[1:]...)
466475
cmd.Args[0] = c.initArgs[0]
467476
cmd.Stdin = p.Stdin
@@ -479,11 +488,18 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
479488
fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
480489
)
481490
}
482-
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
491+
cmd.ExtraFiles = append(cmd.ExtraFiles, childInitPipe)
483492
cmd.Env = append(cmd.Env,
484493
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
485494
fmt.Sprintf("_LIBCONTAINER_STATEDIR=%s", c.root),
486495
)
496+
497+
cmd.ExtraFiles = append(cmd.ExtraFiles, childLogPipe)
498+
cmd.Env = append(cmd.Env,
499+
fmt.Sprintf("_LIBCONTAINER_LOGPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
500+
fmt.Sprintf("_LIBCONTAINER_LOGLEVEL=%s", p.LogLevel),
501+
)
502+
487503
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
488504
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
489505
// even with the parent still running.
@@ -493,7 +509,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
493509
return cmd, nil
494510
}
495511

496-
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) {
512+
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
497513
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
498514
nsMaps := make(map[configs.NamespaceType]string)
499515
for _, ns := range c.config.Namespaces {
@@ -508,8 +524,8 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
508524
}
509525
init := &initProcess{
510526
cmd: cmd,
511-
childPipe: childPipe,
512-
parentPipe: parentPipe,
527+
messageSockPair: messageSockPair,
528+
logFilePair: logFilePair,
513529
manager: c.cgroupManager,
514530
intelRdtManager: c.intelRdtManager,
515531
config: c.newInitConfig(p),
@@ -522,7 +538,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
522538
return init, nil
523539
}
524540

525-
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) {
541+
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*setnsProcess, error) {
526542
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
527543
state, err := c.currentState()
528544
if err != nil {
@@ -539,8 +555,8 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
539555
cgroupPaths: c.cgroupManager.GetPaths(),
540556
rootlessCgroups: c.config.RootlessCgroups,
541557
intelRdtPath: state.IntelRdtPath,
542-
childPipe: childPipe,
543-
parentPipe: parentPipe,
558+
messageSockPair: messageSockPair,
559+
logFilePair: logFilePair,
544560
config: c.newInitConfig(p),
545561
process: p,
546562
bootstrapData: data,

libcontainer/container_linux_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ func (m *mockProcess) externalDescriptors() []string {
114114
func (m *mockProcess) setExternalDescriptors(newFds []string) {
115115
}
116116

117+
func (m *mockProcess) forwardChildLogs() {
118+
}
119+
117120
func TestGetContainerPids(t *testing.T) {
118121
container := &linuxContainer{
119122
id: "myid",

libcontainer/logs/logs.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package logs
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strconv"
10+
"sync"
11+
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
var (
16+
configureMutex = sync.Mutex{}
17+
// loggingConfigured will be set once logging has been configured via invoking `ConfigureLogging`.
18+
// Subsequent invocations of `ConfigureLogging` would be no-op
19+
loggingConfigured = false
20+
)
21+
22+
type Config struct {
23+
LogLevel logrus.Level
24+
LogFormat string
25+
LogFilePath string
26+
LogPipeFd string
27+
}
28+
29+
func ForwardLogs(logPipe io.Reader) {
30+
lineReader := bufio.NewReader(logPipe)
31+
for {
32+
line, err := lineReader.ReadBytes('\n')
33+
if len(line) > 0 {
34+
processEntry(line)
35+
}
36+
if err == io.EOF {
37+
logrus.Debugf("log pipe has been closed: %+v", err)
38+
return
39+
}
40+
if err != nil {
41+
logrus.Errorf("log pipe read error: %+v", err)
42+
}
43+
}
44+
}
45+
46+
func processEntry(text []byte) {
47+
type jsonLog struct {
48+
Level string `json:"level"`
49+
Msg string `json:"msg"`
50+
}
51+
52+
var jl jsonLog
53+
if err := json.Unmarshal(text, &jl); err != nil {
54+
logrus.Errorf("failed to decode %q to json: %+v", text, err)
55+
return
56+
}
57+
58+
lvl, err := logrus.ParseLevel(jl.Level)
59+
if err != nil {
60+
logrus.Errorf("failed to parse log level %q: %v\n", jl.Level, err)
61+
return
62+
}
63+
logrus.StandardLogger().Logf(lvl, jl.Msg)
64+
}
65+
66+
func ConfigureLogging(config Config) error {
67+
configureMutex.Lock()
68+
defer configureMutex.Unlock()
69+
70+
if loggingConfigured {
71+
logrus.Debug("logging has already been configured")
72+
return nil
73+
}
74+
75+
logrus.SetLevel(config.LogLevel)
76+
77+
if config.LogPipeFd != "" {
78+
logPipeFdInt, err := strconv.Atoi(config.LogPipeFd)
79+
if err != nil {
80+
return fmt.Errorf("failed to convert _LIBCONTAINER_LOGPIPE environment variable value %q to int: %v", config.LogPipeFd, err)
81+
}
82+
logrus.SetOutput(os.NewFile(uintptr(logPipeFdInt), "logpipe"))
83+
} else if config.LogFilePath != "" {
84+
f, err := os.OpenFile(config.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0644)
85+
if err != nil {
86+
return err
87+
}
88+
logrus.SetOutput(f)
89+
}
90+
91+
switch config.LogFormat {
92+
case "text":
93+
// retain logrus's default.
94+
case "json":
95+
logrus.SetFormatter(new(logrus.JSONFormatter))
96+
default:
97+
return fmt.Errorf("unknown log-format %q", config.LogFormat)
98+
}
99+
100+
loggingConfigured = true
101+
return nil
102+
}

0 commit comments

Comments
 (0)