github.com/containerd/nerdctl@v1.7.7/pkg/logging/logging.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 "bufio" 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "sort" 29 "sync" 30 31 "github.com/containerd/containerd/runtime/v2/logging" 32 "github.com/containerd/errdefs" 33 "github.com/containerd/log" 34 "github.com/muesli/cancelreader" 35 ) 36 37 const ( 38 // MagicArgv1 is the magic argv1 for the containerd runtime v2 logging plugin mode. 39 MagicArgv1 = "_NERDCTL_INTERNAL_LOGGING" 40 LogPath = "log-path" 41 MaxSize = "max-size" 42 MaxFile = "max-file" 43 Tag = "tag" 44 ) 45 46 type Driver interface { 47 Init(dataStore, ns, id string) error 48 PreProcess(dataStore string, config *logging.Config) error 49 Process(stdout <-chan string, stderr <-chan string) error 50 PostProcess() error 51 } 52 53 type DriverFactory func(map[string]string) (Driver, error) 54 type LogOptsValidateFunc func(logOptMap map[string]string) error 55 56 var drivers = make(map[string]DriverFactory) 57 var driversLogOptsValidateFunctions = make(map[string]LogOptsValidateFunc) 58 59 func ValidateLogOpts(logDriver string, logOpts map[string]string) error { 60 if value, ok := driversLogOptsValidateFunctions[logDriver]; ok && value != nil { 61 return value(logOpts) 62 } 63 return nil 64 } 65 66 func RegisterDriver(name string, f DriverFactory, validateFunc LogOptsValidateFunc) { 67 drivers[name] = f 68 driversLogOptsValidateFunctions[name] = validateFunc 69 } 70 71 func Drivers() []string { 72 var ss []string // nolint: prealloc 73 for f := range drivers { 74 ss = append(ss, f) 75 } 76 sort.Strings(ss) 77 return ss 78 } 79 80 func GetDriver(name string, opts map[string]string) (Driver, error) { 81 driverFactory, ok := drivers[name] 82 if !ok { 83 return nil, fmt.Errorf("unknown logging driver %q: %w", name, errdefs.ErrNotFound) 84 } 85 return driverFactory(opts) 86 } 87 88 func init() { 89 RegisterDriver("json-file", func(opts map[string]string) (Driver, error) { 90 return &JSONLogger{Opts: opts}, nil 91 }, JSONFileLogOptsValidate) 92 RegisterDriver("journald", func(opts map[string]string) (Driver, error) { 93 return &JournaldLogger{Opts: opts}, nil 94 }, JournalLogOptsValidate) 95 RegisterDriver("fluentd", func(opts map[string]string) (Driver, error) { 96 return &FluentdLogger{Opts: opts}, nil 97 }, FluentdLogOptsValidate) 98 RegisterDriver("syslog", func(opts map[string]string) (Driver, error) { 99 return &SyslogLogger{Opts: opts}, nil 100 }, SyslogOptsValidate) 101 } 102 103 // Main is the entrypoint for the containerd runtime v2 logging plugin mode. 104 // 105 // Should be called only if argv1 == MagicArgv1. 106 func Main(argv2 string) error { 107 fn, err := loggerFunc(argv2) 108 if err != nil { 109 return err 110 } 111 logging.Run(fn) 112 return nil 113 } 114 115 // LogConfig is marshalled as "log-config.json" 116 type LogConfig struct { 117 Driver string `json:"driver"` 118 Opts map[string]string `json:"opts,omitempty"` 119 LogURI string `json:"-"` 120 } 121 122 // LogConfigFilePath returns the path of log-config.json 123 func LogConfigFilePath(dataStore, ns, id string) string { 124 return filepath.Join(dataStore, "containers", ns, id, "log-config.json") 125 } 126 127 // LoadLogConfig loads the log-config.json for the afferrent container store 128 func LoadLogConfig(dataStore, ns, id string) (LogConfig, error) { 129 logConfig := LogConfig{} 130 131 logConfigFilePath := LogConfigFilePath(dataStore, ns, id) 132 logConfigData, err := os.ReadFile(logConfigFilePath) 133 if err != nil { 134 return logConfig, fmt.Errorf("failed to read log config file %q: %s", logConfigFilePath, err) 135 } 136 137 err = json.Unmarshal(logConfigData, &logConfig) 138 if err != nil { 139 return logConfig, fmt.Errorf("failed to load JSON logging config file %q: %s", logConfigFilePath, err) 140 } 141 return logConfig, nil 142 } 143 144 func loggingProcessAdapter(ctx context.Context, driver Driver, dataStore string, config *logging.Config) error { 145 if err := driver.PreProcess(dataStore, config); err != nil { 146 return err 147 } 148 149 stdoutR, err := cancelreader.NewReader(config.Stdout) 150 if err != nil { 151 return err 152 } 153 stderrR, err := cancelreader.NewReader(config.Stderr) 154 if err != nil { 155 return err 156 } 157 go func() { 158 <-ctx.Done() // delivered on SIGTERM 159 stdoutR.Cancel() 160 stderrR.Cancel() 161 }() 162 163 var wg sync.WaitGroup 164 wg.Add(3) 165 stdout := make(chan string, 10000) 166 stderr := make(chan string, 10000) 167 processLogFunc := func(reader io.Reader, dataChan chan string) { 168 defer wg.Done() 169 defer close(dataChan) 170 scanner := bufio.NewScanner(reader) 171 for scanner.Scan() { 172 if scanner.Err() != nil { 173 log.L.Errorf("failed to read log: %v", scanner.Err()) 174 return 175 } 176 dataChan <- scanner.Text() 177 } 178 } 179 180 go processLogFunc(stdoutR, stdout) 181 go processLogFunc(stderrR, stderr) 182 go func() { 183 defer wg.Done() 184 driver.Process(stdout, stderr) 185 }() 186 wg.Wait() 187 return driver.PostProcess() 188 } 189 190 func loggerFunc(dataStore string) (logging.LoggerFunc, error) { 191 if dataStore == "" { 192 return nil, errors.New("got empty data store") 193 } 194 return func(ctx context.Context, config *logging.Config, ready func() error) error { 195 if config.Namespace == "" || config.ID == "" { 196 return errors.New("got invalid config") 197 } 198 logConfigFilePath := LogConfigFilePath(dataStore, config.Namespace, config.ID) 199 if _, err := os.Stat(logConfigFilePath); err == nil { 200 logConfig, err := LoadLogConfig(dataStore, config.Namespace, config.ID) 201 if err != nil { 202 return err 203 } 204 driver, err := GetDriver(logConfig.Driver, logConfig.Opts) 205 if err != nil { 206 return err 207 } 208 if err := ready(); err != nil { 209 return err 210 } 211 212 return loggingProcessAdapter(ctx, driver, dataStore, config) 213 } else if !errors.Is(err, os.ErrNotExist) { 214 // the file does not exist if the container was created with nerdctl < 0.20 215 return err 216 } 217 return nil 218 }, nil 219 }