Skip to content

Commit

Permalink
fix: add fields such as CONTAINER_NAME to journald log entries sent t…
Browse files Browse the repository at this point in the history
…o by containers

In the current implementation, containers running by `nerdctl` dose not
export entries containing fields such as `CONTAINER_NAME`, `IMAGE_NAME`
, and etc to the journald log like containers running by `docker cli`.

At this time, the journald log entry describes below when sending to the
journald log using nerdctl.

```
> nerdctl run -d --name nginx-nerdctl --log-driver=journald nginx
bb7df47d27fd73426cec286ed88c5abf1443e74df637e2440d2dbca7229a84dc

> nerdctl ps
CONTAINER ID    IMAGE                             COMMAND                   CREATED          STATUS    PORTS    NAMES
bb7df47d27fd    docker.io/library/nginx:latest    "/docker-entrypoint.…"    3 seconds ago    Up                 nginx-nerdctl

> sudo journalctl SYSLOG_IDENTIFIER=bb7df47d27fd -a -n 1 -o json-pretty
{
        "__CURSOR" : "???",
        "__REALTIME_TIMESTAMP" : "1730899940827182",
        "__MONOTONIC_TIMESTAMP" : "10815937979908",
        "_BOOT_ID" : "???",
        "_UID" : "0",
        "_GID" : "0",
        "_CAP_EFFECTIVE" : "1ffffffffff",
        "_MACHINE_ID" : "???",
        "_HOSTNAME" : "???.us-west-2.amazon.com",
        "_TRANSPORT" : "journal",
        "_SYSTEMD_SLICE" : "system.slice",
        "PRIORITY" : "3",
        "_SYSTEMD_CGROUP" : "/system.slice/containerd.service",
        "_SYSTEMD_UNIT" : "containerd.service",
        "_COMM" : "nerdctl",
        "_EXE" : "/usr/local/bin/nerdctl",
        "_CMDLINE" : "/usr/local/bin/nerdctl _NERDCTL_INTERNAL_LOGGING /var/lib/nerdctl/1935db59",
        "SYSLOG_IDENTIFIER" : "bb7df47d27fd",
        "_PID" : "8118",
        "MESSAGE" : "2024/11/06 13:32:20 [notice] 1#1: start worker process 44",
        "_SOURCE_REALTIME_TIMESTAMP" : "1730899940825905"
}
```

On the other hand, the output fields are listed below when we use the
journald logging driver with docker cli.

- https://docs.docker.com/engine/logging/drivers/journald/

As you can see, some entries are not output by nerdctl and are
incompatible with the docker cli.

This feature request is reported in the following:

- #3486

Therefore, in this pull request, we will add the fields to be output in
the journald log.

After applying this fix, the journald log will output the following
fields.

```
{
  "__CURSOR": "???",
  "__REALTIME_TIMESTAMP": "1731385591671422",
  "__MONOTONIC_TIMESTAMP": "11301588824148",
  "_BOOT_ID": "???",
  "_MACHINE_ID": "???",
  "_HOSTNAME": "???.us-west-2.amazon.com",
  "PRIORITY": "3",
  "_TRANSPORT": "journal",
  "_UID": "0",
  "_GID": "0",
  "_COMM": "nerdctl",
  "_EXE": "/usr/local/bin/nerdctl",
  "_CMDLINE": "/usr/local/bin/nerdctl _NERDCTL_INTERNAL_LOGGING /var/lib/nerdctl/1935db59",
  "_CAP_EFFECTIVE": "1ffffffffff",
  "_SYSTEMD_CGROUP": "/system.slice/containerd.service",
  "_SYSTEMD_UNIT": "containerd.service",
  "_SYSTEMD_SLICE": "system.slice",
  "CONTAINER_NAME": "nginx-nerdctl",
  "IMAGE_NAME": "nginx",
  "CONTAINER_ID_FULL": "fe22eccbd704ba799785999079ac465ed067d5914e9e3f1020e769921d5a83c5",
  "SYSLOG_IDENTIFIER": "fe22eccbd704",
  "CONTAINER_TAG": "fe22eccbd704",
  "CONTAINER_ID": "fe22eccbd704",
  "_PID": "31643",
  "MESSAGE": "2024/11/12 04:26:31 [notice] 1#1: start worker process 44",
  "_SOURCE_REALTIME_TIMESTAMP": "1731385591669765"
}
```

