mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	Modified the LogIf function to log only if the error passed is not on the ignored errors list. Currently, only disk not found error is added to the list. Added a new function in logger package called LogAlwaysIf, which will print on any error. Fixes #5997
		
			
				
	
	
		
			502 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			502 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2015, 2016, 2017, 2018 Minio, Inc.
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
package logger
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"go/build"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	c "github.com/minio/mc/pkg/console"
 | 
						|
)
 | 
						|
 | 
						|
// Disable disables all logging, false by default. (used for "go test")
 | 
						|
var Disable = false
 | 
						|
 | 
						|
var trimStrings []string
 | 
						|
 | 
						|
// Level type
 | 
						|
type Level int8
 | 
						|
 | 
						|
// Enumerated level types
 | 
						|
const (
 | 
						|
	InformationLvl Level = iota + 1
 | 
						|
	ErrorLvl
 | 
						|
	FatalLvl
 | 
						|
)
 | 
						|
 | 
						|
const loggerTimeFormat string = "15:04:05 MST 01/02/2006"
 | 
						|
 | 
						|
// List of error strings to be ignored by LogIf
 | 
						|
const (
 | 
						|
	diskNotFoundError = "disk not found"
 | 
						|
)
 | 
						|
 | 
						|
var matchingFuncNames = [...]string{
 | 
						|
	"http.HandlerFunc.ServeHTTP",
 | 
						|
	"cmd.serverMain",
 | 
						|
	"cmd.StartGateway",
 | 
						|
	"cmd.(*webAPIHandlers).ListBuckets",
 | 
						|
	"cmd.(*webAPIHandlers).MakeBucket",
 | 
						|
	"cmd.(*webAPIHandlers).DeleteBucket",
 | 
						|
	"cmd.(*webAPIHandlers).ListObjects",
 | 
						|
	"cmd.(*webAPIHandlers).RemoveObject",
 | 
						|
	"cmd.(*webAPIHandlers).Login",
 | 
						|
	"cmd.(*webAPIHandlers).GenerateAuth",
 | 
						|
	"cmd.(*webAPIHandlers).SetAuth",
 | 
						|
	"cmd.(*webAPIHandlers).GetAuth",
 | 
						|
	"cmd.(*webAPIHandlers).CreateURLToken",
 | 
						|
	"cmd.(*webAPIHandlers).Upload",
 | 
						|
	"cmd.(*webAPIHandlers).Download",
 | 
						|
	"cmd.(*webAPIHandlers).DownloadZip",
 | 
						|
	"cmd.(*webAPIHandlers).GetBucketPolicy",
 | 
						|
	"cmd.(*webAPIHandlers).ListAllBucketPolicies",
 | 
						|
	"cmd.(*webAPIHandlers).SetBucketPolicy",
 | 
						|
	"cmd.(*webAPIHandlers).PresignedGet",
 | 
						|
	"cmd.(*webAPIHandlers).ServerInfo",
 | 
						|
	"cmd.(*webAPIHandlers).StorageInfo",
 | 
						|
	// add more here ..
 | 
						|
}
 | 
						|
 | 
						|
func (level Level) String() string {
 | 
						|
	var lvlStr string
 | 
						|
	switch level {
 | 
						|
	case InformationLvl:
 | 
						|
		lvlStr = "INFO"
 | 
						|
	case ErrorLvl:
 | 
						|
		lvlStr = "ERROR"
 | 
						|
	case FatalLvl:
 | 
						|
		lvlStr = "FATAL"
 | 
						|
	}
 | 
						|
	return lvlStr
 | 
						|
}
 | 
						|
 | 
						|
// Console interface describes the methods that needs to be implemented to satisfy the interface requirements.
 | 
						|
type Console interface {
 | 
						|
	json(msg string, args ...interface{})
 | 
						|
	quiet(msg string, args ...interface{})
 | 
						|
	pretty(msg string, args ...interface{})
 | 
						|
}
 | 
						|
 | 
						|
