Skip to content

Commit 55f77dc

Browse files
committed
fix: add fields such as CONTAINER_NAME to journald log entries sent to 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: - containerd#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 <[email protected]>
1 parent f128aac commit 55f77dc

File tree

10 files changed

+105
-40
lines changed

10 files changed

+105
-40
lines changed

cmd/nerdctl/container/container_run_test.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -329,19 +329,44 @@ func TestRunWithJournaldLogDriver(t *testing.T) {
329329
time.Sleep(3 * time.Second)
330330
journalctl, err := exec.LookPath("journalctl")
331331
assert.NilError(t, err)
332+
332333
inspectedContainer := base.InspectContainer(containerName)
333-
found := 0
334-
check := func(log poll.LogT) poll.Result {
335-
res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID[:12])))
336-
assert.Equal(t, 0, res.ExitCode, res)
337-
if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") {
338-
found = 1
339-
return poll.Success()
340-
}
341-
return poll.Continue("reading from journald is not yet finished")
334+
335+
type testCase struct {
336+
name string
337+
filter string
338+
}
339+
testCases := []testCase{
340+
{
341+
name: "filter journald logs using SYSLOG_IDENTIFIER field",
342+
filter: fmt.Sprintf("SYSLOG_IDENTIFIER=%s", inspectedContainer.ID[:12]),
343+
},
344+
{
345+
name: "filter journald logs using CONTAINER_NAME field",
346+
filter: fmt.Sprintf("CONTAINER_NAME=%s", containerName),
347+
},
348+
{
349+
name: "filter journald logs using IMAGE_NAME field",
350+
filter: fmt.Sprintf("IMAGE_NAME=%s", testutil.CommonImage),
351+
},
352+
}
353+
for _, tc := range testCases {
354+
tc := tc
355+
t.Run(tc.name, func(t *testing.T) {
356+
found := 0
357+
check := func(log poll.LogT) poll.Result {
358+
res := icmd.RunCmd(icmd.Command(journalctl, "--no-pager", "--since", "2 minutes ago", tc.filter))
359+
assert.Equal(t, 0, res.ExitCode, res)
360+
if strings.Contains(res.Stdout(), "bar") && strings.Contains(res.Stdout(), "foo") {
361+
found = 1
362+
return poll.Success()
363+
}
364+
return poll.Continue("reading from journald is not yet finished")
365+
}
366+
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))
367+
assert.Equal(t, 1, found)
368+
})
342369
}
343-
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))
344-
assert.Equal(t, 1, found)
345370
}
346371

347372
func TestRunWithJournaldLogDriverAndLogOpt(t *testing.T) {

pkg/cmd/container/create.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
158158
}
159159

160160
var ensuredImage *imgutil.EnsuredImage
161+
var rawImageRef string
161162
if !options.Rootfs {
162163
var platformSS []string // len: 0 or 1
163164
if options.Platform != "" {
@@ -167,13 +168,13 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
167168
if err != nil {
168169
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err
169170
}
170-
rawRef := args[0]
171+
rawImageRef = args[0]
171172

172173
options.ImagePullOpt.Mode = options.Pull
173174
options.ImagePullOpt.OCISpecPlatform = ocispecPlatforms
174175
options.ImagePullOpt.Unpack = nil
175176

176-
ensuredImage, err = image.EnsureImage(ctx, client, rawRef, options.ImagePullOpt)
177+
ensuredImage, err = image.EnsureImage(ctx, client, rawImageRef, options.ImagePullOpt)
177178
if err != nil {
178179
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err
179180
}
@@ -218,7 +219,7 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
218219
// 1, nerdctl run --name demo -it imagename
219220
// 2, ctrl + c to stop demo container
220221
// 3, nerdctl start/restart demo
221-
logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace)
222+
logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace, options.GOptions.Address, rawImageRef)
222223
if err != nil {
223224
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err
224225
}
@@ -819,12 +820,14 @@ func writeCIDFile(path, id string) error {
819820
}
820821

