github.com/containerd/nerdctl@v1.7.7/pkg/logging/fluentd_logger.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 "math" 22 "net/url" 23 "runtime" 24 "strconv" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/containerd/containerd/runtime/v2/logging" 30 "github.com/containerd/log" 31 "github.com/containerd/nerdctl/pkg/strutil" 32 "github.com/fluent/fluent-logger-golang/fluent" 33 ) 34 35 type FluentdLogger struct { 36 Opts map[string]string 37 fluentClient *fluent.Fluent 38 config *logging.Config 39 } 40 41 const ( 42 fluentAddress = "fluentd-address" 43 fluentdAsync = "fluentd-async" 44 fluentdBufferLimit = "fluentd-buffer-limit" 45 fluentdRetryWait = "fluentd-retry-wait" 46 fluentdMaxRetries = "fluentd-max-retries" 47 fluentdSubSecondPrecision = "fluentd-sub-second-precision" 48 fluentdAsyncReconnectInterval = "fluentd-async-reconnect-interval" 49 fluentRequestAck = "fluentd-request-ack" 50 ) 51 52 var FluentdLogOpts = []string{ 53 fluentAddress, 54 fluentdAsync, 55 fluentdBufferLimit, 56 fluentdRetryWait, 57 fluentdMaxRetries, 58 fluentdSubSecondPrecision, 59 fluentdAsyncReconnectInterval, 60 fluentRequestAck, 61 Tag, 62 } 63 64 const ( 65 defaultBufferLimit = 1024 * 1024 66 defaultHost = "127.0.0.1" 67 defaultPort = 24224 68 defaultProtocol = "tcp" 69 70 defaultMaxRetries = math.MaxInt32 71 defaultRetryWait = 1000 * time.Millisecond 72 73 minReconnectInterval = 100 * time.Millisecond 74 maxReconnectInterval = 10 * time.Second 75 ) 76 77 func FluentdLogOptsValidate(logOptMap map[string]string) error { 78 for key := range logOptMap { 79 if !strutil.InStringSlice(FluentdLogOpts, key) { 80 log.L.Warnf("log-opt %s is ignored for fluentd log driver", key) 81 } 82 } 83 if _, ok := logOptMap[fluentAddress]; !ok { 84 log.L.Warnf("%s is missing for fluentd log driver, the default value %s:%d will be used", fluentAddress, defaultHost, defaultPort) 85 } 86 return nil 87 } 88 89 type fluentdLocation struct { 90 protocol string 91 host string 92 port int 93 path string 94 } 95 96 func (f *FluentdLogger) Init(dataStore, ns, id string) error { 97 return nil 98 } 99 100 func (f *FluentdLogger) PreProcess(_ string, config *logging.Config) error { 101 if runtime.GOOS == "windows" { 102 // TODO: support fluentd on windows 103 return fmt.Errorf("logging to fluentd is not supported on windows") 104 } 105 fluentConfig, err := parseFluentdConfig(f.Opts) 106 if err != nil { 107 return err 108 } 109 fluentClient, err := fluent.New(fluentConfig) 110 if err != nil { 111 return fmt.Errorf("failed to create fluent client: %w", err) 112 } 113 f.fluentClient = fluentClient 114 f.config = config 115 return nil 116 } 117 func (f *FluentdLogger) Process(stdout <-chan string, stderr <-chan string) error { 118 var wg sync.WaitGroup 119 wg.Add(2) 120 fun := func(wg *sync.WaitGroup, dataChan <-chan string, id, namespace, source string) { 121 defer wg.Done() 122 metaData := map[string]string{ 123 "container_id": id, 124 "namespace": namespace, 125 "source": source, 126 } 127 for log := range dataChan { 128 metaData["log"] = log 129 f.fluentClient.PostWithTime(f.Opts[Tag], time.Now(), metaData) 130 } 131 } 132 go fun(&wg, stdout, f.config.ID, f.config.Namespace, "stdout") 133 go fun(&wg, stderr, f.config.ID, f.config.Namespace, "stderr") 134 135 wg.Wait() 136 return nil 137 } 138 139 func (f *FluentdLogger) PostProcess() error { 140 defer f.fluentClient.Close() 141 return nil 142 } 143 144 func parseAddress(address string) (*fluentdLocation, error) { 145 if address == "" { 146 return &fluentdLocation{ 147 protocol: defaultProtocol, 148 host: defaultHost, 149 port: defaultPort, 150 }, nil 151 } 152 if !strings.Contains(address, "://") { 153 address = defaultProtocol + "://" + address 154 } 155 tempURL, err := url.Parse(address) 156 if err != nil { 157 return nil, err 158 } 159 switch tempURL.Scheme { 160 case "unix": 161 if strings.TrimLeft(tempURL.Path, "/") == "" { 162 return nil, fmt.Errorf("unix socket path must not be empty") 163 } 164 return &fluentdLocation{ 165 protocol: tempURL.Scheme, 166 path: tempURL.Path, 167 }, nil 168 case "tcp", "tls": 169 // continue to process below 170 default: 171 return nil, fmt.Errorf("unsupported protocol: %s", tempURL.Scheme) 172 } 173 if tempURL.Path != "" { 174 return nil, fmt.Errorf("path is not supported: %s", tempURL.Path) 175 } 176 host := defaultHost 177 port := defaultPort 178 if h := tempURL.Hostname(); h != "" { 179 host = h 180 } 181 if p := tempURL.Port(); p != "" { 182 portNum, err := strconv.ParseUint(p, 10, 16) 183 if err != nil { 184 return nil, fmt.Errorf("error occurs %v,invalid port", err) 185 } 186 port = int(portNum) 187 } 188 return &fluentdLocation{ 189 protocol: tempURL.Scheme, 190 host: host, 191 port: port, 192 }, nil 193 } 194 195 func ValidateFluentdLoggerOpts(config map[string]string) error { 196 for key := range config { 197 switch key { 198 case Tag: 199 case fluentdBufferLimit: 200 case fluentdMaxRetries: 201 case fluentdRetryWait: 202 case fluentdSubSecondPrecision: 203 case fluentdAsync: 204 case fluentAddress: 205 case fluentdAsyncReconnectInterval: 206 case fluentRequestAck: 207 // Accepted logger opts 208 default: 209 return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key) 210 } 211 } 212 return nil 213 } 214 215 func parseFluentdConfig(config map[string]string) (fluent.Config, error) { 216 result := fluent.Config{} 217 location, err := parseAddress(config[fluentAddress]) 218 if err != nil { 219 return result, fmt.Errorf("error occurs %v,invalid fluentd address (%s)", err, config[fluentAddress]) 220 } 221 bufferLimit := defaultBufferLimit 222 if config[fluentdBufferLimit] != "" { 223 bufferLimit, err = strconv.Atoi(config[fluentdBufferLimit]) 224 if err != nil { 225 return result, fmt.Errorf("error occurs %v,invalid buffer limit (%s)", err, config[fluentdBufferLimit]) 226 } 227 } 228 retryWait := int(defaultRetryWait) 229 if config[fluentdRetryWait] != "" { 230 temp, err := time.ParseDuration(config[fluentdRetryWait]) 231 if err != nil { 232 return result, fmt.Errorf("error occurs %v,invalid retry wait (%s)", err, config[fluentdRetryWait]) 233 } 234 retryWait = int(temp.Milliseconds()) 235 } 236 maxRetries := defaultMaxRetries 237 if config[fluentdMaxRetries] != "" { 238 maxRetries, err = strconv.Atoi(config[fluentdMaxRetries]) 239 if err != nil { 240 return result, fmt.Errorf("error occurs %v,invalid max retries (%s)", err, config[fluentdMaxRetries]) 241 } 242 } 243 async := false 244 if config[fluentdAsync] != "" { 245 async, err = strconv.ParseBool(config[fluentdAsync]) 246 if err != nil { 247 return result, fmt.Errorf("error occurs %v,invalid async (%s)", err, config[fluentdAsync]) 248 } 249 } 250 asyncReconnectInterval := 0 251 if config[fluentdAsyncReconnectInterval] != "" { 252 tempDuration, err := time.ParseDuration(config[fluentdAsyncReconnectInterval]) 253 if err != nil { 254 return result, fmt.Errorf("error occurs %v,invalid async connect interval (%s)", err, config[fluentdAsyncReconnectInterval]) 255 } 256 if tempDuration != 0 && (tempDuration < minReconnectInterval || tempDuration > maxReconnectInterval) { 257 return result, fmt.Errorf("invalid async connect interval (%s), must be between %d and %d", config[fluentdAsyncReconnectInterval], minReconnectInterval.Milliseconds(), maxReconnectInterval.Milliseconds()) 258 } 259 asyncReconnectInterval = int(tempDuration.Milliseconds()) 260 } 261 subSecondPrecision := false 262 if config[fluentdSubSecondPrecision] != "" { 263 subSecondPrecision, err = strconv.ParseBool(config[fluentdSubSecondPrecision]) 264 if err != nil { 265 return result, fmt.Errorf("error occurs %v,invalid sub second precision (%s)", err, config[fluentdSubSecondPrecision]) 266 } 267 } 268 requestAck := false 269 if config[fluentRequestAck] != "" { 270 requestAck, err = strconv.ParseBool(config[fluentRequestAck]) 271 if err != nil { 272 return result, fmt.Errorf("error occurs %v,invalid request ack (%s)", err, config[fluentRequestAck]) 273 } 274 } 275 result = fluent.Config{ 276 FluentPort: location.port, 277 FluentHost: location.host, 278 FluentNetwork: location.protocol, 279 FluentSocketPath: location.path, 280 BufferLimit: bufferLimit, 281 RetryWait: retryWait, 282 MaxRetry: maxRetries, 283 Async: async, 284 AsyncReconnectInterval: asyncReconnectInterval, 285 SubSecondPrecision: subSecondPrecision, 286 RequestAck: requestAck, 287 } 288 return result, nil 289 }