Skip to content

Commit f39dbe8

Browse files
committed
feat: add --details flag to logs command
Signed-off-by: Ruihua Wen <[email protected]>
1 parent 268aae8 commit f39dbe8

File tree

10 files changed

+193
-2
lines changed

10 files changed

+193
-2
lines changed

cmd/nerdctl/container/container_logs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ The following containers are supported:
5353
cmd.Flags().StringP("tail", "n", "all", "Number of lines to show from the end of the logs")
5454
cmd.Flags().String("since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
5555
cmd.Flags().String("until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
56+
cmd.Flags().Bool("details", false, "Show extra details provided to logs")
5657
return cmd
5758
}
5859

@@ -88,6 +89,10 @@ func logsOptions(cmd *cobra.Command) (types.ContainerLogsOptions, error) {
8889
if err != nil {
8990
return types.ContainerLogsOptions{}, err
9091
}
92+
details, err := cmd.Flags().GetBool("details")
93+
if err != nil {
94+
return types.ContainerLogsOptions{}, err
95+
}
9196
return types.ContainerLogsOptions{
9297
Stdout: cmd.OutOrStdout(),
9398
Stderr: cmd.OutOrStderr(),
@@ -97,6 +102,7 @@ func logsOptions(cmd *cobra.Command) (types.ContainerLogsOptions, error) {
97102
Tail: tail,
98103
Since: since,
99104
Until: until,
105+
Details: details,
100106
}, nil
101107
}
102108

cmd/nerdctl/container/container_logs_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,35 @@ func TestNoneLoggerHasNoLogURI(t *testing.T) {
345345
testCase.Expected = test.Expects(1, nil, nil)
346346
testCase.Run(t)
347347
}
348+
349+
func TestLogsWithDetails(t *testing.T) {
350+
testCase := nerdtest.Setup()
351+
352+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
353+
helpers.Ensure("run", "-d", "--log-driver", "json-file",
354+
"--log-opt", "max-size=10m",
355+
"--log-opt", "max-file=3",
356+
"--log-opt", "env=ENV",
357+
"--env", "ENV=foo",
358+
"--log-opt", "labels=LABEL",
359+
"--label", "LABEL=bar",
360+
"--name", data.Identifier(), testutil.CommonImage,
361+
"sh", "-ec", "echo baz")
362+
}
363+
364+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
365+
helpers.Anyhow("rm", "-f", data.Identifier())
366+
}
367+
368+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
369+
return helpers.Command("logs", "--details", data.Identifier())
370+
}
371+
372+
testCase.Expected = test.Expects(0, nil, expect.All(
373+
expect.Contains("ENV=foo"),
374+
expect.Contains("LABEL=bar"),
375+
expect.Contains("baz"),
376+
))
377+
378+
testCase.Run(t)
379+
}

docs/command-reference.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,12 @@ Logging flags:
326326
- :nerd_face: `--log-opt=log-path=<LOG-PATH>`: The log path where the logs are written. The path will be created if it does not exist. If the log file exists, the old file will be renamed to `<LOG-PATH>.1`.
327327
- Default: `<data-root>/<containerd-socket-hash>/<namespace>/<container-id>/<container-id>-json.log`
328328
- Example: `/var/lib/nerdctl/1935db59/containers/default/<container-id>/<container-id>-json.log`
329+
- :whale: `--log-opt labels=production_status,geo`: Applies when starting the Docker daemon. A comma-separated list of logging-related labels this daemon accepts.
330+
- :whale: `--log-opt env=os,customer`: Applies when starting the Docker daemon. A comma-separated list of logging-related environment variables this daemon accepts.
329331
- :whale: `--log-driver=journald`: Writes log messages to `journald`. The `journald` daemon must be running on the host machine.
330332
- :whale: `--log-opt=tag=<TEMPLATE>`: Specify template to set `SYSLOG_IDENTIFIER` value in journald logs.
333+
- :whale: `--log-opt labels=production_status,geo`: Applies when starting the Docker daemon. A comma-separated list of logging-related labels this daemon accepts.
334+
- :whale: `--log-opt env=os,customer`: Applies when starting the Docker daemon. A comma-separated list of logging-related environment variables this daemon accepts.
331335
- :whale: `--log-driver=fluentd`: Writes log messages to `fluentd`. The `fluentd` daemon must be running on the host machine.
332336
- The `fluentd` logging driver supports the following logging options:
333337
- :whale: `--log-opt=fluentd-address=<ADDRESS>`: The address of the `fluentd` daemon, tcp(default) and unix sockets are supported..
@@ -542,14 +546,13 @@ Usage: `nerdctl logs [OPTIONS] CONTAINER`
542546

