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 }