github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/logging/logconfig/sinks.go (about) 1 package logconfig 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/eapache/channels" 8 "github.com/go-kit/kit/log" 9 "github.com/hyperledger/burrow/logging" 10 "github.com/hyperledger/burrow/logging/loggers" 11 "github.com/hyperledger/burrow/logging/structure" 12 ) 13 14 // This file contains definitions for a configurable output graph for the 15 // logging system. 16 17 type outputType string 18 type transformType string 19 type filterMode string 20 21 const ( 22 // OutputType 23 NoOutput outputType = "" 24 Stdout outputType = "stdout" 25 Stderr outputType = "stderr" 26 File outputType = "file" 27 28 // TransformType 29 NoTransform transformType = "" 30 // Filter log lines 31 Filter transformType = "filter" 32 // Remove key-val pairs from each log line 33 Prune transformType = "prune" 34 // Add key value pairs to each log line 35 Label transformType = "label" 36 Capture transformType = "capture" 37 Sort transformType = "sort" 38 Vectorise transformType = "vectorise" 39 40 // TODO [Silas]: add 'flush on exit' transform which flushes the buffer of 41 // CaptureLogger to its OutputLogger a non-passthrough capture when an exit 42 // signal is detected or some other exceptional thing happens 43 44 NoFilterMode filterMode = "" 45 IncludeWhenAllMatch filterMode = "include_when_all_match" 46 IncludeWhenAnyMatches filterMode = "include_when_any_match" 47 ExcludeWhenAllMatch filterMode = "exclude_when_all_match" 48 ExcludeWhenAnyMatches filterMode = "exclude_when_any_match" 49 ) 50 51 // Only include log lines matching the filter so negate the predicate in filter 52 func (mode filterMode) Include() bool { 53 switch mode { 54 case IncludeWhenAllMatch, IncludeWhenAnyMatches: 55 return true 56 default: 57 return false 58 } 59 } 60 61 // The predicate should only evaluate true if all the key value predicates match 62 func (mode filterMode) MatchAll() bool { 63 switch mode { 64 case IncludeWhenAllMatch, ExcludeWhenAllMatch: 65 return true 66 default: 67 return false 68 } 69 } 70 71 // Exclude log lines that match the predicate 72 func (mode filterMode) Exclude() bool { 73 return !mode.Include() 74 } 75 76 // The predicate should evaluate true if at least one of the key value predicates matches 77 func (mode filterMode) MatchAny() bool { 78 return !mode.MatchAll() 79 } 80 81 // Sink configuration types 82 type ( 83 // Outputs 84 // TODO: reintroduce syslog removed when we dropped log15 dependency 85 SyslogConfig struct { 86 Url string 87 Tag string 88 } 89 90 FileConfig struct { 91 Path string 92 } 93 94 OutputConfig struct { 95 OutputType outputType 96 Format string 97 *FileConfig `json:",omitempty" toml:",omitempty"` 98 *SyslogConfig `json:",omitempty" toml:",omitempty"` 99 } 100 101 // Transforms 102 LabelConfig struct { 103 Labels map[string]string 104 Prefix bool 105 } 106 107 PruneConfig struct { 108 Keys []string 109 IncludeKeys bool 110 } 111 112 CaptureConfig struct { 113 Name string 114 BufferCap int 115 Passthrough bool 116 } 117 118 // Generates true if KeyRegex matches a log line key and ValueRegex matches that key's value. 119 // If ValueRegex is empty then returns true if any key matches 120 // If KeyRegex is empty then returns true if any value matches 121 KeyValuePredicateConfig struct { 122 KeyRegex string 123 ValueRegex string 124 } 125 126 // Filter types 127 FilterConfig struct { 128 FilterMode filterMode 129 // Predicates to match a log line against using FilterMode 130 Predicates []*KeyValuePredicateConfig 131 } 132 133 SortConfig struct { 134 // Sort keys-values with keys in this list first 135 Keys []string 136 } 137 138 TransformConfig struct { 139 TransformType transformType 140 LabelConfig *LabelConfig `json:",omitempty" toml:",omitempty"` 141 PruneConfig *PruneConfig `json:",omitempty" toml:",omitempty"` 142 CaptureConfig *CaptureConfig `json:",omitempty" toml:",omitempty"` 143 FilterConfig *FilterConfig `json:",omitempty" toml:",omitempty"` 144 SortConfig *SortConfig `json:",omitempty" toml:",omitempty"` 145 } 146 147 // Sink 148 // A Sink describes a logger that logs to zero or one output and logs to zero or more child sinks. 149 // before transmitting its log it applies zero or one transforms to the stream of log lines. 150 // by chaining together many Sinks arbitrary transforms to and multi 151 SinkConfig struct { 152 Transform *TransformConfig `json:",omitempty" toml:",omitempty"` 153 Sinks []*SinkConfig `json:",omitempty" toml:",omitempty"` 154 Output *OutputConfig `json:",omitempty" toml:",omitempty"` 155 } 156 ) 157 158 // Builders 159 func Sink() *SinkConfig { 160 return &SinkConfig{} 161 } 162 163 func (sc *SinkConfig) MustLogger() *logging.Logger { 164 return sc.LoggingConfig().MustLogger() 165 } 166 167 func (sc *SinkConfig) Logger() (*logging.Logger, error) { 168 return sc.LoggingConfig().Logger() 169 } 170 171 // Wrap this sink as RootSink of LoggingCOnfig 172 func (sc *SinkConfig) LoggingConfig() *LoggingConfig { 173 lc := New() 174 lc.RootSink = sc 175 return lc 176 } 177 178 func (sc *SinkConfig) AddSinks(sinks ...*SinkConfig) *SinkConfig { 179 sc.Sinks = append(sc.Sinks, sinks...) 180 return sc 181 } 182 183 func (sc *SinkConfig) SetTransform(transform *TransformConfig) *SinkConfig { 184 sc.Transform = transform 185 return sc 186 } 187 188 func (sc *SinkConfig) FilterScope(scope string) *SinkConfig { 189 return sc.SetTransform(FilterTransform(IncludeWhenAllMatch, structure.ScopeKey, scope)) 190 } 191 192 func (sc *SinkConfig) Terminal() *SinkConfig { 193 return sc.SetOutput(StderrOutput().SetFormat(TerminalFormat)) 194 } 195 196 func (sc *SinkConfig) SetOutput(output *OutputConfig) *SinkConfig { 197 sc.Output = output 198 return sc 199 } 200 201 func (outputConfig *OutputConfig) SetFormat(format string) *OutputConfig { 202 outputConfig.Format = format 203 return outputConfig 204 } 205 206 func StdoutOutput() *OutputConfig { 207 return &OutputConfig{ 208 OutputType: Stdout, 209 } 210 } 211 212 func StderrOutput() *OutputConfig { 213 return &OutputConfig{ 214 OutputType: Stderr, 215 } 216 } 217 218 func FileOutput(path string) *OutputConfig { 219 return &OutputConfig{ 220 OutputType: File, 221 FileConfig: &FileConfig{ 222 Path: path, 223 }, 224 } 225 } 226 227 // Transforms 228 229 func CaptureTransform(name string, bufferCap int, passthrough bool) *TransformConfig { 230 return &TransformConfig{ 231 TransformType: Capture, 232 CaptureConfig: &CaptureConfig{ 233 Name: name, 234 BufferCap: bufferCap, 235 Passthrough: passthrough, 236 }, 237 } 238 } 239 240 func LabelTransform(prefix bool, labelKeyvals ...string) *TransformConfig { 241 length := len(labelKeyvals) / 2 242 labels := make(map[string]string, length) 243 for i := 0; i < 2*length; i += 2 { 244 labels[labelKeyvals[i]] = labelKeyvals[i+1] 245 } 246 return &TransformConfig{ 247 TransformType: Label, 248 LabelConfig: &LabelConfig{ 249 Prefix: prefix, 250 Labels: labels, 251 }, 252 } 253 } 254 255 func OnlyTransform(keys ...string) *TransformConfig { 256 return &TransformConfig{ 257 TransformType: Prune, 258 PruneConfig: &PruneConfig{ 259 Keys: keys, 260 IncludeKeys: true, 261 }, 262 } 263 } 264 265 func PruneTransform(keys ...string) *TransformConfig { 266 return &TransformConfig{ 267 TransformType: Prune, 268 PruneConfig: &PruneConfig{ 269 Keys: keys, 270 }, 271 } 272 } 273 274 func FilterTransform(fmode filterMode, keyValueRegexes ...string) *TransformConfig { 275 if len(keyValueRegexes)%2 == 1 { 276 keyValueRegexes = append(keyValueRegexes, "") 277 } 278 length := len(keyValueRegexes) / 2 279 predicates := make([]*KeyValuePredicateConfig, length) 280 for i := 0; i < length; i++ { 281 kv := i * 2 282 predicates[i] = &KeyValuePredicateConfig{ 283 KeyRegex: keyValueRegexes[kv], 284 ValueRegex: keyValueRegexes[kv+1], 285 } 286 } 287 return &TransformConfig{ 288 TransformType: Filter, 289 FilterConfig: &FilterConfig{ 290 FilterMode: fmode, 291 Predicates: predicates, 292 }, 293 } 294 } 295 296 func (filterConfig *FilterConfig) SetFilterMode(filterMode filterMode) *FilterConfig { 297 filterConfig.FilterMode = filterMode 298 return filterConfig 299 } 300 301 func (filterConfig *FilterConfig) AddPredicate(keyRegex, valueRegex string) *FilterConfig { 302 filterConfig.Predicates = append(filterConfig.Predicates, &KeyValuePredicateConfig{ 303 KeyRegex: keyRegex, 304 ValueRegex: valueRegex, 305 }) 306 return filterConfig 307 } 308 309 func SortTransform(keys ...string) *TransformConfig { 310 return &TransformConfig{ 311 TransformType: Sort, 312 SortConfig: &SortConfig{ 313 Keys: keys, 314 }, 315 } 316 } 317 318 func VectoriseTransform() *TransformConfig { 319 return &TransformConfig{ 320 TransformType: Vectorise, 321 } 322 } 323 324 // Logger formation 325 func (sc *SinkConfig) BuildLogger() (log.Logger, map[string]*loggers.CaptureLogger, error) { 326 if sc == nil { 327 return log.NewNopLogger(), nil, nil 328 } 329 return BuildLoggerFromSinkConfig(sc, make(map[string]*loggers.CaptureLogger)) 330 } 331 332 func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, captures map[string]*loggers.CaptureLogger) (log.Logger, 333 map[string]*loggers.CaptureLogger, error) { 334 335 if sinkConfig == nil { 336 return log.NewNopLogger(), captures, nil 337 } 338 numSinks := len(sinkConfig.Sinks) 339 outputLoggers := make([]log.Logger, numSinks, numSinks+1) 340 // We need a depth-first post-order over the output loggers so we'll keep 341 // recurring into children sinks we reach a terminal sink (with no children) 342 for i, sc := range sinkConfig.Sinks { 343 var err error 344 outputLoggers[i], captures, err = BuildLoggerFromSinkConfig(sc, captures) 345 if err != nil { 346 return nil, nil, err 347 } 348 } 349 350 // Grab the outputs after we have terminated any children sinks above 351 if sinkConfig.Output != nil && sinkConfig.Output.OutputType != NoOutput { 352 l, err := BuildOutputLogger(sinkConfig.Output) 353 if err != nil { 354 return nil, captures, err 355 } 356 outputLoggers = append(outputLoggers, l) 357 } 358 359 outputLogger := loggers.NewMultipleOutputLogger(outputLoggers...) 360 361 if sinkConfig.Transform != nil && sinkConfig.Transform.TransformType != NoTransform { 362 return BuildTransformLoggerPassthrough(sinkConfig.Transform, captures, outputLogger) 363 } 364 return outputLogger, captures, nil 365 } 366 367 func BuildOutputLogger(outputConfig *OutputConfig) (log.Logger, error) { 368 switch outputConfig.OutputType { 369 case NoOutput: 370 return log.NewNopLogger(), nil 371 case File: 372 return loggers.NewFileLogger(outputConfig.FileConfig.Path, outputConfig.Format) 373 case Stdout: 374 return loggers.NewStreamLogger(os.Stdout, outputConfig.Format) 375 case Stderr: 376 return loggers.NewStreamLogger(os.Stderr, outputConfig.Format) 377 default: 378 return nil, fmt.Errorf("could not build logger for output: '%s'", 379 outputConfig.OutputType) 380 } 381 } 382 383 func BuildTransformLoggerPassthrough(transformConfig *TransformConfig, captures map[string]*loggers.CaptureLogger, 384 outputLogger log.Logger) (log.Logger, map[string]*loggers.CaptureLogger, error) { 385 386 transformThenOutputLogger, captures, err := BuildTransformLogger(transformConfig, captures, outputLogger) 387 if err != nil { 388 return nil, nil, err 389 } 390 // send signals through captures so they can be flushed 391 if transformConfig.TransformType == Capture { 392 return transformThenOutputLogger, captures, nil 393 } 394 return signalPassthroughLogger(outputLogger, transformThenOutputLogger), captures, nil 395 } 396 397 func BuildTransformLogger(transformConfig *TransformConfig, captures map[string]*loggers.CaptureLogger, 398 outputLogger log.Logger) (log.Logger, map[string]*loggers.CaptureLogger, error) { 399 400 switch transformConfig.TransformType { 401 case NoTransform: 402 return outputLogger, captures, nil 403 case Label: 404 if transformConfig.LabelConfig == nil { 405 return nil, nil, fmt.Errorf("label transform specified but no LabelConfig provided") 406 } 407 keyvals := make([]interface{}, 0, len(transformConfig.LabelConfig.Labels)*2) 408 for k, v := range transformConfig.LabelConfig.Labels { 409 keyvals = append(keyvals, k, v) 410 } 411 if transformConfig.LabelConfig.Prefix { 412 return log.WithPrefix(outputLogger, keyvals...), captures, nil 413 } else { 414 return log.With(outputLogger, keyvals...), captures, nil 415 } 416 case Prune: 417 if transformConfig.PruneConfig == nil { 418 return nil, nil, fmt.Errorf("prune transform specified but no PruneConfig provided") 419 } 420 keys := make([]interface{}, len(transformConfig.PruneConfig.Keys)) 421 for i, k := range transformConfig.PruneConfig.Keys { 422 keys[i] = k 423 } 424 return log.LoggerFunc(func(keyvals ...interface{}) error { 425 if transformConfig.PruneConfig.IncludeKeys { 426 return outputLogger.Log(structure.OnlyKeys(keyvals, keys...)...) 427 } else { 428 return outputLogger.Log(structure.RemoveKeys(keyvals, keys...)...) 429 } 430 }), captures, nil 431 432 case Capture: 433 if transformConfig.CaptureConfig == nil { 434 return nil, nil, fmt.Errorf("capture transform specified but no CaptureConfig provided") 435 } 436 name := transformConfig.CaptureConfig.Name 437 if _, ok := captures[name]; ok { 438 return nil, captures, fmt.Errorf("could not register new logging capture since name '%s' already "+ 439 "registered", name) 440 } 441 // Create a capture logger according to configuration (it may tee the output) 442 // or capture it to be flushed later 443 captureLogger := loggers.NewCaptureLogger(outputLogger, 444 channels.BufferCap(transformConfig.CaptureConfig.BufferCap), 445 transformConfig.CaptureConfig.Passthrough) 446 // Register the capture 447 captures[name] = captureLogger 448 // Pass it upstream to be logged to 449 return captureLogger, captures, nil 450 case Filter: 451 if transformConfig.FilterConfig == nil { 452 return nil, nil, fmt.Errorf("filter transform specified but no FilterConfig provided") 453 } 454 predicate, err := BuildFilterPredicate(transformConfig.FilterConfig) 455 if err != nil { 456 return nil, captures, fmt.Errorf("could not build filter predicate: '%s'", err) 457 } 458 return loggers.FilterLogger(outputLogger, predicate), captures, nil 459 case Sort: 460 if transformConfig.SortConfig == nil { 461 return nil, nil, fmt.Errorf("sort transform specified but no SortConfig provided") 462 } 463 return loggers.SortLogger(outputLogger, transformConfig.SortConfig.Keys...), captures, nil 464 case Vectorise: 465 return loggers.VectorValuedLogger(outputLogger), captures, nil 466 default: 467 return nil, captures, fmt.Errorf("could not build logger for transform: '%s'", transformConfig.TransformType) 468 } 469 } 470 471 func signalPassthroughLogger(ifSignalLogger log.Logger, otherwiseLogger log.Logger) log.Logger { 472 return log.LoggerFunc(func(keyvals ...interface{}) error { 473 if structure.Signal(keyvals) != "" { 474 return ifSignalLogger.Log(keyvals...) 475 } 476 return otherwiseLogger.Log(keyvals...) 477 }) 478 }