minio/cmd/logger.go

224 lines
5.6 KiB
Go

/*
* Minio Cloud Storage, (C) 2015, 2016, 2017 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 cmd
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/errors"
)
var log = NewLogger()
var trimStrings []string
// Level type
type Level int8
// Enumerated level types
const (
Error Level = iota + 1
Fatal
)
func (level Level) String() string {
var lvlStr string
switch level {
case Error:
lvlStr = "ERROR"
case Fatal:
lvlStr = "FATAL"
}
return lvlStr
}
type logEntry struct {
Level string `json:"level"`
Message string `json:"message"`
Time string `json:"time"`
Cause string `json:"cause"`
Trace []string `json:"trace"`
}
// Logger - for console messages
type Logger struct {
quiet bool
json bool
}
// NewLogger - to create a new Logger object
func NewLogger() *Logger {
return &Logger{}
}
// EnableQuiet - turns quiet option on.
func (log *Logger) EnableQuiet() {
log.quiet = true
}
// EnableJSON - outputs logs in json format.
func (log *Logger) EnableJSON() {
log.json = true
log.quiet = true
}
// Println - wrapper to console.Println() with quiet flag.
func (log *Logger) Println(args ...interface{}) {
if !log.quiet {
console.Println(args...)
}
}
// Printf - wrapper to console.Printf() with quiet flag.
func (log *Logger) Printf(format string, args ...interface{}) {
if !log.quiet {
console.Printf(format, args...)
}
}
func init() {
var goPathList []string
// Add all possible GOPATH paths into trimStrings
// Split GOPATH depending on the OS type
if runtime.GOOS == "windows" {
goPathList = strings.Split(GOPATH, ";")
} else {
// All other types of OSs
goPathList = strings.Split(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))
}
// 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))
}
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{}) {
isErrIgnored := func(err error) (ok bool) {
err = errors.Cause(err)
switch err.(type) {
case BucketNotFound, BucketNotEmpty, BucketExists:
ok = true
case ObjectNotFound, ObjectExistsAsDirectory:
ok = true
case BucketPolicyNotFound, InvalidUploadID:
ok = true
}
return ok
}
if err == nil || isErrIgnored(err) {
return
}
// Get the cause for the Error
cause := err.Error()
// Get full stack trace
trace := getTrace(3)
// Get time
timeOfError := UTCNow().Format(time.RFC3339Nano)
// Output the formatted log message at console
var output string
message := fmt.Sprintf(msg, data...)
if log.json {
logJSON, err := json.Marshal(&logEntry{
Level: level.String(),
Message: message,
Time: timeOfError,
Cause: cause,
Trace: trace,
})
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)
}
}
func errorIf(err error, msg string, data ...interface{}) {
logIf(Error, err, msg, data...)
}
func fatalIf(err error, msg string, data ...interface{}) {
logIf(Fatal, err, msg, data...)
}