543547
Flags:
544548

549+
- :whale: `--details`: Show extra details provided to logs
545550
- :whale: `-f, --follow`: Follow log output
546551
- :whale: `--since`: Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
547552
- :whale: `--until`: Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
548553
- :whale: `-t, --timestamps`: Show timestamps
549554
- :whale: `-n, --tail`: Number of lines to show from the end of the logs (default "all")
550555

551-
Unimplemented `docker logs` flags: `--details`
552-
553556
### :whale: nerdctl port
554557

555558
List port mappings or a specific mapping for the container.

pkg/api/types/container_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ type ContainerLogsOptions struct {
400400
Since string
401401
// Show logs before a timestamp (e.g., 2013-01-02T13:23:37Z) or relative (e.g., 42m for 42 minutes).
402402
Until string
403+
// Details specifies whether to show extra details provided to logs
404+
Details bool
403405
}
404406

405407
// ContainerWaitOptions specifies options for `nerdctl (container) wait`.

pkg/cmd/container/logs.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ package container
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223
"os"
2324
"os/signal"
25+
"sort"
26+
"strings"
2427
"syscall"
2528

2629
containerd "github.com/containerd/containerd/v2/client"
@@ -102,6 +105,44 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
102105
}
103106
}
104107

108+
detailPrefix := ""
109+
if options.Details {
110+
if logConfigJSON, ok := l["nerdctl/log-config"]; ok {
111+
type LogConfig struct {
112+
Opts map[string]string `json:"opts"`
113+
}
114+
115+
e, err := getContainerEnvs(ctx, found.Container)
116+
if err != nil {
117+
return err
118+
}
119+
120+
var logConfig LogConfig
121+
var optPairs []string
122+
123+
if err := json.Unmarshal([]byte(logConfigJSON), &logConfig); err == nil {
124+
envOpts, labelOpts := getLogOpts(logConfig.Opts)
125+
126+
for _, v := range envOpts {
127+
if env, ok := e[v]; ok {
128+
optPairs = append(optPairs, fmt.Sprintf("%s=%s", v, env))
129+
}
130+
}
131+
132+
for _, v := range labelOpts {
133+
if label, ok := l[v]; ok {
134+
optPairs = append(optPairs, fmt.Sprintf("%s=%s", v, label))
135+
}
136+
}
137+
138+
if len(optPairs) > 0 {
139+
sort.Strings(optPairs)
140+
detailPrefix = strings.Join(optPairs, ",")
141+
}
142+
}
143+
}
144+
}
145+
105146
logViewOpts := logging.LogViewOptions{
106147
ContainerID: found.Container.ID(),
107148
Namespace: l[labels.Namespace],
@@ -112,6 +153,8 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
112153
Tail: options.Tail,
113154
Since: options.Since,
114155
Until: options.Until,
156+
Details: options.Details,
157+
DetailPrefix: detailPrefix,
115158
}
116159
logViewer, err := logging.InitContainerLogViewer(l, logViewOpts, stopChannel, options.GOptions.Experimental)
117160
if err != nil {
@@ -146,3 +189,45 @@ func getLogPath(ctx context.Context, container containerd.Container) (string, er
146189

147190
return meta.LogPath, nil
148191
}
192+
193+
func getContainerEnvs(ctx context.Context, container containerd.Container) (map[string]string, error) {
194+
envMap := make(map[string]string)
195+
196+
spec, err := container.Spec(ctx)
197+
if err != nil {
198+
return nil, err
199+
}
200+
201+
if spec.Process == nil {
202+
return envMap, nil
203+
}
204+
205+
for _, env := range spec.Process.Env {
206+
parts := strings.SplitN(env, "=", 2)
207+
if len(parts) == 2 {
208+
envMap[parts[0]] = parts[1]
209+
}
210+
}
211+
212+
return envMap, nil
213+
}
214+
215+
func getLogOpts(logOpts map[string]string) ([]string, []string) {
216+
var envOpts []string
217+
var labelOpts []string
218+
219+
for k, v := range logOpts {
220+
lowerKey := strings.ToLower(k)
221+
if lowerKey == "env" {
222+
envNames := strings.Split(v, ",")
223+
envOpts = append(envOpts, envNames...)
224+
}
225+
226+
if lowerKey == "labels" {
227+
labelNames := strings.Split(v, ",")
228+
labelOpts = append(labelOpts, labelNames...)
229+
}
230+
}
231+
232+
return envOpts, labelOpts
233+
}

pkg/logging/detail_writer.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package logging
18+
19+
import "io"
20+
21+
type DetailWriter struct {
22+
w io.Writer
23+
prefix string
24+
}
25+
26+
func NewDetailWriter(w io.Writer, prefix string) io.Writer {
27+
return &DetailWriter{
28+
w: w,
29+
prefix: prefix,
30+
}
31+
}
32+
33+
func (dw *DetailWriter) Write(p []byte) (n int, err error) {
34+
if len(p) > 0 {
35+
if _, err = dw.w.Write([]byte(dw.prefix)); err != nil {
36+
return 0, err
37+
}
38+
39+
return dw.w.Write(p)
40+
}
41+
return 0, nil
42+
}

pkg/logging/journald_logger.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import (
4343

4444
var JournalDriverLogOpts = []string{
4545
Tag,
46+
Env,
47+
Labels,
4648
}
4749

4850
func JournalLogOptsValidate(logOptMap map[string]string) error {

pkg/logging/json_logger.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ var JSONDriverLogOpts = []string{
4242
LogPath,
4343
MaxSize,
4444
MaxFile,
45+
Env,
46+
Labels,
4547
}
4648

4749
type JSONLogger struct {

pkg/logging/log_viewer.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type LogViewOptions struct {
8282
// Start/end timestampts to filter logs by.
8383
Since string
8484
Until string
85+
86+
// Details enables showing extra details(env and label) in logs.
87+
Details bool
88+
89+
// DetailPrefix is the prefix added when Details is enabled.
90+
DetailPrefix string
8591
}
8692

8793
func (lvo *LogViewOptions) Validate() error {
@@ -150,6 +156,15 @@ func InitContainerLogViewer(containerLabels map[string]string, lvopts LogViewOpt
150156

151157
// Prints all logs for this LogViewer's containers to the provided io.Writers.
152158
func (lv *ContainerLogViewer) PrintLogsTo(stdout, stderr io.Writer) error {
159+
if lv.logViewingOptions.Details {
160+
prefix := " "
161+
if lv.logViewingOptions.DetailPrefix != "" {
162+
prefix = lv.logViewingOptions.DetailPrefix + " "
163+
}
164+
165+
stdout = NewDetailWriter(stdout, prefix)
166+
stderr = NewDetailWriter(stderr, prefix)
167+
}
153168
viewerFunc, err := getLogViewer(lv.loggingConfig.Driver)
154169
if err != nil {
155170
return err

pkg/logging/logging.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const (
4848
MaxSize = "max-size"
4949
MaxFile = "max-file"
5050
Tag = "tag"
51+
Env = "env"
52+
Labels = "labels"
5153
)
5254

5355
type Driver interface {

0 commit comments

Comments
 (0)