821822
// generateLogConfig creates a LogConfig for the current container store
822-
func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns string) (logConfig logging.LogConfig, err error) {
823+
func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns, address, rawImageRef string) (logConfig logging.LogConfig, err error) {
823824
var u *url.URL
824825
if u, err = url.Parse(logDriver); err == nil && u.Scheme != "" {
825826
logConfig.LogURI = logDriver
826827
} else {
827828
logConfig.Driver = logDriver
829+
logConfig.Address = address
830+
logConfig.RawImageRef = rawImageRef
828831
logConfig.Opts, err = parseKVStringsMapFromLogOpt(logOpt, logDriver)
829832
if err != nil {
830833
return logConfig, err
@@ -834,7 +837,7 @@ func generateLogConfig(dataStore string, id string, logDriver string, logOpt []s
834837
logConfigB []byte
835838
lu *url.URL
836839
)
837-
logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts)
840+
logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts, logConfig.Address, logConfig.RawImageRef)
838841
if err != nil {
839842
return logConfig, err
840843
}

pkg/logging/fluentd_logger.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package logging
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"math"
2223
"net/url"
@@ -99,7 +100,7 @@ func (f *FluentdLogger) Init(dataStore, ns, id string) error {
99100
return nil
100101
}
101102

102-
func (f *FluentdLogger) PreProcess(_ string, config *logging.Config) error {
103+
func (f *FluentdLogger) PreProcess(_ context.Context, _ string, config *logging.Config) error {
103104
if runtime.GOOS == "windows" {
104105
// TODO: support fluentd on windows
105106
return fmt.Errorf("logging to fluentd is not supported on windows")

pkg/logging/journald_logger.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package logging
1818

1919
import (
2020
"bytes"
21+
"context"
2122
"errors"
2223
"fmt"
2324
"io"
@@ -35,6 +36,8 @@ import (
3536
"github.com/containerd/containerd/v2/core/runtime/v2/logging"
3637
"github.com/containerd/log"
3738

39+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
40+
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3841
"github.com/containerd/nerdctl/v2/pkg/strutil"
3942
)
4043

@@ -52,8 +55,10 @@ func JournalLogOptsValidate(logOptMap map[string]string) error {
5255
}
5356

5457
type JournaldLogger struct {
55-
Opts map[string]string
56-
vars map[string]string
58+
Opts map[string]string
59+
vars map[string]string
60+
Address string
61+
RawImageRef string
5762
}
5863

5964
type identifier struct {
@@ -66,7 +71,7 @@ func (journaldLogger *JournaldLogger) Init(dataStore, ns, id string) error {
6671
return nil
6772
}
6873

69-
func (journaldLogger *JournaldLogger) PreProcess(dataStore string, config *logging.Config) error {
74+
func (journaldLogger *JournaldLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {
7075
if !journal.Enabled() {
7176
return errors.New("the local systemd journal is not available for logging")
7277
}
@@ -95,9 +100,33 @@ func (journaldLogger *JournaldLogger) PreProcess(dataStore string, config *loggi
95100
syslogIdentifier = b.String()
96101
}
97102
}
103+
104+
client, ctx, cancel, err := clientutil.NewClient(ctx, config.Namespace, journaldLogger.Address)
105+
if err != nil {
106+
return err
107+
}
108+
defer func() {
109+
cancel()
110+
client.Close()
111+
}()
112+
containerID := config.ID
113+
container, err := client.LoadContainer(ctx, containerID)
114+
if err != nil {
115+
return err
116+
}
117+
containerLabels, err := container.Labels(ctx)
118+
if err != nil {
119+
return err
120+
}
121+
98122
// construct log metadata for the container
99123
vars := map[string]string{
100124
"SYSLOG_IDENTIFIER": syslogIdentifier,
125+
"CONTAINER_TAG": syslogIdentifier,
126+
"CONTAINER_ID": shortID,
127+
"CONTAINER_ID_FULL": containerID,
128+
"CONTAINER_NAME": containerutil.GetContainerName(containerLabels),
129+
"IMAGE_NAME": journaldLogger.RawImageRef,
101130
}
102131
journaldLogger.vars = vars
103132
return nil

pkg/logging/json_logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func (jsonLogger *JSONLogger) Init(dataStore, ns, id string) error {
7878
return nil
7979
}
8080

81-
func (jsonLogger *JSONLogger) PreProcess(dataStore string, config *logging.Config) error {
81+
func (jsonLogger *JSONLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {
8282
var jsonFilePath string
8383
if logPath, ok := jsonLogger.Opts[LogPath]; ok {
8484
jsonFilePath = logPath

pkg/logging/logging.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ const (
4949

5050
type Driver interface {
5151
Init(dataStore, ns, id string) error
52-
PreProcess(dataStore string, config *logging.Config) error
52+
PreProcess(ctx context.Context, dataStore string, config *logging.Config) error
5353
Process(stdout <-chan string, stderr <-chan string) error
5454
PostProcess() error
5555
}
5656

57-
type DriverFactory func(map[string]string) (Driver, error)
57+
type DriverFactory func(map[string]string, string, string) (Driver, error)
5858
type LogOptsValidateFunc func(logOptMap map[string]string) error
5959

6060
var drivers = make(map[string]DriverFactory)
@@ -81,28 +81,28 @@ func Drivers() []string {
8181
return ss
8282
}
8383

84-
func GetDriver(name string, opts map[string]string) (Driver, error) {
84+
func GetDriver(name string, opts map[string]string, address, rawImageRef string) (Driver, error) {
8585
driverFactory, ok := drivers[name]
8686
if !ok {
8787
return nil, fmt.Errorf("unknown logging driver %q: %w", name, errdefs.ErrNotFound)
8888
}
89-
return driverFactory(opts)
89+
return driverFactory(opts, address, rawImageRef)
9090
}
9191

9292
func init() {
93-
RegisterDriver("none", func(opts map[string]string) (Driver, error) {
93+
RegisterDriver("none", func(opts map[string]string, address, rawImageRef string) (Driver, error) {
9494
return &NoneLogger{}, nil
9595
}, NoneLogOptsValidate)
96-
RegisterDriver("json-file", func(opts map[string]string) (Driver, error) {
96+
RegisterDriver("json-file", func(opts map[string]string, address, rawImageRef string) (Driver, error) {
9797
return &JSONLogger{Opts: opts}, nil
9898
}, JSONFileLogOptsValidate)
99-
RegisterDriver("journald", func(opts map[string]string) (Driver, error) {
100-
return &JournaldLogger{Opts: opts}, nil
99+
RegisterDriver("journald", func(opts map[string]string, address, rawImageRef string) (Driver, error) {
100+
return &JournaldLogger{Opts: opts, Address: address, RawImageRef: rawImageRef}, nil
101101
}, JournalLogOptsValidate)
102-
RegisterDriver("fluentd", func(opts map[string]string) (Driver, error) {
102+
RegisterDriver("fluentd", func(opts map[string]string, address, rawImageRef string) (Driver, error) {
103103
return &FluentdLogger{Opts: opts}, nil
104104
}, FluentdLogOptsValidate)
105-
RegisterDriver("syslog", func(opts map[string]string) (Driver, error) {
105+
RegisterDriver("syslog", func(opts map[string]string, address, rawImageRef string) (Driver, error) {
106106
return &SyslogLogger{Opts: opts}, nil
107107
}, SyslogOptsValidate)
108108
}
@@ -121,9 +121,11 @@ func Main(argv2 string) error {
121121

122122
// LogConfig is marshalled as "log-config.json"
123123
type LogConfig struct {
124-
Driver string `json:"driver"`
125-
Opts map[string]string `json:"opts,omitempty"`
126-
LogURI string `json:"-"`
124+
Driver string `json:"driver"`
125+
Opts map[string]string `json:"opts,omitempty"`
126+
LogURI string `json:"-"`
127+
Address string `json:"address"`
128+
RawImageRef string `json:"image"`
127129
}
128130

129131
// LogConfigFilePath returns the path of log-config.json
@@ -149,7 +151,7 @@ func LoadLogConfig(dataStore, ns, id string) (LogConfig, error) {
149151
}
150152

151153
func loggingProcessAdapter(ctx context.Context, driver Driver, dataStore string, config *logging.Config) error {
152-
if err := driver.PreProcess(dataStore, config); err != nil {
154+
if err := driver.PreProcess(ctx, dataStore, config); err != nil {
153155
return err
154156
}
155157

@@ -215,7 +217,7 @@ func loggerFunc(dataStore string) (logging.LoggerFunc, error) {
215217
if err != nil {
216218
return err
217219
}
218-
driver, err := GetDriver(logConfig.Driver, logConfig.Opts)
220+
driver, err := GetDriver(logConfig.Driver, logConfig.Opts, logConfig.Address, logConfig.RawImageRef)
219221
if err != nil {
220222
return err
221223
}

pkg/logging/logging_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (m *MockDriver) Init(dataStore, ns, id string) error {
3838
return nil
3939
}
4040

41-
func (m *MockDriver) PreProcess(dataStore string, config *logging.Config) error {
41+
func (m *MockDriver) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {
4242
return nil
4343
}
4444

pkg/logging/none_logger.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package logging
1818

1919
import (
20+
"context"
21+
2022
"github.com/containerd/containerd/v2/core/runtime/v2/logging"
2123
)
2224

@@ -28,7 +30,7 @@ func (n *NoneLogger) Init(dataStore, ns, id string) error {
2830
return nil
2931
}
3032

31-
func (n *NoneLogger) PreProcess(dataStore string, config *logging.Config) error {
33+
func (n *NoneLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {
3234
return nil
3335
}
3436

pkg/logging/none_logger_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package logging
1818

1919
import (
20+
"context"
2021
"os"
2122
"testing"
2223
"time"
@@ -29,6 +30,7 @@ import (
2930
func TestNoneLogger(t *testing.T) {
3031
// Create a temporary directory for potential log files
3132
tmpDir := t.TempDir()
33+
ctx := context.Background()
3234

3335
logger := &NoneLogger{
3436
Opts: map[string]string{},
@@ -40,7 +42,7 @@ func TestNoneLogger(t *testing.T) {
4042

4143
// Run all logger methods
4244
logger.Init(tmpDir, "namespace", "id")
43-
logger.PreProcess(tmpDir, &logging.Config{})
45+
logger.PreProcess(ctx, tmpDir, &logging.Config{})
4446

4547
stdout := make(chan string)
4648
stderr := make(chan string)

pkg/logging/syslog_logger.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package logging
1818

1919
import (
20+
"context"
2021
"crypto/tls"
2122
"errors"
2223
"fmt"
@@ -122,7 +123,7 @@ func (sy *SyslogLogger) Init(dataStore string, ns string, id string) error {
122123
return nil
123124
}
124125

125-
func (sy *SyslogLogger) PreProcess(dataStore string, config *logging.Config) error {
126+
func (sy *SyslogLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {
126127
logger, err := parseSyslog(config.ID, sy.Opts)
127128
if err != nil {
128129
return err

0 commit comments

Comments
 (0)