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  }