Skip to content

Commit 1fdc31f

Browse files
danail-branekovgeorgethebeatlegcapizziCallisto13
committed
Address comments in PR 1861
Refactor configuring logging into a reusable component so that it can be nicely used in both main() and init process init() Co-authored-by: Georgi Sabev <[email protected]> Co-authored-by: Giuseppe Capizzi <[email protected]> Co-authored-by: Claudia Beresford <[email protected]>
1 parent e4ca720 commit 1fdc31f

File tree

11 files changed

+381
-134
lines changed

11 files changed

+381
-134
lines changed

init.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
package main
22

33
import (
4-
"os"
5-
"runtime"
6-
"strconv"
7-
4+
"fmt"
85
"github.com/opencontainers/runc/libcontainer"
6+
"github.com/opencontainers/runc/libcontainer/logs"
97
_ "github.com/opencontainers/runc/libcontainer/nsenter"
108
"github.com/sirupsen/logrus"
119
"github.com/urfave/cli"
10+
"os"
11+
"runtime"
1212
)
1313

1414
func init() {
1515
if len(os.Args) > 1 && os.Args[1] == "init" {
1616
runtime.GOMAXPROCS(1)
1717
runtime.LockOSThread()
1818

19-
// in child process, we need to retrieve the log pipe
20-
envLogPipe := os.Getenv("_LIBCONTAINER_LOGPIPE")
21-
logPipeFd, err := strconv.Atoi(envLogPipe)
22-
19+
err := logs.ConfigureLogging(&logs.LoggingConfiguration{
20+
LogPipeFd: os.Getenv("_LIBCONTAINER_LOGPIPE"),
21+
LogFormat: "json",
22+
IsDebug: true,
23+
})
2324
if err != nil {
24-
return
25+
panic(fmt.Sprintf("libcontainer: failed to configure logging: %v", err))
2526
}
26-
logPipe := os.NewFile(uintptr(logPipeFd), "logpipe")
27-
logrus.SetOutput(logPipe)
28-
logrus.SetFormatter(&logrus.JSONFormatter{})
29-
logrus.SetLevel(logrus.DebugLevel)
3027
logrus.Debug("child process in init()")
3128
}
3229
}

libcontainer/container_linux.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"syscall" // only for SysProcAttr and Signal
2020
"time"
2121

22-
"github.com/cyphar/filepath-securejoin"
22+
securejoin "github.com/cyphar/filepath-securejoin"
2323
"github.com/opencontainers/runc/libcontainer/cgroups"
2424
"github.com/opencontainers/runc/libcontainer/configs"
2525
"github.com/opencontainers/runc/libcontainer/intelrdt"
@@ -337,7 +337,7 @@ func (c *linuxContainer) start(process *Process) error {
337337
if err != nil {
338338
return newSystemErrorWithCause(err, "creating new parent process")
339339
}
340-
parent.getChildLogs()
340+
parent.forwardChildLogs()
341341
if err := parent.start(); err != nil {
342342
// terminate the process to ensure that it properly is reaped.
343343
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
@@ -443,19 +443,20 @@ func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
443443
if err != nil {
444444
return nil, newSystemErrorWithCause(err, "creating new init pipe")
445445
}
446+
messagePipe := readWritePair{parentPipe, childPipe}
446447

447448
r, w, err := os.Pipe()
448449
if err != nil {
449450
return nil, fmt.Errorf("Unable to create the log pipe: %s", err)
450451
}
451-
logPipe := pipePair{r, w}
452+
logPipe := readWritePair{r, w}
452453

453454
cmd, err := c.commandTemplate(p, childPipe, logPipe.w)
454455
if err != nil {
455456
return nil, newSystemErrorWithCause(err, "creating new command template")
456457
}
457458
if !p.Init {
458-
return c.newSetnsProcess(p, cmd, parentPipe, childPipe, &logPipe)
459+
return c.newSetnsProcess(p, cmd, messagePipe, logPipe)
459460
}
460461

461462
// We only set up fifoFd if we're not doing a `runc exec`. The historic
@@ -466,7 +467,7 @@ func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
466467
if err := c.includeExecFifo(cmd); err != nil {
467468
return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup")
468469
}
469-
return c.newInitProcess(p, cmd, parentPipe, childPipe, &logPipe)
470+
return c.newInitProcess(p, cmd, messagePipe, logPipe)
470471
}
471472

472473
func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File, logPipe *os.File) (*exec.Cmd, error) {
@@ -507,7 +508,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File, logPipe
507508
return cmd, nil
508509
}
509510

