Skip to content

Commit 7e376fb

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

11 files changed

+171
-0
lines changed

cmd/nerdctl/container/container_logs.go

+6
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

+32
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+
}

pkg/api/types/container_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ type ContainerLogsOptions struct {
384384
Since string
385385
// Show logs before a timestamp (e.g., 2013-01-02T13:23:37Z) or relative (e.g., 42m for 42 minutes).
386386
Until string
387+
// Details specifies whether to show extra details provided to logs
388+
Details bool
387389
}
388390

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

pkg/cmd/container/logs.go

+64
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package container
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223
"os"
2324
"os/signal"
25+
"strings"
2426
"syscall"
2527

2628
containerd "github.com/containerd/containerd/v2/client"
@@ -102,6 +104,44 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
102104
}
103105
}
104106

107+
detailPrefix := ""
108+
if options.Details {
109+
if logConfigJSON, ok := l["nerdctl/log-config"]; ok {
110+
type LogConfig struct {
111+
Opts map[string]string `json:"opts"`
112+
}
113+
114+
e, err := getContainerEnvs(ctx, found.Container)
115+
if err != nil {
116+
return err
117+
}
118+
119+
var logConfig LogConfig
120+
if err := json.Unmarshal([]byte(logConfigJSON), &logConfig); err == nil {
121+
var optPairs []string
122+
123+
for k, v := range logConfig.Opts {
124+
lowerKey := strings.ToLower(k)
125+
if lowerKey == "env" {
126+
if env, ok := e[v]; ok {
127+
optPairs = append(optPairs, fmt.Sprintf("%s=%s", v, env))
128+
}
129+
}
130+
131+
if lowerKey == "labels" {
132+
if label, ok := l[v]; ok {
133+
optPairs = append(optPairs, fmt.Sprintf("%s=%s", v, label))
134+
}
135+
}
136+
}
137+
138+
if len(optPairs) > 0 {
139+
detailPrefix = strings.Join(optPairs, ",")
140+
}
141+
}
142+
}
143+
}
144+
105145
logViewOpts := logging.LogViewOptions{
106146
ContainerID: found.Container.ID(),
107147
Namespace: l[labels.Namespace],
@@ -112,6 +152,8 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
112152
Tail: options.Tail,
113153
Since: options.Since,
114154
Until: options.Until,
155+
Details: options.Details,
156+
DetailPrefix: detailPrefix,
115157
}
116158
logViewer, err := logging.InitContainerLogViewer(l, logViewOpts, stopChannel, options.GOptions.Experimental)
117159
if err != nil {
@@ -146,3 +188,25 @@ func getLogPath(ctx context.Context, container containerd.Container) (string, er
146188

147189
return meta.LogPath, nil
148190
}
191+
192+
func getContainerEnvs(ctx context.Context, container containerd.Container) (map[string]string, error) {
193+
envMap := make(map[string]string)
194+
195+
spec, err := container.Spec(ctx)
196+
if err != nil {
197+
return nil, err
198+
}
199+
200+
if spec.Process == nil {
201+
return envMap, nil
202+
}
203+
204+
for _, env := range spec.Process.Env {
205+
parts := strings.SplitN(env, "=", 2)
206+
if len(parts) == 2 {
207+
envMap[parts[0]] = parts[1]
208+
}
209+
}
210+
211+
return envMap, nil
212+
}

pkg/logging/detail_writer.go

+42
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/fluentd_logger.go

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ var FluentdLogOpts = []string{
6262
fluentdAsyncReconnectInterval,
6363
fluentRequestAck,
6464
Tag,
65+
Env,
66+
Labels,
6567
}
6668

6769
const (

pkg/logging/journald_logger.go

+2
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

+2
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

+15
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

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const (
4747
MaxSize = "max-size"
4848
MaxFile = "max-file"
4949
Tag = "tag"
50+
Env = "env"
51+
Labels = "labels"
5052
)
5153

5254
type Driver interface {

pkg/logging/syslog_logger.go

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ var syslogOpts = []string{
5656
syslogTLSSkipVerify,
5757
syslogFormat,
5858
Tag,
59+
Env,
60+
Labels,
5961
}
6062

6163
var syslogFacilities = map[string]syslog.Priority{

0 commit comments

Comments
 (0)