You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
8.9 KiB
351 lines
8.9 KiB
9 years ago
|
/*
|
||
7 years ago
|
* Minio Cloud Storage, (C) 2015, 2016, 2017, 2018 Minio, Inc.
|
||
9 years ago
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
7 years ago
|
package logger
|
||
9 years ago
|
|
||
|
import (
|
||
7 years ago
|
"context"
|
||
7 years ago
|
"encoding/json"
|
||
8 years ago
|
"fmt"
|
||
7 years ago
|
"go/build"
|
||
7 years ago
|
"os"
|
||
|
"path/filepath"
|
||
8 years ago
|
"runtime"
|
||
9 years ago
|
"strings"
|
||
7 years ago
|
"time"
|
||
9 years ago
|
|
||
7 years ago
|
"github.com/fatih/color"
|
||
8 years ago
|
"github.com/minio/mc/pkg/console"
|
||
9 years ago
|
)
|
||
|
|
||
7 years ago
|
// global colors.
|
||
|
var (
|
||
|
colorBold = color.New(color.Bold).SprintFunc()
|
||
|
colorYellow = color.New(color.FgYellow).SprintfFunc()
|
||
|
colorRed = color.New(color.FgRed).SprintfFunc()
|
||
|
)
|
||
|
|
||
7 years ago
|
var trimStrings []string
|
||
9 years ago
|
|
||
7 years ago
|
// Level type
|
||
|
type Level int8
|
||
8 years ago
|
|
||
7 years ago
|
// Enumerated level types
|
||
|
const (
|
||
|
Error Level = iota + 1
|
||
|
Fatal
|
||
|
)
|
||
8 years ago
|
|
||
7 years ago
|
const loggerTimeFormat string = "15:04:05 MST 01/02/2006"
|
||
|
|
||
|
var matchingFuncNames = [...]string{
|
||
|
"http.HandlerFunc.ServeHTTP",
|
||
|
"cmd.serverMain",
|
||
|
"cmd.StartGateway",
|
||
|
// add more here ..
|
||
|
}
|
||
|
|
||
7 years ago
|
func (level Level) String() string {
|
||
|
var lvlStr string
|
||
|
switch level {
|
||
|
case Error:
|
||
|
lvlStr = "ERROR"
|
||
|
case Fatal:
|
||
|
lvlStr = "FATAL"
|
||
|
}
|
||
|
return lvlStr
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
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"`
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
type api struct {
|
||
|
Name string `json:"name,omitempty"`
|
||
|
Args args `json:"args,omitempty"`
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
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"`
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
// quiet: Hide startup messages if enabled
|
||
|
// jsonFlag: Display in JSON format, if enabled
|
||
|
var (
|
||
|
quiet, jsonFlag bool
|
||
|
)
|
||
|
|
||
7 years ago
|
// EnableQuiet - turns quiet option on.
|
||
7 years ago
|
func EnableQuiet() {
|
||
|
quiet = true
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
// EnableJSON - outputs logs in json format.
|
||
7 years ago
|
func EnableJSON() {
|
||
|
jsonFlag = true
|
||
|
quiet = true
|
||
8 years ago
|
}
|
||
|
|
||
|
// Println - wrapper to console.Println() with quiet flag.
|
||
7 years ago
|
func Println(args ...interface{}) {
|
||
|
if !quiet {
|
||
8 years ago
|
console.Println(args...)
|
||
9 years ago
|
}
|
||
8 years ago
|
}
|
||
|
|
||
|
// Printf - wrapper to console.Printf() with quiet flag.
|
||
7 years ago
|
func Printf(format string, args ...interface{}) {
|
||
|
if !quiet {
|
||
8 years ago
|
console.Printf(format, args...)
|
||
9 years ago
|
}
|
||
8 years ago
|
}
|
||
8 years ago
|
|
||
7 years ago
|
// 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) {
|
||
7 years ago
|
var goPathList []string
|
||
7 years ago
|
var defaultgoPathList []string
|
||
7 years ago
|
// Add all possible GOPATH paths into trimStrings
|
||
|
// Split GOPATH depending on the OS type
|
||
|
if runtime.GOOS == "windows" {
|
||
7 years ago
|
goPathList = strings.Split(goPath, ";")
|
||
7 years ago
|
defaultgoPathList = strings.Split(build.Default.GOPATH, ";")
|
||
7 years ago
|
} else {
|
||
|
// All other types of OSs
|
||
7 years ago
|
goPathList = strings.Split(goPath, ":")
|
||
7 years ago
|
defaultgoPathList = strings.Split(build.Default.GOPATH, ":")
|
||
|
|
||
8 years ago
|
}
|
||
8 years ago
|
|
||
7 years ago
|
// Add trim string "{GOROOT}/src/" into trimStrings
|
||
|
trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)}
|
||
8 years ago
|
|
||
7 years ago
|
// 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))
|
||
|
}
|
||
7 years ago
|
|
||
|
for _, defaultgoPathString := range defaultgoPathList {
|
||
|
trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
|
||
|
}
|
||
|
|
||
7 years ago
|
// 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))
|
||
9 years ago
|
}
|
||
|
|
||
7 years ago
|
func trimTrace(f string) string {
|
||
|
for _, trimString := range trimStrings {
|
||
|
f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString))
|
||
8 years ago
|
}
|
||
7 years ago
|
return filepath.FromSlash(f)
|
||
|
}
|
||
8 years ago
|
|
||
7 years ago
|
// 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))
|
||
7 years ago
|
|
||
|
// Ignore trace logs beyond the following conditions
|
||
|
for _, name := range matchingFuncNames {
|
||
|
if funcName == name {
|
||
|
return trace
|
||
|
}
|
||
|
}
|
||
7 years ago
|
}
|
||
|
traceLevel++
|
||
|
// Read stack trace information from PC
|
||
|
pc, file, lineNumber, ok = runtime.Caller(traceLevel)
|
||
|
}
|
||
|
return trace
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
func logIf(level Level, err error, msg string,
|
||
|
data ...interface{}) {
|
||
7 years ago
|
if err == nil {
|
||
9 years ago
|
return
|
||
|
}
|
||
7 years ago
|
// Get the cause for the Error
|
||
|
cause := err.Error()
|
||
7 years ago
|
// Get full stack trace
|
||
|
trace := getTrace(3)
|
||
|
// Get time
|
||
7 years ago
|
timeOfError := time.Now().UTC().Format(time.RFC3339Nano)
|
||
7 years ago
|
// Output the formatted log message at console
|
||
|
var output string
|
||
|
message := fmt.Sprintf(msg, data...)
|
||
7 years ago
|
if jsonFlag {
|
||
7 years ago
|
logJSON, err := json.Marshal(&logEntry{
|
||
7 years ago
|
Level: level.String(),
|
||
|
Time: timeOfError,
|
||
|
Cause: cause,
|
||
|
Trace: traceEntry{Source: trace, Message: message},
|
||
7 years ago
|
})
|
||
|
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)
|
||
8 years ago
|
|
||
7 years ago
|
output = fmt.Sprintf("\nTrace: %s\n%s",
|
||
|
strings.Join(trace, "\n"),
|
||
|
colorRed(colorBold(errMsg)))
|
||
9 years ago
|
}
|
||
7 years ago
|
fmt.Println(output)
|
||
8 years ago
|
|
||
7 years ago
|
if level == Fatal {
|
||
|
os.Exit(1)
|
||
8 years ago
|
}
|
||
9 years ago
|
}
|
||
8 years ago
|
|
||
7 years ago
|
// FatalIf :
|
||
|
func FatalIf(err error, msg string, data ...interface{}) {
|
||
|
logIf(Fatal, err, msg, data...)
|
||
8 years ago
|
}
|
||
|
|
||
7 years ago
|
// 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)
|
||
8 years ago
|
}
|