Skip to content

Commit 017e286

Browse files
authored
Merge pull request containerd#4009 from SpiffyEight77/feat/support-logs-details
feat: add --details flag to logs command
2 parents 9e058bb + d8ef366 commit 017e286

File tree

10 files changed

+190
-2
lines changed

10 files changed

+190
-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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,31 @@ func TestNoneLoggerHasNoLogURI(t *testing.T) {
355355
testCase.Expected = test.Expects(1, nil, nil)
356356
testCase.Run(t)
357357
}
358+
359+
func TestLogsWithDetails(t *testing.T) {
360+
testCase := nerdtest.Setup()
361+
362+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
363+
helpers.Ensure("run", "-d", "--log-driver", "json-file",
364+
"--log-opt", "max-size=10m",
365+
"--log-opt", "max-file=3",
366+
"--log-opt", "env=ENV",
367+
"--env", "ENV=foo",
368+
"--log-opt", "labels=LABEL",
369+
"--label", "LABEL=bar",
370+
"--name", data.Identifier(), testutil.CommonImage,
371+
"sh", "-ec", "echo baz")
372+
}
373+
374+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
375+
helpers.Anyhow("rm", "-f", data.Identifier())
376+
}
377+
378+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
379+
return helpers.Command("logs", "--details", data.Identifier())
380+
}
381+
382+
testCase.Expected = test.Expects(0, nil, expect.Contains("ENV=foo", "LABEL=bar", "baz"))
383+
384+
testCase.Run(t)
385+
}

docs/command-reference.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,12 @@ Logging flags:
328328
- :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`.
329329
- Default: `<data-root>/<containerd-socket-hash>/<namespace>/<container-id>/<container-id>-json.log`
330330
- Example: `/var/lib/nerdctl/1935db59/containers/default/<container-id>/<container-id>-json.log`
331+
- :whale: `--log-opt labels=production_status,geo`: A comma-separated list of logging-related labels this daemon accepts.
332+
- :whale: `--log-opt env=os,customer`: A comma-separated list of logging-related environment variables this daemon accepts.
331333
- :whale: `--log-driver=journald`: Writes log messages to `journald`. The `journald` daemon must be running on the host machine.
332334
- :whale: `--log-opt=tag=<TEMPLATE>`: Specify template to set `SYSLOG_IDENTIFIER` value in journald logs.
335+
- :whale: `--log-opt labels=production_status,geo`: A comma-separated list of logging-related labels this daemon accepts.
336+
- :whale: `--log-opt env=os,customer`: A comma-separated list of logging-related environment variables this daemon accepts.
333337
- :whale: `--log-driver=fluentd`: Writes log messages to `fluentd`. The `fluentd` daemon must be running on the host machine.
334338
- The `fluentd` logging driver supports the following logging options:
335339
- :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
@@ -404,6 +404,8 @@ type ContainerLogsOptions struct {
404404
Since string
405405
// Show logs before a timestamp (e.g., 2013-01-02T13:23:37Z) or relative (e.g., 42m for 42 minutes).
406406
Until string
407+
// Details specifies whether to show extra details provided to logs
408+
Details bool
407409
}
408410

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

pkg/cmd/container/logs.go

Lines changed: 87 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,46 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
102105
}
103106
}
104107

108+
var detailPrefix string
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 logCfg logConfig
121+
var optPairs []string
122+
123+
if err := json.Unmarshal([]byte(logConfigJSON), &logCfg); err == nil {
124+
envOpts, labelOpts := getLogOpts(logCfg.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+
} else {
143+
log.L.Warn("failed to parse `--details` option, detailed information might not be displayed")
144+
}
145+
}
146+
}
147+
105148
logViewOpts := logging.LogViewOptions{
106149
ContainerID: found.Container.ID(),
107150
Namespace: l[labels.Namespace],
@@ -112,6 +155,8 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
112155
Tail: options.Tail,
113156
Since: options.Since,
114157
Until: options.Until,
158+
Details: options.Details,
159+
DetailPrefix: &detailPrefix,
115160
}
116161
logViewer, err := logging.InitContainerLogViewer(l, logViewOpts, stopChannel, options.GOptions.Experimental)
117162
if err != nil {
@@ -146,3 +191,45 @@ func getLogPath(ctx context.Context, container containerd.Container) (string, er
146191

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

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: 14 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,14 @@ 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+
if lv.logViewingOptions.DetailPrefix != nil {
161+
prefix := *lv.logViewingOptions.DetailPrefix + " "
162+
stdout = NewDetailWriter(stdout, prefix)
163+
stderr = NewDetailWriter(stderr, prefix)
164+
}
165+
166+
}
153167
viewerFunc, err := getLogViewer(lv.loggingConfig.Driver)
154168
if err != nil {
155169
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)