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  }