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 }