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  }