sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/logrusutil/logrusutil.go (about) 1 /* 2 Copyright 2018 The Kubernetes 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 logrusutil implements some helpers for using logrus 18 package logrusutil 19 20 import ( 21 "sync" 22 "time" 23 24 "github.com/sirupsen/logrus" 25 "k8s.io/apimachinery/pkg/util/sets" 26 27 "sigs.k8s.io/prow/pkg/secretutil" 28 "sigs.k8s.io/prow/pkg/version" 29 ) 30 31 // DefaultFieldsFormatter wraps another logrus.Formatter, injecting 32 // DefaultFields into each Format() call, existing fields are preserved 33 // if they have the same key 34 type DefaultFieldsFormatter struct { 35 WrappedFormatter logrus.Formatter 36 DefaultFields logrus.Fields 37 PrintLineNumber bool 38 } 39 40 // Init set Logrus formatter 41 // if DefaultFieldsFormatter.wrappedFormatter is nil &logrus.JSONFormatter{} will be used instead 42 func Init(formatter *DefaultFieldsFormatter) { 43 if formatter == nil { 44 return 45 } 46 if formatter.WrappedFormatter == nil { 47 formatter.WrappedFormatter = &logrus.JSONFormatter{} 48 } 49 logrus.SetFormatter(formatter) 50 logrus.SetReportCaller(formatter.PrintLineNumber) 51 } 52 53 // ComponentInit is a syntax sugar for easier Init 54 func ComponentInit() { 55 Init( 56 &DefaultFieldsFormatter{ 57 PrintLineNumber: true, 58 DefaultFields: logrus.Fields{"component": version.Name}, 59 }, 60 ) 61 } 62 63 // Format implements logrus.Formatter's Format. We allocate a new Fields 64 // map in order to not modify the caller's Entry, as that is not a thread 65 // safe operation. 66 func (f *DefaultFieldsFormatter) Format(entry *logrus.Entry) ([]byte, error) { 67 data := make(logrus.Fields, len(entry.Data)+len(f.DefaultFields)+1) 68 // GCP's log collection expects a "severity" field instead of "level" 69 data["severity"] = entry.Level 70 for k, v := range f.DefaultFields { 71 data[k] = v 72 } 73 for k, v := range entry.Data { 74 data[k] = v 75 } 76 return f.WrappedFormatter.Format(&logrus.Entry{ 77 Logger: entry.Logger, 78 Data: data, 79 Time: entry.Time, 80 Level: entry.Level, 81 Message: entry.Message, 82 Caller: entry.Caller, 83 }) 84 } 85 86 // CensoringFormatter represents a logrus formatter that 87 // can be used to censor sensitive information 88 type CensoringFormatter struct { 89 delegate logrus.Formatter 90 censorer secretutil.Censorer 91 } 92 93 func (f CensoringFormatter) Format(entry *logrus.Entry) ([]byte, error) { 94 // Depending on the formatter in the delegate, the message will actually 95 // change shape/content - think of a message with newlines or quotes going 96 // to a JSON output. In order to catch this, we need to pre-censor the message. 97 message := []byte(entry.Message) 98 f.censorer.Censor(&message) 99 entry.Message = string(message) 100 raw, err := f.delegate.Format(entry) 101 if err != nil { 102 return raw, err 103 } 104 f.censorer.Censor(&raw) 105 return raw, nil 106 } 107 108 // NewCensoringFormatter generates a `CensoringFormatter` with 109 // a formatter as delegate and a set of strings to censor 110 func NewCensoringFormatter(f logrus.Formatter, getSecrets func() sets.Set[string]) CensoringFormatter { 111 censorer := secretutil.NewCensorer() 112 censorer.Refresh(sets.List(getSecrets())...) 113 return NewFormatterWithCensor(f, censorer) 114 } 115 116 // NewFormatterWithCensor generates a `CensoringFormatter` with 117 // a formatter as delegate and censorer to use 118 func NewFormatterWithCensor(f logrus.Formatter, censorer secretutil.Censorer) CensoringFormatter { 119 return CensoringFormatter{ 120 censorer: censorer, 121 delegate: f, 122 } 123 } 124 125 // ThrottledWarnf prints a warning the first time called and if at most `period` has elapsed since the last time. 126 func ThrottledWarnf(last *time.Time, period time.Duration, format string, args ...interface{}) { 127 if throttleCheck(last, period) { 128 logrus.Warnf(format, args...) 129 } 130 } 131 132 var throttleLock sync.RWMutex // Rare updates and concurrent readers, so reuse the same lock 133 134 // throttleCheck returns true when first called or if 135 // at least `period` has elapsed since the last time it returned true. 136 func throttleCheck(last *time.Time, period time.Duration) bool { 137 // has it been at least `period` since we won the race? 138 throttleLock.RLock() 139 fresh := time.Since(*last) <= period 140 throttleLock.RUnlock() 141 if fresh { // event occurred too recently 142 return false 143 } 144 // Event is stale, will we win the race? 145 throttleLock.Lock() 146 defer throttleLock.Unlock() 147 now := time.Now() // Recalculate now, we might wait awhile for the lock 148 if now.Sub(*last) <= period { // Nope, we lost 149 return false 150 } 151 *last = now 152 return true 153 }