Signed-off-by: Hayato Kiwata <haytok@amazon.co.jp>
  • Loading branch information
haytok committed Nov 20, 2024
1 parent f128aac commit deb3ea5
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 29 deletions.
48 changes: 37 additions & 11 deletions cmd/nerdctl/container/container_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"gotest.tools/v3/poll"

"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/logging"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)
Expand Down Expand Up @@ -329,19 +330,44 @@ func TestRunWithJournaldLogDriver(t *testing.T) {
time.Sleep(3 * time.Second)
journalctl, err := exec.LookPath("journalctl")
assert.NilError(t, err)

inspectedContainer := base.InspectContainer(containerName)
found := 0
check := func(log poll.LogT) poll.Result {
res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID[:12])))
assert.Equal(t, 0, res.ExitCode, res)
if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") {
found = 1
return poll.Success()
}
return poll.Continue("reading from journald is not yet finished")

type testCase struct {
name string
filter string
}
testCases := []testCase{
{
name: "filter using SYSLOG_IDENTIFIER field",
filter: fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID[:12]),
},
{
name: "filter using CONTAINER_NAME field",
filter: fmt.Sprintf("CONTAINER_NAME=%s", containerName),
},
{
name: "filter using IMAGE_NAME field",
filter: fmt.Sprintf("IMAGE_NAME=%s", logging.GetJournaldImageNameField(testutil.CommonImage)),
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
found := 0
check := func(log poll.LogT) poll.Result {
res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", tc.filter))
assert.Equal(t, 0, res.ExitCode, res)
if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") {
found = 1
return poll.Success()
}
return poll.Continue("reading from journald is not yet finished")
}
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))
assert.Equal(t, 1, found)
})
}
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))
assert.Equal(t, 1, found)
}

func TestRunWithJournaldLogDriverAndLogOpt(t *testing.T) {
Expand Down
7 changes: 4 additions & 3 deletions pkg/cmd/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
// 1, nerdctl run --name demo -it imagename
// 2, ctrl + c to stop demo container
// 3, nerdctl start/restart demo
logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace)
logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace, options.GOptions.Address)
if err != nil {
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err
}
Expand Down Expand Up @@ -819,12 +819,13 @@ func writeCIDFile(path, id string) error {
}

