github.com/thanos-io/thanos@v0.32.5/pkg/logging/options.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package logging
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"time"
    10  
    11  	extflag "github.com/efficientgo/tools/extkingpin"
    12  	"github.com/go-kit/log"
    13  	"github.com/go-kit/log/level"
    14  	grpc_logging "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
    15  	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/tags"
    16  	"github.com/oklog/ulid"
    17  	"google.golang.org/grpc/codes"
    18  )
    19  
    20  // Decision defines rules for enabling start and end of logging.
    21  type Decision int
    22  
    23  const (
    24  	// NoLogCall - Logging is disabled.
    25  	NoLogCall Decision = iota
    26  	// LogFinishCall - Only finish logs of request is enabled.
    27  	LogFinishCall
    28  	// LogStartAndFinishCall - Logging of start and end of request is enabled.
    29  	LogStartAndFinishCall
    30  )
    31  
    32  var defaultOptions = &options{
    33  	shouldLog:         DefaultDeciderMethod,
    34  	codeFunc:          DefaultErrorToCode,
    35  	levelFunc:         DefaultCodeToLevel,
    36  	durationFieldFunc: DurationToTimeMillisFields,
    37  	filterLog:         DefaultFilterLogging,
    38  }
    39  
    40  func evaluateOpt(opts []Option) *options {
    41  	optCopy := &options{}
    42  	*optCopy = *defaultOptions
    43  	optCopy.levelFunc = DefaultCodeToLevel
    44  	for _, o := range opts {
    45  		o(optCopy)
    46  	}
    47  	return optCopy
    48  }
    49  
    50  // WithDecider customizes the function for deciding if the HTTP Middlewares/Tripperwares should log.
    51  func WithDecider(f Decider) Option {
    52  	return func(o *options) {
    53  		o.shouldLog = f
    54  	}
    55  }
    56  
    57  // WithLevels customizes the function for mapping HTTP response codes and interceptor log level statements.
    58  func WithLevels(f CodeToLevel) Option {
    59  	return func(o *options) {
    60  		o.levelFunc = f
    61  	}
    62  }
    63  
    64  // WithFilter customizes the function for deciding which level of logging should be allowed.
    65  // Follows go-kit Allow<level of log> convention.
    66  func WithFilter(f FilterLogging) Option {
    67  	return func(o *options) {
    68  		o.filterLog = f
    69  	}
    70  }
    71  
    72  // Interface for the additional methods.
    73  
    74  // Types for the Options.
    75  type Option func(*options)
    76  
    77  // Fields represents logging fields. It has to have even number of elements (pairs).
    78  type Fields []string
    79  
    80  // ErrorToCode function determines the error code of the error
    81  // for the http response.
    82  type ErrorToCode func(err error) int
    83  
    84  // DefaultErrorToCode returns an InternalServerError.
    85  func DefaultErrorToCode(_ error) int {
    86  	return 500
    87  }
    88  
    89  // Decider function defines rules for suppressing the logging.
    90  type Decider func(methodName string, err error) Decision
    91  
    92  // DefaultDeciderMethod is the default implementation of decider to see if you should log the call
    93  // by default this is set to LogStartAndFinishCall.
    94  func DefaultDeciderMethod(_ string, _ error) Decision {
    95  	return LogStartAndFinishCall
    96  }
    97  
    98  // CodeToLevel function defines the mapping between HTTP Response codes to log levels for server side.
    99  type CodeToLevel func(logger log.Logger, code int) log.Logger
   100  
   101  // DurationToFields function defines how to produce duration fields for logging.
   102  type DurationToFields func(duration time.Duration) Fields
   103  
   104  // FilterLogging makes sure only the logs with level=lvl gets logged, or filtered.
   105  type FilterLogging func(logger log.Logger) log.Logger
   106  
   107  // DefaultFilterLogging allows logs from all levels to be logged in output.
   108  func DefaultFilterLogging(logger log.Logger) log.Logger {
   109  	return level.NewFilter(logger, level.AllowAll())
   110  }
   111  
   112  type options struct {
   113  	levelFunc         CodeToLevel
   114  	shouldLog         Decider
   115  	codeFunc          ErrorToCode
   116  	durationFieldFunc DurationToFields
   117  	filterLog         FilterLogging
   118  }
   119  
   120  // DefaultCodeToLevel is the helper mapper that maps HTTP Response codes to log levels.
   121  func DefaultCodeToLevel(logger log.Logger, code int) log.Logger {
   122  	if code >= 200 && code < 500 {
   123  		return level.Debug(logger)
   124  	}
   125  	return level.Error(logger)
   126  }
   127  
   128  // DefaultCodeToLevelGRPC is the helper mapper that maps gRPC Response codes to log levels.
   129  func DefaultCodeToLevelGRPC(c codes.Code) grpc_logging.Level {
   130  	switch c {
   131  	case codes.Unknown, codes.Unimplemented, codes.Internal, codes.DataLoss:
   132  		return grpc_logging.ERROR
   133  	default:
   134  		return grpc_logging.DEBUG
   135  	}
   136  }
   137  
   138  // DurationToTimeMillisFields converts the duration to milliseconds and uses the key `http.time_ms`.
   139  func DurationToTimeMillisFields(duration time.Duration) Fields {
   140  	return Fields{"http.time_ms", fmt.Sprintf("%v", durationToMilliseconds(duration))}
   141  }
   142  
   143  func durationToMilliseconds(duration time.Duration) float32 {
   144  	return float32(duration.Nanoseconds()/1000) / 1000
   145  }
   146  
   147  // LogDecision defines mapping of flag options to the logging decision.
   148  var LogDecision = map[string]Decision{
   149  	"NoLogCall":             NoLogCall,
   150  	"LogFinishCall":         LogFinishCall,
   151  	"LogStartAndFinishCall": LogStartAndFinishCall,
   152  }
   153  
   154  // MapAllowedLevels allows to map a given level to a list of allowed level.
   155  // Convention taken from go-kit/level v0.10.0 https://godoc.org/github.com/go-kit/log/level#AllowAll.
   156  var MapAllowedLevels = map[string][]string{
   157  	"DEBUG": {"INFO", "DEBUG", "WARN", "ERROR"},
   158  	"ERROR": {"ERROR"},
   159  	"INFO":  {"INFO", "WARN", "ERROR"},
   160  	"WARN":  {"WARN", "ERROR"},
   161  }
   162  
   163  // TODO: @yashrsharma44 - To be deprecated in the next release.
   164  func ParseHTTPOptions(flagDecision string, reqLogConfig *extflag.PathOrContent) ([]Option, error) {
   165  	// Default Option: No Logging.
   166  	logOpts := []Option{WithDecider(func(_ string, _ error) Decision {
   167  		return NoLogCall
   168  	})}
   169  
   170  	// If flag is incorrectly parsed.
   171  	configYAML, err := reqLogConfig.Content()
   172  	if err != nil {
   173  		return logOpts, fmt.Errorf("getting request logging config failed. %v", err)
   174  	}
   175  
   176  	// Check if the user enables request logging through flags and YAML.
   177  	if len(configYAML) != 0 && flagDecision != "" {
   178  		return logOpts, fmt.Errorf("both log.request.decision and request.logging have been enabled, please use only one of the flags")
   179  	}
   180  	// If old flag is enabled.
   181  	if len(flagDecision) > 0 {
   182  		logOpts := []Option{WithDecider(func(_ string, _ error) Decision {
   183  			return LogDecision[flagDecision]
   184  		})}
   185  		return logOpts, nil
   186  	}
   187  	return NewHTTPOption(configYAML)
   188  }
   189  
   190  // TODO: @yashrsharma44 - To be deprecated in the next release.
   191  func ParsegRPCOptions(flagDecision string, reqLogConfig *extflag.PathOrContent) ([]tags.Option, []grpc_logging.Option, error) {
   192  	// Default Option: No Logging.
   193  	logOpts := []grpc_logging.Option{grpc_logging.WithDecider(func(_ string, _ error) grpc_logging.Decision {
   194  		return grpc_logging.NoLogCall
   195  	})}
   196  
   197  	configYAML, err := reqLogConfig.Content()
   198  	if err != nil {
   199  		return []tags.Option{}, logOpts, fmt.Errorf("getting request logging config failed. %v", err)
   200  	}
   201  
   202  	// Check if the user enables request logging through flags and YAML.
   203  	if len(configYAML) != 0 && flagDecision != "" {
   204  		return []tags.Option{}, logOpts, fmt.Errorf("both log.request.decision and request.logging-config have been enabled, please use only one of the flags")
   205  	}
   206  
   207  	// If the old flag is empty, use the new YAML config.
   208  	if flagDecision == "" {
   209  		tagOpts, logOpts, err := NewGRPCOption(configYAML)
   210  		if err != nil {
   211  			return []tags.Option{}, logOpts, err
   212  		}
   213  		return tagOpts, logOpts, nil
   214  	}
   215  
   216  	tagOpts := []tags.Option{
   217  		tags.WithFieldExtractor(func(_ string, req interface{}) map[string]string {
   218  			tagMap := tags.TagBasedRequestFieldExtractor("request-id")("", req)
   219  			// If a request-id exists for a given request.
   220  			if tagMap != nil {
   221  				if _, ok := tagMap["request-id"]; ok {
   222  					return tagMap
   223  				}
   224  			}
   225  			entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
   226  			reqID := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
   227  			tagMap = make(map[string]string)
   228  			tagMap["request-id"] = reqID.String()
   229  			return tagMap
   230  		}),
   231  	}
   232  	logOpts = []grpc_logging.Option{grpc_logging.WithDecider(func(_ string, _ error) grpc_logging.Decision {
   233  		switch flagDecision {
   234  		case "NoLogCall":
   235  			return grpc_logging.NoLogCall
   236  		case "LogFinishCall":
   237  			return grpc_logging.LogFinishCall
   238  		case "LogStartAndFinishCall":
   239  			return grpc_logging.LogStartAndFinishCall
   240  		default:
   241  			return grpc_logging.NoLogCall
   242  		}
   243  	})}
   244  	return tagOpts, logOpts, nil
   245  }