510-
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File, logPipe *pipePair) (*initProcess, error) {
511+
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, initPipe, logPipe readWritePair) (*initProcess, error) {
511512
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
512513
nsMaps := make(map[configs.NamespaceType]string)
513514
for _, ns := range c.config.Namespaces {
@@ -522,9 +523,9 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
522523
}
523524
init := &initProcess{
524525
cmd: cmd,
525-
childPipe: childPipe,
526-
parentPipe: parentPipe,
527-
logPipe: *logPipe,
526+
childPipe: initPipe.w,
527+
parentPipe: initPipe.r,
528+
logPipe: logPipe,
528529
manager: c.cgroupManager,
529530
intelRdtManager: c.intelRdtManager,
530531
config: c.newInitConfig(p),
@@ -537,7 +538,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
537538
return init, nil
538539
}
539540

540-
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File, logPipe *pipePair) (*setnsProcess, error) {
541+
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, nsPipe, logPipe readWritePair) (*setnsProcess, error) {
541542
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
542543
state, err := c.currentState()
543544
if err != nil {
@@ -554,9 +555,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
554555
cgroupPaths: c.cgroupManager.GetPaths(),
555556
rootlessCgroups: c.config.RootlessCgroups,
556557
intelRdtPath: state.IntelRdtPath,
557-
childPipe: childPipe,
558-
parentPipe: parentPipe,
559-
logPipe: *logPipe,
558+
childPipe: nsPipe.w,
559+
parentPipe: nsPipe.r,
560+
logPipe: logPipe,
560561
config: c.newInitConfig(p),
561562
process: p,
562563
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.go

Lines changed: 0 additions & 70 deletions
This file was deleted.

libcontainer/logs/logs.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package logs
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strconv"
10+
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
// loggingConfigured will be set once logging has been configured via invoking `ConfigureLogging`.
15+
// Subsequent invocations of `ConfigureLogging` would be no-op
16+
var loggingConfigured = false
17+
18+
type LoggingConfiguration struct {
19+
IsDebug bool
20+
LogFormat string
21+
LogFilePath string
22+
LogPipeFd string
23+
}
24+
25+
func ForwardLogs(logPipe io.Reader) {
26+
type jsonLog struct {
27+
Level string `json:"level"`
28+
Msg string `json:"msg"`
29+
}
30+
31+
lineReader := bufio.NewReader(logPipe)
32+
for {
33+
line, err := lineReader.ReadBytes('\n')
34+
if len(line) > 0 {
35+
processEntry(line)
36+
}
37+
if err == io.EOF {
38+
logrus.Debugf("log pipe has been closed: %+v", err)
39+
return
40+
}
41+
if err != nil {
42+
logrus.Errorf("log pipe read error: %+v", err)
43+
}
44+
}
45+
}
46+
47+
func processEntry(text []byte) {
48+
type jsonLog struct {
49+
Level string `json:"level"`
50+
Msg string `json:"msg"`
51+
}
52+
53+
var jl jsonLog
54+
if err := json.Unmarshal(text, &jl); err != nil {
55+
logrus.Errorf("failed to decode %q to json: %+v", text, err)
56+
return
57+
}
58+
59+
lvl, err := logrus.ParseLevel(jl.Level)
60+
if err != nil {
61+
logrus.Errorf("failed to parse log level %q: %v\n", jl.Level, err)
62+
return
63+
}
64+
log(lvl, jl.Msg)
65+
}
66+
67+
func log(level logrus.Level, args ...interface{}) {
68+
switch level {
69+
case logrus.PanicLevel:
70+
logrus.Panic(args...)
71+
case logrus.FatalLevel:
72+
logrus.Fatal(args...)
73+
case logrus.ErrorLevel:
74+
logrus.Error(args...)
75+
case logrus.WarnLevel:
76+
logrus.Warn(args...)
77+
case logrus.InfoLevel:
78+
logrus.Info(args...)
79+
case logrus.DebugLevel:
80+
logrus.Debug(args...)
81+
default:
82+
logrus.Warnf("Unsupported log level %v while trying to log '%#v'", level, args)
83+
}
84+
}
85+
86+
func ConfigureLogging(loggingConfig *LoggingConfiguration) error {
87+
if loggingConfigured {
88+
logrus.Debug("logging has been already configured")
89+
return nil
90+
}
91+
92+
configureLogLevel(loggingConfig)
93+
if err := configureLogOutput(loggingConfig); err != nil {
94+
return err
95+
}
96+
if err := configureLogFormat(loggingConfig); err != nil {
97+
return err
98+
}
99+
100+
loggingConfigured = true
101+
return nil
102+
}
103+
104+
func configureLogLevel(loggingConfig *LoggingConfiguration) {
105+
if loggingConfig.IsDebug {
106+
logrus.SetLevel(logrus.DebugLevel)
107+
}
108+
}
109+
110+
func configureLogOutput(loggingConfig *LoggingConfiguration) error {
111+
if loggingConfig.LogFilePath != "" {
112+
return configureLogFileOutput(loggingConfig.LogFilePath)
113+
}
114+
115+
if loggingConfig.LogPipeFd != "" {
116+
logPipeFdInt, err := strconv.Atoi(loggingConfig.LogPipeFd)
117+
if err != nil {
118+
return fmt.Errorf("failed to convert _LIBCONTAINER_LOGPIPE environment variable value %q to int: %v", loggingConfig.LogPipeFd, err)
119+
}
120+
configureLogPipeOutput(logPipeFdInt)
121+
return nil
122+
}
123+
124+
return nil
125+
}
126+
127+
func configureLogPipeOutput(logPipeFd int) {
128+
logrus.SetOutput(os.NewFile(uintptr(logPipeFd), "logpipe"))
129+
}
130+
131+
func configureLogFileOutput(logFilePath string) error {
132+
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0644)
133+
if err != nil {
134+
return err
135+
}
136+
logrus.SetOutput(f)
137+
return nil
138+
}
139+
140+
func configureLogFormat(loggingConfig *LoggingConfiguration) error {
141+
switch loggingConfig.LogFormat {
142+
case "text":
143+
// retain logrus's default.
144+
case "json":
145+
logrus.SetFormatter(new(logrus.JSONFormatter))
146+
default:
147+
return fmt.Errorf("unknown log-format %q", loggingConfig.LogFormat)
148+
}
149+
return nil
150+
}

0 commit comments

Comments
 (0)