storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/logger/logger.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc. 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 logger 18 19 import ( 20 "context" 21 "encoding/hex" 22 "errors" 23 "fmt" 24 "go/build" 25 "hash" 26 "net/http" 27 "path/filepath" 28 "reflect" 29 "runtime" 30 "strings" 31 "time" 32 33 "github.com/minio/highwayhash" 34 "github.com/minio/minio-go/v7/pkg/set" 35 36 "storj.io/minio/cmd/logger/message/log" 37 ) 38 39 var ( 40 // HighwayHash key for logging in anonymous mode 41 magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0") 42 // HighwayHash hasher for logging in anonymous mode 43 loggerHighwayHasher hash.Hash 44 ) 45 46 // Disable disables all logging, false by default. (used for "go test") 47 var Disable = false 48 49 // Level type 50 type Level int8 51 52 // Enumerated level types 53 const ( 54 InformationLvl Level = iota + 1 55 ErrorLvl 56 FatalLvl 57 ) 58 59 var trimStrings []string 60 61 var globalDeploymentID string 62 63 // TimeFormat - logging time format. 64 const TimeFormat string = "15:04:05 MST 01/02/2006" 65 66 var matchingFuncNames = [...]string{ 67 "http.HandlerFunc.ServeHTTP", 68 "cmd.serverMain", 69 "cmd.StartGateway", 70 "cmd.(*webAPIHandlers).ListBuckets", 71 "cmd.(*webAPIHandlers).MakeBucket", 72 "cmd.(*webAPIHandlers).DeleteBucket", 73 "cmd.(*webAPIHandlers).ListObjects", 74 "cmd.(*webAPIHandlers).RemoveObject", 75 "cmd.(*webAPIHandlers).Login", 76 "cmd.(*webAPIHandlers).SetAuth", 77 "cmd.(*webAPIHandlers).CreateURLToken", 78 "cmd.(*webAPIHandlers).Upload", 79 "cmd.(*webAPIHandlers).Download", 80 "cmd.(*webAPIHandlers).DownloadZip", 81 "cmd.(*webAPIHandlers).GetBucketPolicy", 82 "cmd.(*webAPIHandlers).ListAllBucketPolicies", 83 "cmd.(*webAPIHandlers).SetBucketPolicy", 84 "cmd.(*webAPIHandlers).PresignedGet", 85 "cmd.(*webAPIHandlers).ServerInfo", 86 "cmd.(*webAPIHandlers).StorageInfo", 87 // add more here .. 88 } 89 90 func (level Level) String() string { 91 var lvlStr string 92 switch level { 93 case InformationLvl: 94 lvlStr = "INFO" 95 case ErrorLvl: 96 lvlStr = "ERROR" 97 case FatalLvl: 98 lvlStr = "FATAL" 99 } 100 return lvlStr 101 } 102 103 // quietFlag: Hide startup messages if enabled 104 // jsonFlag: Display in JSON format, if enabled 105 var ( 106 quietFlag, jsonFlag, anonFlag bool 107 // Custom function to format error 108 errorFmtFunc func(string, error, bool) string 109 ) 110 111 // EnableQuiet - turns quiet option on. 112 func EnableQuiet() { 113 quietFlag = true 114 } 115 116 // EnableJSON - outputs logs in json format. 117 func EnableJSON() { 118 jsonFlag = true 119 quietFlag = true 120 } 121 122 // EnableAnonymous - turns anonymous flag 123 // to avoid printing sensitive information. 124 func EnableAnonymous() { 125 anonFlag = true 126 } 127 128 // IsJSON - returns true if jsonFlag is true 129 func IsJSON() bool { 130 return jsonFlag 131 } 132 133 // IsQuiet - returns true if quietFlag is true 134 func IsQuiet() bool { 135 return quietFlag 136 } 137 138 // RegisterError registers the specified rendering function. This latter 139 // will be called for a pretty rendering of fatal errors. 140 func RegisterError(f func(string, error, bool) string) { 141 errorFmtFunc = f 142 } 143 144 // Remove any duplicates and return unique entries. 145 func uniqueEntries(paths []string) []string { 146 m := make(set.StringSet) 147 for _, p := range paths { 148 if !m.Contains(p) { 149 m.Add(p) 150 } 151 } 152 return m.ToSlice() 153 } 154 155 // SetDeploymentID -- Deployment Id from the main package is set here 156 func SetDeploymentID(deploymentID string) { 157 globalDeploymentID = deploymentID 158 } 159 160 // Init sets the trimStrings to possible GOPATHs 161 // and GOROOT directories. Also append github.com/minio/minio 162 // This is done to clean up the filename, when stack trace is 163 // displayed when an error happens. 164 func Init(goPath string, goRoot string) { 165 166 var goPathList []string 167 var goRootList []string 168 var defaultgoPathList []string 169 var defaultgoRootList []string 170 pathSeperator := ":" 171 // Add all possible GOPATH paths into trimStrings 172 // Split GOPATH depending on the OS type 173 if runtime.GOOS == "windows" { 174 pathSeperator = ";" 175 } 176 177 goPathList = strings.Split(goPath, pathSeperator) 178 goRootList = strings.Split(goRoot, pathSeperator) 179 defaultgoPathList = strings.Split(build.Default.GOPATH, pathSeperator) 180 defaultgoRootList = strings.Split(build.Default.GOROOT, pathSeperator) 181 182 // Add trim string "{GOROOT}/src/" into trimStrings 183 trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)} 184 185 // Add all possible path from GOPATH=path1:path2...:pathN 186 // as "{path#}/src/" into trimStrings 187 for _, goPathString := range goPathList { 188 trimStrings = append(trimStrings, filepath.Join(goPathString, "src")+string(filepath.Separator)) 189 } 190 191 for _, goRootString := range goRootList { 192 trimStrings = append(trimStrings, filepath.Join(goRootString, "src")+string(filepath.Separator)) 193 } 194 195 for _, defaultgoPathString := range defaultgoPathList { 196 trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator)) 197 } 198 199 for _, defaultgoRootString := range defaultgoRootList { 200 trimStrings = append(trimStrings, filepath.Join(defaultgoRootString, "src")+string(filepath.Separator)) 201 } 202 203 // Remove duplicate entries. 204 trimStrings = uniqueEntries(trimStrings) 205 206 // Add "github.com/minio/minio" as the last to cover 207 // paths like "{GOROOT}/src/github.com/minio/minio" 208 // and "{GOPATH}/src/github.com/minio/minio" 209 trimStrings = append(trimStrings, filepath.Join("github.com", "minio", "minio")+string(filepath.Separator)) 210 211 loggerHighwayHasher, _ = highwayhash.New(magicHighwayHash256Key) // New will never return error since key is 256 bit 212 } 213 214 func trimTrace(f string) string { 215 for _, trimString := range trimStrings { 216 f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString)) 217 } 218 return filepath.FromSlash(f) 219 } 220 221 func getSource(level int) string { 222 pc, file, lineNumber, ok := runtime.Caller(level) 223 if ok { 224 // Clean up the common prefixes 225 file = trimTrace(file) 226 _, funcName := filepath.Split(runtime.FuncForPC(pc).Name()) 227 return fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName) 228 } 229 return "" 230 } 231 232 // getTrace method - creates and returns stack trace 233 func getTrace(traceLevel int) []string { 234 var trace []string 235 pc, file, lineNumber, ok := runtime.Caller(traceLevel) 236 237 for ok && file != "" { 238 // Clean up the common prefixes 239 file = trimTrace(file) 240 // Get the function name 241 _, funcName := filepath.Split(runtime.FuncForPC(pc).Name()) 242 // Skip duplicate traces that start with file name, "<autogenerated>" 243 // and also skip traces with function name that starts with "runtime." 244 if !strings.HasPrefix(file, "<autogenerated>") && 245 !strings.HasPrefix(funcName, "runtime.") { 246 // Form and append a line of stack trace into a 247 // collection, 'trace', to build full stack trace 248 trace = append(trace, fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName)) 249 250 // Ignore trace logs beyond the following conditions 251 for _, name := range matchingFuncNames { 252 if funcName == name { 253 return trace 254 } 255 } 256 } 257 traceLevel++ 258 // Read stack trace information from PC 259 pc, file, lineNumber, ok = runtime.Caller(traceLevel) 260 } 261 return trace 262 } 263 264 // Return the highway hash of the passed string 265 func hashString(input string) string { 266 defer loggerHighwayHasher.Reset() 267 loggerHighwayHasher.Write([]byte(input)) 268 checksum := loggerHighwayHasher.Sum(nil) 269 return hex.EncodeToString(checksum) 270 } 271 272 // Kind specifies the kind of error log 273 type Kind string 274 275 const ( 276 // Minio errors 277 Minio Kind = "MINIO" 278 // Application errors 279 Application Kind = "APPLICATION" 280 // All errors 281 All Kind = "ALL" 282 ) 283 284 // LogAlwaysIf prints a detailed error message during 285 // the execution of the server. 286 func LogAlwaysIf(ctx context.Context, err error, errKind ...interface{}) { 287 if err == nil { 288 return 289 } 290 291 logIf(ctx, err, errKind...) 292 } 293 294 // LogIf prints a detailed error message during 295 // the execution of the server, if it is not an 296 // ignored error. 297 func LogIf(ctx context.Context, err error, errKind ...interface{}) { 298 if err == nil { 299 return 300 } 301 302 if errors.Is(err, context.Canceled) { 303 return 304 } 305 306 if err.Error() == http.ErrServerClosed.Error() || err.Error() == "disk not found" { 307 return 308 } 309 310 logIf(ctx, err, errKind...) 311 } 312 313 // logIf prints a detailed error message during 314 // the execution of the server. 315 func logIf(ctx context.Context, err error, errKind ...interface{}) { 316 if Disable { 317 return 318 } 319 logKind := string(Minio) 320 if len(errKind) > 0 { 321 if ek, ok := errKind[0].(Kind); ok { 322 logKind = string(ek) 323 } 324 } 325 req := GetReqInfo(ctx) 326 327 if req == nil { 328 req = &ReqInfo{API: "SYSTEM"} 329 } 330 331 API := "SYSTEM" 332 if req.API != "" { 333 API = req.API 334 } 335 336 kv := req.GetTags() 337 tags := make(map[string]interface{}, len(kv)) 338 for _, entry := range kv { 339 tags[entry.Key] = entry.Val 340 } 341 342 // Get full stack trace 343 trace := getTrace(3) 344 345 // Get the cause for the Error 346 message := fmt.Sprintf("%v (%T)", err, err) 347 if req.DeploymentID == "" { 348 req.DeploymentID = globalDeploymentID 349 } 350 entry := log.Entry{ 351 DeploymentID: req.DeploymentID, 352 Level: ErrorLvl.String(), 353 LogKind: logKind, 354 RemoteHost: req.RemoteHost, 355 Host: req.Host, 356 RequestID: req.RequestID, 357 UserAgent: req.UserAgent, 358 Time: time.Now().UTC().Format(time.RFC3339Nano), 359 API: &log.API{ 360 Name: API, 361 Args: &log.Args{ 362 Bucket: req.BucketName, 363 Object: req.ObjectName, 364 }, 365 }, 366 Trace: &log.Trace{ 367 Message: message, 368 Source: trace, 369 Variables: tags, 370 }, 371 } 372 373 if anonFlag { 374 entry.API.Args.Bucket = hashString(entry.API.Args.Bucket) 375 entry.API.Args.Object = hashString(entry.API.Args.Object) 376 entry.RemoteHost = hashString(entry.RemoteHost) 377 entry.Trace.Message = reflect.TypeOf(err).String() 378 entry.Trace.Variables = make(map[string]interface{}) 379 } 380 381 // Iterate over all logger targets to send the log entry 382 for _, t := range Targets { 383 t.Send(entry, entry.LogKind) 384 } 385 } 386 387 // ErrCritical is the value panic'd whenever CriticalIf is called. 388 var ErrCritical struct{} 389 390 // CriticalIf logs the provided error on the console. It fails the 391 // current go-routine by causing a `panic(ErrCritical)`. 392 func CriticalIf(ctx context.Context, err error, errKind ...interface{}) { 393 if err != nil { 394 LogIf(ctx, err, errKind...) 395 panic(ErrCritical) 396 } 397 } 398 399 // FatalIf is similar to Fatal() but it ignores passed nil error 400 func FatalIf(err error, msg string, data ...interface{}) { 401 if err == nil { 402 return 403 } 404 fatal(err, msg, data...) 405 }