github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/logs.go (about) 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 container 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "os/signal" 24 "syscall" 25 26 "github.com/containerd/containerd" 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/log" 29 "github.com/containerd/nerdctl/v2/pkg/api/types" 30 "github.com/containerd/nerdctl/v2/pkg/api/types/cri" 31 "github.com/containerd/nerdctl/v2/pkg/clientutil" 32 "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" 33 "github.com/containerd/nerdctl/v2/pkg/labels" 34 "github.com/containerd/nerdctl/v2/pkg/labels/k8slabels" 35 "github.com/containerd/nerdctl/v2/pkg/logging" 36 ) 37 38 func Logs(ctx context.Context, client *containerd.Client, container string, options types.ContainerLogsOptions) error { 39 dataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address) 40 if err != nil { 41 return err 42 } 43 44 switch options.GOptions.Namespace { 45 case "moby": 46 log.G(ctx).Warn("Currently, `nerdctl logs` only supports containers created with `nerdctl run -d` or CRI") 47 } 48 49 stopChannel := make(chan os.Signal, 1) 50 // catch OS signals: 51 signal.Notify(stopChannel, syscall.SIGTERM, syscall.SIGINT) 52 53 walker := &containerwalker.ContainerWalker{ 54 Client: client, 55 OnFound: func(ctx context.Context, found containerwalker.Found) error { 56 if found.MatchCount > 1 { 57 return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) 58 } 59 l, err := found.Container.Labels(ctx) 60 if err != nil { 61 return err 62 } 63 64 logPath, err := getLogPath(ctx, found.Container) 65 if err != nil { 66 return err 67 } 68 69 follow := options.Follow 70 if follow { 71 task, err := found.Container.Task(ctx, nil) 72 if err != nil { 73 if !errdefs.IsNotFound(err) { 74 return err 75 } 76 follow = false 77 } else { 78 status, err := task.Status(ctx) 79 if err != nil { 80 return err 81 } 82 if status.Status != containerd.Running { 83 follow = false 84 } else { 85 waitCh, err := task.Wait(ctx) 86 if err != nil { 87 return fmt.Errorf("failed to get wait channel for task %#v: %s", task, err) 88 } 89 90 // Setup goroutine to send stop event if container task finishes: 91 go func() { 92 <-waitCh 93 log.G(ctx).Debugf("container task has finished, sending kill signal to log viewer") 94 stopChannel <- os.Interrupt 95 }() 96 } 97 } 98 } 99 100 logViewOpts := logging.LogViewOptions{ 101 ContainerID: found.Container.ID(), 102 Namespace: l[labels.Namespace], 103 DatastoreRootPath: dataStore, 104 LogPath: logPath, 105 Follow: follow, 106 Timestamps: options.Timestamps, 107 Tail: options.Tail, 108 Since: options.Since, 109 Until: options.Until, 110 } 111 logViewer, err := logging.InitContainerLogViewer(l, logViewOpts, stopChannel, options.GOptions.Experimental) 112 if err != nil { 113 return err 114 } 115 116 return logViewer.PrintLogsTo(options.Stdout, options.Stderr) 117 }, 118 } 119 n, err := walker.Walk(ctx, container) 120 if err != nil { 121 return err 122 } else if n == 0 { 123 return fmt.Errorf("no such container %s", container) 124 } 125 return nil 126 } 127 128 func getLogPath(ctx context.Context, container containerd.Container) (string, error) { 129 extensions, err := container.Extensions(ctx) 130 if err != nil { 131 return "", fmt.Errorf("get extensions for container %s,failed: %#v", container.ID(), err) 132 } 133 metaData := extensions[k8slabels.ContainerMetadataExtension] 134 var meta cri.ContainerMetadata 135 if metaData != nil { 136 err = meta.UnmarshalJSON(metaData.GetValue()) 137 if err != nil { 138 return "", fmt.Errorf("unmarshal extensions for container %s,failed: %#v", container.ID(), err) 139 } 140 } 141 142 return meta.LogPath, nil 143 }