log: support --log-opt tag=

support a custom tag to add to each log for the container.

It is currently supported only by the journald backend.

Closes: https://github.com/containers/libpod/issues/3653

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano
2020-01-07 13:41:56 +01:00
parent f3fc10feb4
commit 71341a1948
9 changed files with 133 additions and 19 deletions

View File

@ -419,6 +419,17 @@ Logging driver specific options. Used to set the path to the container log file
`--log-opt path=/var/log/container/mycontainer.json`
**--log-opt**=*tag*
Set custom logging configuration. Presently supports the `tag` option
which specified a custom log tag for the container. For example:
`--log-opt tag="{{.ImageName}}"`
It supports the same keys as `podman inspect --format`.
It is currently supported only by the journald log driver.
**--mac-address**=*address*
Container MAC address (e.g. 92:d0:c6:0a:29:33)

View File

@ -426,10 +426,21 @@ Logging driver for the container. Currently available options are *k8s-file* an
**--log-opt**=*path*
Logging driver specific options. Used to set the path to the container log file. For example:
Set custom logging configuration. Presently supports the `tag` option
which specified a custom log tag for the container. For example:
`--log-opt path=/var/log/container/mycontainer.json`
**--log-opt**=*tag*
Specify a custom log tag for the container. For example:
`--log-opt tag="{{.ImageName}}"`
It supports the same keys as `podman inspect --format`.
It is currently supported only by the journald log driver.
**--mac-address**=*address*
Container MAC address (e.g. `92:d0:c6:0a:29:33`)

View File

