github.com/containerd/nerdctl@v1.7.7/pkg/logging/log_viewer.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 logging
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  
    26  	"github.com/containerd/log"
    27  	"github.com/containerd/nerdctl/pkg/labels/k8slabels"
    28  )
    29  
    30  // Type alias for functions which write out logs to the provided stdout/stderr Writers.
    31  // Depending on the provided `LogViewOptions.Follow` option, the function may block
    32  // indefinitely until something is sent through the `stopChannel`.
    33  type LogViewerFunc func(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error
    34  
    35  var logViewers = make(map[string]LogViewerFunc)
    36  
    37  // Registers a LogViewerFunc for the
    38  func RegisterLogViewer(driverName string, lvfn LogViewerFunc) {
    39  	if v, ok := logViewers[driverName]; ok {
    40  		log.L.Warnf("A LogViewerFunc with name %q has already been registered: %#v, overriding with %#v either way", driverName, v, lvfn)
    41  	}
    42  	logViewers[driverName] = lvfn
    43  }
    44  
    45  func init() {
    46  	RegisterLogViewer("json-file", viewLogsJSONFile)
    47  	RegisterLogViewer("journald", viewLogsJournald)
    48  	RegisterLogViewer("cri", viewLogsCRI)
    49  }
    50  
    51  // Returns a LogViewerFunc for the provided logging driver name.
    52  func getLogViewer(driverName string) (LogViewerFunc, error) {
    53  	lv, ok := logViewers[driverName]
    54  	if !ok {
    55  		return nil, fmt.Errorf("no log viewer type registered for logging driver %q", driverName)
    56  	}
    57  	return lv, nil
    58  }
    59  
    60  // Set of options passable to log viewers.
    61  type LogViewOptions struct {
    62  	// Identifier (ID) of the container and namespace it's in.
    63  	ContainerID string
    64  	Namespace   string
    65  
    66  	// Absolute path to the nerdctl datastore's root.
    67  	DatastoreRootPath string
    68  
    69  	// LogPath specify the log path for container created via CRI
    70  	LogPath string
    71  
    72  	// Whether or not to follow the output of the container logs.
    73  	Follow bool
    74  
    75  	// Whether or not to print timestampts for each line.
    76  	Timestamps bool
    77  
    78  	// Uint representing the number of most recent log entries to display. 0 = "all".
    79  	Tail uint
    80  
    81  	// Start/end timestampts to filter logs by.
    82  	Since string
    83  	Until string
    84  }
    85  
    86  func (lvo *LogViewOptions) Validate() error {
    87  	if lvo.ContainerID == "" || lvo.Namespace == "" {
    88  		return fmt.Errorf("log viewing options require a ContainerID and Namespace: %#v", lvo)
    89  	}
    90  
    91  	if lvo.DatastoreRootPath == "" || !filepath.IsAbs(lvo.DatastoreRootPath) {
    92  		abs, err := filepath.Abs(lvo.DatastoreRootPath)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		log.L.Warnf("given relative datastore path %q, transformed it to absolute path: %q", lvo.DatastoreRootPath, abs)
    97  		lvo.DatastoreRootPath = abs
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  // Implements functionality for loading the logging configuration and
   104  // fetching/outputting container logs based on its internal LogViewOptions.
   105  type ContainerLogViewer struct {
   106  	// Logging configuration.
   107  	loggingConfig LogConfig
   108  
   109  	// Log viewing options and filters.
   110  	logViewingOptions LogViewOptions
   111  
   112  	// Channel to send stop events to the viewer.
   113  	stopChannel chan os.Signal
   114  }
   115  
   116  // Validates the given LogViewOptions, loads the logging config for the
   117  // given container and returns a ContainerLogViewer.
   118  func InitContainerLogViewer(containerLabels map[string]string, lvopts LogViewOptions, stopChannel chan os.Signal, experimental bool) (contlv *ContainerLogViewer, err error) {
   119  	var lcfg LogConfig
   120  	if _, ok := containerLabels[k8slabels.ContainerType]; ok {
   121  		lcfg.Driver = "cri"
   122  	} else {
   123  		if err := lvopts.Validate(); err != nil {
   124  			return nil, fmt.Errorf("invalid LogViewOptions provided (%#v): %s", lvopts, err)
   125  		}
   126  
   127  		lcfg, err = LoadLogConfig(lvopts.DatastoreRootPath, lvopts.Namespace, lvopts.ContainerID)
   128  		if err != nil {
   129  			return nil, fmt.Errorf("failed to load logging config: %s", err)
   130  		}
   131  	}
   132  
   133  	if lcfg.Driver == "cri" && !experimental {
   134  		return nil, fmt.Errorf("the `cri` log viewer requires nerdctl to be running in experimental mode")
   135  	}
   136  
   137  	lv := &ContainerLogViewer{
   138  		loggingConfig:     lcfg,
   139  		logViewingOptions: lvopts,
   140  		stopChannel:       stopChannel,
   141  	}
   142  
   143  	return lv, nil
   144  }
   145  
   146  // Prints all logs for this LogViewer's containers to the provided io.Writers.
   147  func (lv *ContainerLogViewer) PrintLogsTo(stdout, stderr io.Writer) error {
   148  	viewerFunc, err := getLogViewer(lv.loggingConfig.Driver)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	return viewerFunc(lv.logViewingOptions, stdout, stderr, lv.stopChannel)
   154  }
   155  
   156  // Convenience wrapper for exec.LookPath.
   157  func checkExecutableAvailableInPath(executable string) bool {
   158  	_, err := exec.LookPath(executable)
   159  	return err == nil
   160  }