mirror of
https://github.com/pgsty/minio.git
synced 2026-07-21 21:20:23 +03:00
Create logger package and rename errorIf to LogIf (#5678)
Removing message from error logging Replace errors.Trace with LogIf
This commit is contained in:
+50
-14
@@ -16,9 +16,13 @@
|
||||
|
||||
package logger
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Key used for Get/SetContext
|
||||
// Key used for Get/SetReqInfo
|
||||
type contextKeyType string
|
||||
|
||||
const contextLogKey = contextKeyType("miniolog")
|
||||
@@ -37,27 +41,59 @@ type ReqInfo struct {
|
||||
API string // API name - GetObject PutObject NewMultipartUpload etc.
|
||||
BucketName string // Bucket name
|
||||
ObjectName string // Object name
|
||||
Tags []KeyVal // Any additional info not accommodated by above fields
|
||||
tags []KeyVal // Any additional info not accommodated by above fields
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// AppendTags - appends key/val to ReqInfo.Tags
|
||||
func (r *ReqInfo) AppendTags(key string, val string) {
|
||||
// NewReqInfo :
|
||||
func NewReqInfo(remoteHost, userAgent, requestID, api, bucket, object string) *ReqInfo {
|
||||
req := ReqInfo{}
|
||||
req.RemoteHost = remoteHost
|
||||
req.UserAgent = userAgent
|
||||
req.API = api
|
||||
req.RequestID = requestID
|
||||
req.BucketName = bucket
|
||||
req.ObjectName = object
|
||||
return &req
|
||||
}
|
||||
|
||||
// AppendTags - appends key/val to ReqInfo.tags
|
||||
func (r *ReqInfo) AppendTags(key string, val string) *ReqInfo {
|
||||
if r == nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
r.Tags = append(r.Tags, KeyVal{key, val})
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.tags = append(r.tags, KeyVal{key, val})
|
||||
return r
|
||||
}
|
||||
|
||||
// SetContext sets ReqInfo in the context.
|
||||
func SetContext(ctx context.Context, req *ReqInfo) context.Context {
|
||||
// GetTags - returns the user defined tags
|
||||
func (r *ReqInfo) GetTags() []KeyVal {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
return append([]KeyVal(nil), r.tags...)
|
||||
}
|
||||
|
||||
// SetReqInfo sets ReqInfo in the context.
|
||||
func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context {
|
||||
if ctx == nil {
|
||||
LogIf(context.Background(), fmt.Errorf("context is nil"))
|
||||
return nil
|
||||
}
|
||||
return context.WithValue(ctx, contextLogKey, req)
|
||||
}
|
||||
|
||||
// GetContext returns ReqInfo if set.
|
||||
func GetContext(ctx context.Context) *ReqInfo {
|
||||
r, ok := ctx.Value(contextLogKey).(*ReqInfo)
|
||||
if ok {
|
||||
return r
|
||||
// GetReqInfo returns ReqInfo if set.
|
||||
func GetReqInfo(ctx context.Context) *ReqInfo {
|
||||
if ctx != nil {
|
||||
r, ok := ctx.Value(contextLogKey).(*ReqInfo)
|
||||
if ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/minio/mc/pkg/console"
|
||||
)
|
||||
|
||||
// global colors.
|
||||
var (
|
||||
colorBold = color.New(color.Bold).SprintFunc()
|
||||
colorYellow = color.New(color.FgYellow).SprintfFunc()
|
||||
colorRed = color.New(color.FgRed).SprintfFunc()
|
||||
)
|
||||
|
||||
var trimStrings []string
|
||||
|
||||
// Level type
|
||||
type Level int8
|
||||
|
||||
// Enumerated level types
|
||||
const (
|
||||
Error Level = iota + 1
|
||||
Fatal
|
||||
)
|
||||
|
||||
const loggerTimeFormat string = "15:04:05 MST 01/02/2006"
|
||||
|
||||
var matchingFuncNames = [...]string{
|
||||
"http.HandlerFunc.ServeHTTP",
|
||||
"cmd.serverMain",
|
||||
"cmd.StartGateway",
|
||||
// add more here ..
|
||||
}
|
||||
|
||||
func (level Level) String() string {
|
||||
var lvlStr string
|
||||
switch level {
|
||||
case Error:
|
||||
lvlStr = "ERROR"
|
||||
case Fatal:
|
||||
lvlStr = "FATAL"
|
||||
}
|
||||
return lvlStr
|
||||
}
|
||||
|
||||
type traceEntry struct {
|
||||
Message string `json:"message"`
|
||||
Source []string `json:"source"`
|
||||
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 {
|
||||
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"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
Trace traceEntry `json:"error"`
|
||||
}
|
||||
|
||||
// quiet: Hide startup messages if enabled
|
||||
// jsonFlag: Display in JSON format, if enabled
|
||||
var (
|
||||
quiet, jsonFlag bool
|
||||
)
|
||||
|
||||
// EnableQuiet - turns quiet option on.
|
||||
func EnableQuiet() {
|
||||
quiet = true
|
||||
}
|
||||
|
||||
// EnableJSON - outputs logs in json format.
|
||||
func EnableJSON() {
|
||||
jsonFlag = true
|
||||
quiet = true
|
||||
}
|
||||
|
||||
// Println - wrapper to console.Println() with quiet flag.
|
||||
func Println(args ...interface{}) {
|
||||
if !quiet {
|
||||
console.Println(args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Printf - wrapper to console.Printf() with quiet flag.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
if !quiet {
|
||||
console.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var goPathList []string
|
||||
var defaultgoPathList []string
|
||||
// Add all possible GOPATH paths into trimStrings
|
||||
// Split GOPATH depending on the OS type
|
||||
if runtime.GOOS == "windows" {
|
||||
goPathList = strings.Split(goPath, ";")
|
||||
defaultgoPathList = strings.Split(build.Default.GOPATH, ";")
|
||||
} else {
|
||||
// All other types of OSs
|
||||
goPathList = strings.Split(goPath, ":")
|
||||
defaultgoPathList = strings.Split(build.Default.GOPATH, ":")
|
||||
|
||||
}
|
||||
|
||||
// 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 _, defaultgoPathString := range defaultgoPathList {
|
||||
trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// getTrace method - creates and returns stack trace
|
||||
func getTrace(traceLevel int) []string {
|
||||
var trace []string
|
||||
pc, file, lineNumber, ok := runtime.Caller(traceLevel)
|
||||
|
||||
for ok {
|
||||
// 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
|
||||
}
|
||||
|
||||
func logIf(level Level, err error, msg string,
|
||||
data ...interface{}) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
// Get the cause for the Error
|
||||
cause := err.Error()
|
||||
// Get full stack trace
|
||||
trace := getTrace(3)
|
||||
// Get time
|
||||
timeOfError := time.Now().UTC().Format(time.RFC3339Nano)
|
||||
// Output the formatted log message at console
|
||||
var output string
|
||||
message := fmt.Sprintf(msg, data...)
|
||||
if jsonFlag {
|
||||
logJSON, err := json.Marshal(&logEntry{
|
||||
Level: level.String(),
|
||||
Time: timeOfError,
|
||||
Cause: cause,
|
||||
Trace: traceEntry{Source: trace, Message: message},
|
||||
})
|
||||
if err != nil {
|
||||
panic("json marshal of logEntry failed: " + err.Error())
|
||||
}
|
||||
output = string(logJSON)
|
||||
} else {
|
||||
// Add a sequence number and formatting for each stack trace
|
||||
// No formatting is required for the first entry
|
||||
trace[0] = "1: " + trace[0]
|
||||
for i, element := range trace[1:] {
|
||||
trace[i+1] = fmt.Sprintf("%8v: %s", i+2, element)
|
||||
}
|
||||
errMsg := fmt.Sprintf("[%s] [%s] %s (%s)",
|
||||
timeOfError, level.String(), message, cause)
|
||||
|
||||
output = fmt.Sprintf("\nTrace: %s\n%s",
|
||||
strings.Join(trace, "\n"),
|
||||
colorRed(colorBold(errMsg)))
|
||||
}
|
||||
fmt.Println(output)
|
||||
|
||||
if level == Fatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// FatalIf :
|
||||
func FatalIf(err error, msg string, data ...interface{}) {
|
||||
logIf(Fatal, err, msg, data...)
|
||||
}
|
||||
|
||||
// LogIf :
|
||||
func LogIf(ctx context.Context, err error) {
|
||||
if err == nil {
|
||||
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 the cause for the Error
|
||||
message := err.Error()
|
||||
// Get full stack trace
|
||||
trace := getTrace(2)
|
||||
// Output the formatted log message at console
|
||||
var output string
|
||||
if jsonFlag {
|
||||
logJSON, err := json.Marshal(&logEntry{
|
||||
Level: Error.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},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
output = string(logJSON)
|
||||
} else {
|
||||
// Add a sequence number and formatting for each stack trace
|
||||
// No formatting is required for the first entry
|
||||
for i, element := range trace {
|
||||
trace[i] = fmt.Sprintf("%8v: %s", i+1, element)
|
||||
}
|
||||
|
||||
tagString := ""
|
||||
for key, value := range tags {
|
||||
if value != "" {
|
||||
if tagString != "" {
|
||||
tagString += ", "
|
||||
}
|
||||
tagString += key + "=" + value
|
||||
}
|
||||
}
|
||||
|
||||
apiString := "API: " + API + "("
|
||||
if req.BucketName != "" {
|
||||
apiString = apiString + "bucket=" + req.BucketName
|
||||
}
|
||||
if req.ObjectName != "" {
|
||||
apiString = apiString + ", object=" + req.ObjectName
|
||||
}
|
||||
apiString += ")"
|
||||
timeString := "Time: " + time.Now().Format(loggerTimeFormat)
|
||||
|
||||
var requestID string
|
||||
if req.RequestID != "" {
|
||||
requestID = "\nRequestID: " + req.RequestID
|
||||
}
|
||||
|
||||
var remoteHost string
|
||||
if req.RemoteHost != "" {
|
||||
remoteHost = "\nRemoteHost: " + req.RemoteHost
|
||||
}
|
||||
|
||||
var userAgent string
|
||||
if req.UserAgent != "" {
|
||||
userAgent = "\nUserAgent: " + req.UserAgent
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
tagString = "\n " + tagString
|
||||
}
|
||||
|
||||
output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s",
|
||||
apiString, timeString, requestID, remoteHost, userAgent,
|
||||
colorRed(colorBold(message)), tagString, strings.Join(trace, "\n"))
|
||||
}
|
||||
fmt.Println(output)
|
||||
}
|
||||
Reference in New Issue
Block a user