@ -379,6 +379,8 @@ type ContainerConfig struct {
CgroupParent string `json:"cgroupParent"`
// LogPath log location
LogPath string `json:"logPath"`
// LogTag is the tag used for logging
LogTag string `json:"logTag"`
// LogDriver driver for logs
LogDriver string `json:"logDriver"`
// File containing the conmon PID
@ -726,6 +728,11 @@ func (c *Container) LogPath() string {
return c.config.LogPath
}
// LogTag returns the tag to the container's log file
func (c *Container) LogTag() string {
return c.config.LogTag
}
// RestartPolicy returns the container's restart policy.
func (c *Container) RestartPolicy() string {
return c.config.RestartPolicy

View File

@ -107,6 +107,7 @@ type InspectContainerData struct {
OCIConfigPath string `json:"OCIConfigPath,omitempty"`
OCIRuntime string `json:"OCIRuntime,omitempty"`
LogPath string `json:"LogPath"`
LogTag string `json:"LogTag"`
ConmonPidFile string `json:"ConmonPidFile"`
Name string `json:"Name"`
RestartCount int32 `json:"RestartCount"`
@ -629,17 +630,9 @@ type InspectNetworkSettings struct {
MacAddress string `json:"MacAddress"`
}
// Inspect a container for low-level information
func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return nil, err
}
}
// inspectLocked inspects a container for low-level information.
// The caller must held c.lock.
func (c *Container) inspectLocked(size bool) (*InspectContainerData, error) {
storeCtr, err := c.runtime.store.Container(c.ID())
if err != nil {
return nil, errors.Wrapf(err, "error getting container from store %q", c.ID())
@ -655,6 +648,20 @@ func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
return c.getContainerInspectData(size, driverData)
}
// Inspect a container for low-level information
func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return nil, err
}
}
return c.inspectLocked(size)
}
func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*InspectContainerData, error) {
config := c.config
runtimeInfo := c.state
@ -732,6 +739,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
HostsPath: hostsPath,
StaticDir: config.StaticDir,
LogPath: config.LogPath,
LogTag: config.LogTag,
OCIRuntime: config.OCIRuntime,
ConmonPidFile: config.ConmonPidFile,
Name: config.Name,

View File

@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"syscall"
"text/template"
"time"
"github.com/containers/libpod/libpod/config"
@ -532,7 +533,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = c.execOCILog(sessionID)
}
args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog)
args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, "")
if options.PreserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...)
@ -887,6 +888,27 @@ func waitPidStop(pid int, timeout time.Duration) error {
}
}
func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
logTag := ctr.LogTag()
if logTag == "" {
return "", nil
}
data, err := ctr.inspectLocked(false)
if err != nil {
return "", nil
}
tmpl, err := template.New("container").Parse(logTag)
if err != nil {
return "", errors.Wrapf(err, "template parsing error %s", logTag)
}
var b bytes.Buffer
err = tmpl.Execute(&b, data)
if err != nil {
return "", err
}
return b.String(), nil
}
// createOCIContainer generates this container's main conmon instance and prepares it for starting
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) {
var stderrBuf bytes.Buffer
@ -913,7 +935,13 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = filepath.Join(ctr.state.RunDir, "oci-log")
}
args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog)
logTag, err := r.getLogTag(ctr)
if err != nil {
return err
}
args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog, logTag)
if ctr.config.Spec.Process.Terminal {
args = append(args, "-t")
@ -1147,7 +1175,7 @@ func (r *ConmonOCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*o
}
// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI
func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath string) []string {
func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath, logTag string) []string {
// set the conmon API version to be able to use the correct sync struct keys
args := []string{"--api-version", "1"}
if r.cgroupManager == define.SystemdCgroupsManager && !ctr.config.NoCgroups {
@ -1194,6 +1222,9 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
if ociLogPath != "" {
args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath))
}
if logTag != "" {
args = append(args, "--log-tag", logTag)
}
if ctr.config.NoCgroups {
logrus.Debugf("Running with no CGroups")
args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled")

View File

@ -1059,6 +1059,23 @@ func WithLogPath(path string) CtrCreateOption {
}
}
// WithLogTag sets the tag to the log file.
func WithLogTag(tag string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
if tag == "" {
return errors.Wrapf(define.ErrInvalidArg, "log tag must be set")
}
ctr.config.LogTag = tag
return nil
}
}
// WithNoCgroups disables the creation of CGroups for the new container.
func WithNoCgroups() CtrCreateOption {
return func(ctr *Container) error {

View File

@ -282,10 +282,13 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, libpod.WithStopSignal(c.StopSignal))
options = append(options, libpod.WithStopTimeout(c.StopTimeout))
logPath := getLoggingPath(c.LogDriverOpt)
logPath, logTag := getLoggingOpts(c.LogDriverOpt)
if logPath != "" {
options = append(options, libpod.WithLogPath(logPath))
}
if logTag != "" {
options = append(options, libpod.WithLogTag(logTag))
}
if c.LogDriver != "" {
options = append(options, libpod.WithLogDriver(c.LogDriver))

View File

@ -132,16 +132,23 @@ func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint
}, nil
}
func getLoggingPath(opts []string) string {
// getLoggingOpts splits the path= and tag= options provided to --log-opt.
func getLoggingOpts(opts []string) (string, string) {
var path, tag string
for _, opt := range opts {
arr := strings.SplitN(opt, "=", 2)
if len(arr) == 2 {
if strings.TrimSpace(arr[0]) == "path" {
return strings.TrimSpace(arr[1])
path = strings.TrimSpace(arr[1])
} else if strings.TrimSpace(arr[0]) == "tag" {
tag = strings.TrimSpace(arr[1])
}
}
if path != "" && tag != "" {
break
}
}
return ""
return path, tag
}
// ParseDevice parses device mapping string to a src, dest & permissions string

View File

@ -1,7 +1,9 @@
package integration
import (
"fmt"
"os"
"os/exec"
"strings"
. "github.com/containers/libpod/test/utils"
@ -153,6 +155,23 @@ var _ = Describe("Podman logs", func() {
Expect(results.ExitCode()).To(BeZero())
})
It("podman journald logs for container with container tag", func() {
Skip("need to verify images have correct packages for journald")
logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "--log-opt=tag={{.ImageName}}", "-d", ALPINE, "sh", "-c", "echo podman; sleep 0.1; echo podman; sleep 0.1; echo podman"})
logc.WaitWithDefaultTimeout()
Expect(logc.ExitCode()).To(Equal(0))
cid := logc.OutputToString()
wait := podmanTest.Podman([]string{"wait", "-l"})
wait.WaitWithDefaultTimeout()
Expect(wait.ExitCode()).To(BeZero())
cmd := exec.Command("journalctl", "--no-pager", "-o", "json", "--output-fields=CONTAINER_TAG", "-u", fmt.Sprintf("libpod-conmon-%s.scope", cid))
out, err := cmd.CombinedOutput()
Expect(err).To(BeNil())
Expect(string(out)).To(ContainSubstring("alpine"))
})
It("podman journald logs for container", func() {
Skip("need to verify images have correct packages for journald")
logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})