// generateLogConfig creates a LogConfig for the current container store
func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns string) (logConfig logging.LogConfig, err error) {
func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns, address string) (logConfig logging.LogConfig, err error) {
var u *url.URL
if u, err = url.Parse(logDriver); err == nil && u.Scheme != "" {
logConfig.LogURI = logDriver
} else {
logConfig.Driver = logDriver
logConfig.Address = address
logConfig.Opts, err = parseKVStringsMapFromLogOpt(logOpt, logDriver)
if err != nil {
return logConfig, err
Expand All @@ -834,7 +835,7 @@ func generateLogConfig(dataStore string, id string, logDriver string, logOpt []s
logConfigB []byte
lu *url.URL
)
logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts)
logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts, logConfig.Address)
if err != nil {
return logConfig, err
}
Expand Down
48 changes: 46 additions & 2 deletions pkg/logging/journald_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package logging

import (
"bytes"
"context"
"errors"
"fmt"
"io"
Expand All @@ -32,9 +33,13 @@ import (
"github.com/docker/cli/templates"
timetypes "github.com/docker/docker/api/types/time"

containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/core/runtime/v2/logging"
"github.com/containerd/log"

"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/containerutil"
"github.com/containerd/nerdctl/v2/pkg/imgutil"
"github.com/containerd/nerdctl/v2/pkg/strutil"
)

Expand All @@ -52,8 +57,9 @@ func JournalLogOptsValidate(logOptMap map[string]string) error {
}

type JournaldLogger struct {
Opts map[string]string
vars map[string]string
Opts map[string]string
vars map[string]string
Address string
}

type identifier struct {
Expand Down Expand Up @@ -95,9 +101,38 @@ func (journaldLogger *JournaldLogger) PreProcess(dataStore string, config *loggi
syslogIdentifier = b.String()
}
}

ctx := context.Background()
client, ctx, cancel, err := clientutil.NewClient(ctx, config.Namespace, journaldLogger.Address)
if err != nil {
return err
}
defer func() {
cancel()
client.Close()
}()
containerID := config.ID
container, err := client.LoadContainer(ctx, containerID)
if err != nil {
return err
}
containerLabels, err := container.Labels(ctx)
if err != nil {
return err
}
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return err
}

// construct log metadata for the container
vars := map[string]string{
"SYSLOG_IDENTIFIER": syslogIdentifier,
"CONTAINER_TAG": syslogIdentifier,
"CONTAINER_ID": shortID,
"CONTAINER_ID_FULL": containerID,
"CONTAINER_NAME": containerutil.GetContainerName(containerLabels),
"IMAGE_NAME": GetJournaldImageNameField(info.Image),
}
journaldLogger.vars = vars
return nil
Expand Down Expand Up @@ -200,3 +235,12 @@ func prepareJournalCtlDate(t string) (string, error) {
s := tm.Format("2006-01-02 15:04:05")
return s, nil
}

func GetJournaldImageNameField(image string) string {
imageName := image
if repo, tag := imgutil.ParseRepoTag(imageName); tag == "latest" {
imageName = repo
}

return imageName
}
27 changes: 14 additions & 13 deletions pkg/logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type Driver interface {
PostProcess() error
}

type DriverFactory func(map[string]string) (Driver, error)
type DriverFactory func(map[string]string, string) (Driver, error)
type LogOptsValidateFunc func(logOptMap map[string]string) error

var drivers = make(map[string]DriverFactory)
Expand All @@ -81,28 +81,28 @@ func Drivers() []string {
return ss
}

func GetDriver(name string, opts map[string]string) (Driver, error) {
func GetDriver(name string, opts map[string]string, address string) (Driver, error) {
driverFactory, ok := drivers[name]
if !ok {
return nil, fmt.Errorf("unknown logging driver %q: %w", name, errdefs.ErrNotFound)
}
return driverFactory(opts)
return driverFactory(opts, address)
}

func init() {
RegisterDriver("none", func(opts map[string]string) (Driver, error) {
RegisterDriver("none", func(opts map[string]string, address string) (Driver, error) {
return &NoneLogger{}, nil
}, NoneLogOptsValidate)
RegisterDriver("json-file", func(opts map[string]string) (Driver, error) {
RegisterDriver("json-file", func(opts map[string]string, address string) (Driver, error) {
return &JSONLogger{Opts: opts}, nil
}, JSONFileLogOptsValidate)
RegisterDriver("journald", func(opts map[string]string) (Driver, error) {
return &JournaldLogger{Opts: opts}, nil
RegisterDriver("journald", func(opts map[string]string, address string) (Driver, error) {
return &JournaldLogger{Opts: opts, Address: address}, nil
}, JournalLogOptsValidate)
RegisterDriver("fluentd", func(opts map[string]string) (Driver, error) {
RegisterDriver("fluentd", func(opts map[string]string, address string) (Driver, error) {
return &FluentdLogger{Opts: opts}, nil
}, FluentdLogOptsValidate)
RegisterDriver("syslog", func(opts map[string]string) (Driver, error) {
RegisterDriver("syslog", func(opts map[string]string, address string) (Driver, error) {
return &SyslogLogger{Opts: opts}, nil
}, SyslogOptsValidate)
}
Expand All @@ -121,9 +121,10 @@ func Main(argv2 string) error {

// LogConfig is marshalled as "log-config.json"
type LogConfig struct {
Driver string `json:"driver"`
Opts map[string]string `json:"opts,omitempty"`
LogURI string `json:"-"`
Driver string `json:"driver"`
Opts map[string]string `json:"opts,omitempty"`
LogURI string `json:"-"`
Address string `json:"address"`
}

// LogConfigFilePath returns the path of log-config.json
Expand Down Expand Up @@ -215,7 +216,7 @@ func loggerFunc(dataStore string) (logging.LoggerFunc, error) {
if err != nil {
return err
}
driver, err := GetDriver(logConfig.Driver, logConfig.Opts)
driver, err := GetDriver(logConfig.Driver, logConfig.Opts, logConfig.Address)
if err != nil {
return err
}
Expand Down

0 comments on commit deb3ea5

Please sign in to comment.