func consoleLog(console Console, msg string, args ...interface{}) {
 | 
						|
	switch {
 | 
						|
	case jsonFlag:
 | 
						|
		// Strip escape control characters from json message
 | 
						|
		msg = ansiRE.ReplaceAllLiteralString(msg, "")
 | 
						|
		console.json(msg, args...)
 | 
						|
	case quiet:
 | 
						|
		console.quiet(msg, args...)
 | 
						|
	default:
 | 
						|
		console.pretty(msg, args...)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type traceEntry struct {
 | 
						|
	Message   string            `json:"message,omitempty"`
 | 
						|
	Source    []string          `json:"source,omitempty"`
 | 
						|
	Variables map[string]string `json:"variables,omitempty"`
 | 
						|
}
 | 
						|
type args struct {
 | 
						|
	Bucket string `json:"bucket,omitempty"`
 | 
						|
	Object string `json:"object,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type api struct {
 | 
						|
	Name string `json:"name,omitempty"`
 | 
						|
	Args *args  `json:"args,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type logEntry struct {
 | 
						|
	DeploymentID string      `json:"deploymentid,omitempty"`
 | 
						|
	Level        string      `json:"level"`
 | 
						|
	Time         string      `json:"time"`
 | 
						|
	API          *api        `json:"api,omitempty"`
 | 
						|
	RemoteHost   string      `json:"remotehost,omitempty"`
 | 
						|
	RequestID    string      `json:"requestID,omitempty"`
 | 
						|
	UserAgent    string      `json:"userAgent,omitempty"`
 | 
						|
	Message      string      `json:"message,omitempty"`
 | 
						|
	Trace        *traceEntry `json:"error,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// quiet: Hide startup messages if enabled
 | 
						|
// jsonFlag: Display in JSON format, if enabled
 | 
						|
var (
 | 
						|
	quiet, jsonFlag bool
 | 
						|
	// Custom function to format error
 | 
						|
	errorFmtFunc func(string, error, bool) string
 | 
						|
 | 
						|
	deploymentID string
 | 
						|
)
 | 
						|
 | 
						|
// SetDeploymentID - Used to set the deployment ID, in XL and FS mode
 | 
						|
func SetDeploymentID(id string) {
 | 
						|
	deploymentID = id
 | 
						|
}
 | 
						|
 | 
						|
// EnableQuiet - turns quiet option on.
 | 
						|
func EnableQuiet() {
 | 
						|
	quiet = true
 | 
						|
}
 | 
						|
 | 
						|
// EnableJSON - outputs logs in json format.
 | 
						|
func EnableJSON() {
 | 
						|
	jsonFlag = true
 | 
						|
	quiet = true
 | 
						|
}
 | 
						|
 | 
						|
// RegisterUIError registers the specified rendering function. This latter
 | 
						|
// will be called for a pretty rendering of fatal errors.
 | 
						|
func RegisterUIError(f func(string, error, bool) string) {
 | 
						|
	errorFmtFunc = f
 | 
						|
}
 | 
						|
 | 
						|
// Init sets the trimStrings to possible GOPATHs
 | 
						|
// and GOROOT directories. Also append github.com/minio/minio
 | 
						|
// This is done to clean up the filename, when stack trace is
 | 
						|
// displayed when an error happens.
 | 
						|
func Init(goPath string, goRoot string) {
 | 
						|
 | 
						|
	var goPathList []string
 | 
						|
	var goRootList []string
 | 
						|
	var defaultgoPathList []string
 | 
						|
	var defaultgoRootList []string
 | 
						|
	pathSeperator := ":"
 | 
						|
	// Add all possible GOPATH paths into trimStrings
 | 
						|
	// Split GOPATH depending on the OS type
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		pathSeperator = ";"
 | 
						|
	}
 | 
						|
 | 
						|
	goPathList = strings.Split(goPath, pathSeperator)
 | 
						|
	goRootList = strings.Split(goRoot, pathSeperator)
 | 
						|
	defaultgoPathList = strings.Split(build.Default.GOPATH, pathSeperator)
 | 
						|
	defaultgoRootList = strings.Split(build.Default.GOROOT, pathSeperator)
 | 
						|
 | 
						|
	// Add trim string "{GOROOT}/src/" into trimStrings
 | 
						|
	trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)}
 | 
						|
 | 
						|
	// Add all possible path from GOPATH=path1:path2...:pathN
 | 
						|
	// as "{path#}/src/" into trimStrings
 | 
						|
	for _, goPathString := range goPathList {
 | 
						|
		trimStrings = append(trimStrings, filepath.Join(goPathString, "src")+string(filepath.Separator))
 | 
						|
	}
 | 
						|
 | 
						|
	for _, goRootString := range goRootList {
 | 
						|
		trimStrings = append(trimStrings, filepath.Join(goRootString, "src")+string(filepath.Separator))
 | 
						|
	}
 | 
						|
 | 
						|
	for _, defaultgoPathString := range defaultgoPathList {
 | 
						|
		trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
 | 
						|
	}
 | 
						|
 | 
						|
	for _, defaultgoRootString := range defaultgoRootList {
 | 
						|
		trimStrings = append(trimStrings, filepath.Join(defaultgoRootString, "src")+string(filepath.Separator))
 | 
						|
	}
 | 
						|
 | 
						|
	// Remove duplicate entries.
 | 
						|
	trimStrings = uniqueEntries(trimStrings)
 | 
						|
 | 
						|
	// Add "github.com/minio/minio" as the last to cover
 | 
						|
	// paths like "{GOROOT}/src/github.com/minio/minio"
 | 
						|
	// and "{GOPATH}/src/github.com/minio/minio"
 | 
						|
	trimStrings = append(trimStrings, filepath.Join("github.com", "minio", "minio")+string(filepath.Separator))
 | 
						|
}
 | 
						|
 | 
						|
func trimTrace(f string) string {
 | 
						|
	for _, trimString := range trimStrings {
 | 
						|
		f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString))
 | 
						|
	}
 | 
						|
	return filepath.FromSlash(f)
 | 
						|
}
 | 
						|
 | 
						|
func getSource(level int) string {
 | 
						|
	pc, file, lineNumber, ok := runtime.Caller(level)
 | 
						|
	if ok {
 | 
						|
		// Clean up the common prefixes
 | 
						|
		file = trimTrace(file)
 | 
						|
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
 | 
						|
		return fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName)
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// getTrace method - creates and returns stack trace
 | 
						|
func getTrace(traceLevel int) []string {
 | 
						|
	var trace []string
 | 
						|
	pc, file, lineNumber, ok := runtime.Caller(traceLevel)
 | 
						|
 | 
						|
	for ok && file != "" {
 | 
						|
		// Clean up the common prefixes
 | 
						|
		file = trimTrace(file)
 | 
						|
		// Get the function name
 | 
						|
		_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
 | 
						|
		// Skip duplicate traces that start with file name, "<autogenerated>"
 | 
						|
		// and also skip traces with function name that starts with "runtime."
 | 
						|
		if !strings.HasPrefix(file, "<autogenerated>") &&
 | 
						|
			!strings.HasPrefix(funcName, "runtime.") {
 | 
						|
			// Form and append a line of stack trace into a
 | 
						|
			// collection, 'trace', to build full stack trace
 | 
						|
			trace = append(trace, fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName))
 | 
						|
 | 
						|
			// Ignore trace logs beyond the following conditions
 | 
						|
			for _, name := range matchingFuncNames {
 | 
						|
				if funcName == name {
 | 
						|
					return trace
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		traceLevel++
 | 
						|
		// Read stack trace information from PC
 | 
						|
		pc, file, lineNumber, ok = runtime.Caller(traceLevel)
 | 
						|
	}
 | 
						|
	return trace
 | 
						|
}
 | 
						|
 | 
						|
// LogAlwaysIf prints a detailed error message during
 | 
						|
// the execution of the server.
 | 
						|
func LogAlwaysIf(ctx context.Context, err error) {
 | 
						|
	if err == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	logIf(ctx, err)
 | 
						|
}
 | 
						|
 | 
						|
// LogIf prints a detailed error message during
 | 
						|
// the execution of the server, if it is not an
 | 
						|
// ignored error.
 | 
						|
func LogIf(ctx context.Context, err error) {
 | 
						|
	if err == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err.Error() != diskNotFoundError {
 | 
						|
		logIf(ctx, err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// logIf prints a detailed error message during
 | 
						|
// the execution of the server.
 | 
						|
func logIf(ctx context.Context, err error) {
 | 
						|
	if Disable {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	req := GetReqInfo(ctx)
 | 
						|
 | 
						|
	if req == nil {
 | 
						|
		req = &ReqInfo{API: "SYSTEM"}
 | 
						|
	}
 | 
						|
 | 
						|
	API := "SYSTEM"
 | 
						|
	if req.API != "" {
 | 
						|
		API = req.API
 | 
						|
	}
 | 
						|
 | 
						|
	tags := make(map[string]string)
 | 
						|
	for _, entry := range req.GetTags() {
 | 
						|
		tags[entry.Key] = entry.Val
 | 
						|
	}
 | 
						|
 | 
						|
	// Get full stack trace
 | 
						|
	trace := getTrace(2)
 | 
						|
 | 
						|
	// Get the cause for the Error
 | 
						|
	message := err.Error()
 | 
						|
 | 
						|
	entry := logEntry{
 | 
						|
		DeploymentID: deploymentID,
 | 
						|
		Level:        ErrorLvl.String(),
 | 
						|
		RemoteHost:   req.RemoteHost,
 | 
						|
		RequestID:    req.RequestID,
 | 
						|
		UserAgent:    req.UserAgent,
 | 
						|
		Time:         time.Now().UTC().Format(time.RFC3339Nano),
 | 
						|
		API:          &api{Name: API, Args: &args{Bucket: req.BucketName, Object: req.ObjectName}},
 | 
						|
		Trace:        &traceEntry{Message: message, Source: trace, Variables: tags},
 | 
						|
	}
 | 
						|
 | 
						|
	// Iterate over all logger targets to send the log entry
 | 
						|
	for _, t := range Targets {
 | 
						|
		t.send(entry)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ErrCritical is the value panic'd whenever CriticalIf is called.
 | 
						|
var ErrCritical struct{}
 | 
						|
 | 
						|
// CriticalIf logs the provided error on the console. It fails the
 | 
						|
// current go-routine by causing a `panic(ErrCritical)`.
 | 
						|
func CriticalIf(ctx context.Context, err error) {
 | 
						|
	if err != nil {
 | 
						|
		LogIf(ctx, err)
 | 
						|
		panic(ErrCritical)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// FatalIf is similar to Fatal() but it ignores passed nil error
 | 
						|
func FatalIf(err error, msg string, data ...interface{}) {
 | 
						|
	if err == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	fatal(err, msg, data...)
 | 
						|
}
 | 
						|
 | 
						|
// Fatal prints only fatal error message without no stack trace
 | 
						|
// it will be called for input validation failures
 | 
						|
func Fatal(err error, msg string, data ...interface{}) {
 | 
						|
	fatal(err, msg, data...)
 | 
						|
}
 | 
						|
 | 
						|
func fatal(err error, msg string, data ...interface{}) {
 | 
						|
	var errMsg string
 | 
						|
	if msg != "" {
 | 
						|
		errMsg = errorFmtFunc(fmt.Sprintf(msg, data...), err, jsonFlag)
 | 
						|
	} else {
 | 
						|
		errMsg = err.Error()
 | 
						|
	}
 | 
						|
	consoleLog(fatalMessage, errMsg)
 | 
						|
}
 | 
						|
 | 
						|
var fatalMessage fatalMsg
 | 
						|
 | 
						|
type fatalMsg struct {
 | 
						|
}
 | 
						|
 | 
						|
func (f fatalMsg) json(msg string, args ...interface{}) {
 | 
						|
	logJSON, err := json.Marshal(&logEntry{
 | 
						|
		Level: FatalLvl.String(),
 | 
						|
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
 | 
						|
		Trace: &traceEntry{Message: fmt.Sprintf(msg, args...), Source: []string{getSource(6)}},
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	fmt.Println(string(logJSON))
 | 
						|
 | 
						|
	os.Exit(1)
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (f fatalMsg) quiet(msg string, args ...interface{}) {
 | 
						|
	f.pretty(msg, args...)
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	logTag      = "ERROR"
 | 
						|
	logBanner   = colorBgRed(colorFgWhite(colorBold(logTag))) + " "
 | 
						|
	emptyBanner = colorBgRed(strings.Repeat(" ", len(logTag))) + " "
 | 
						|
	bannerWidth = len(logTag) + 1
 | 
						|
)
 | 
						|
 | 
						|
func (f fatalMsg) pretty(msg string, args ...interface{}) {
 | 
						|
	// Build the passed error message
 | 
						|
	errMsg := fmt.Sprintf(msg, args...)
 | 
						|
 | 
						|
	tagPrinted := false
 | 
						|
 | 
						|
	// Print the error message: the following code takes care
 | 
						|
	// of splitting error text and always pretty printing the
 | 
						|
	// red banner along with the error message. Since the error
 | 
						|
	// message itself contains some colored text, we needed
 | 
						|
	// to use some ANSI control escapes to cursor color state
 | 
						|
	// and freely move in the screen.
 | 
						|
	for _, line := range strings.Split(errMsg, "\n") {
 | 
						|
		if len(line) == 0 {
 | 
						|
			// No more text to print, just quit.
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		for {
 | 
						|
			// Save the attributes of the current cursor helps
 | 
						|
			// us save the text color of the passed error message
 | 
						|
			ansiSaveAttributes()
 | 
						|
			// Print banner with or without the log tag
 | 
						|
			if !tagPrinted {
 | 
						|
				fmt.Print(logBanner)
 | 
						|
				tagPrinted = true
 | 
						|
			} else {
 | 
						|
				fmt.Print(emptyBanner)
 | 
						|
			}
 | 
						|
			// Restore the text color of the error message
 | 
						|
			ansiRestoreAttributes()
 | 
						|
			ansiMoveRight(bannerWidth)
 | 
						|
			// Continue  error message printing
 | 
						|
			fmt.Println(line)
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Exit because this is a fatal error message
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
var info infoMsg
 | 
						|
 | 
						|
type infoMsg struct {
 | 
						|
}
 | 
						|
 | 
						|
func (i infoMsg) json(msg string, args ...interface{}) {
 | 
						|
	logJSON, err := json.Marshal(&logEntry{
 | 
						|
		Level:   InformationLvl.String(),
 | 
						|
		Message: fmt.Sprintf(msg, args...),
 | 
						|
		Time:    time.Now().UTC().Format(time.RFC3339Nano),
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	fmt.Println(string(logJSON))
 | 
						|
}
 | 
						|
 | 
						|
func (i infoMsg) quiet(msg string, args ...interface{}) {
 | 
						|
	i.pretty(msg, args...)
 | 
						|
}
 | 
						|
 | 
						|
func (i infoMsg) pretty(msg string, args ...interface{}) {
 | 
						|
	c.Printf(msg, args...)
 | 
						|
}
 | 
						|
 | 
						|
// Info :
 | 
						|
func Info(msg string, data ...interface{}) {
 | 
						|
	consoleLog(info, msg+"\n", data...)
 | 
						|
}
 | 
						|
 | 
						|
var startupMessage startUpMsg
 | 
						|
 | 
						|
type startUpMsg struct {
 | 
						|
}
 | 
						|
 | 
						|
func (s startUpMsg) json(msg string, args ...interface{}) {
 | 
						|
}
 | 
						|
 | 
						|
func (s startUpMsg) quiet(msg string, args ...interface{}) {
 | 
						|
}
 | 
						|
 | 
						|
func (s startUpMsg) pretty(msg string, args ...interface{}) {
 | 
						|
	c.Printf(msg, args...)
 | 
						|
}
 | 
						|
 | 
						|
// StartupMessage :
 | 
						|
func StartupMessage(msg string, data ...interface{}) {
 | 
						|
	consoleLog(startupMessage, msg+"\n", data...)
 | 
						|
}
 |