Refactor logger (#3924)

This patch fixes below

* Previously fatalIf() never writes log other than first logging target.
* quiet flag is not honored to show progress messages other than startup messages.
* Removes console package usage for progress messages.
This commit is contained in:
Bala FA 2017-03-24 05:06:00 +05:30 committed by Harshavardhana
parent 11e15f9b4c
commit d3cb79a57c
18 changed files with 450 additions and 426 deletions

View file

@ -21,7 +21,6 @@ import (
"sync"
homedir "github.com/minio/go-homedir"
"github.com/minio/mc/pkg/console"
)
const (
@ -97,9 +96,7 @@ func (config *ConfigDir) GetPrivateKeyFile() string {
func mustGetDefaultConfigDir() string {
homeDir, err := homedir.Dir()
if err != nil {
console.Fatalln("Unable to get home directory.", err)
}
fatalIf(err, "Unable to get home directory.")
return filepath.Join(homeDir, defaultMinioConfigDir)
}

View file

@ -21,7 +21,6 @@ import (
"os"
"path/filepath"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/quick"
)
@ -106,7 +105,7 @@ func purgeV1() error {
}
removeAll(configFile)
console.Println("Removed unsupported config version 1.")
log.Println("Removed unsupported config version 1.")
return nil
}
@ -164,7 +163,7 @@ func migrateV2ToV3() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv2.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv2.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv2.Version, srvConfig.Version)
return nil
}
@ -202,7 +201,7 @@ func migrateV3ToV4() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv3.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv3.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv3.Version, srvConfig.Version)
return nil
}
@ -243,7 +242,7 @@ func migrateV4ToV5() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv4.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv4.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv4.Version, srvConfig.Version)
return nil
}
@ -311,7 +310,7 @@ func migrateV5ToV6() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv5.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv5.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv5.Version, srvConfig.Version)
return nil
}
@ -367,7 +366,7 @@ func migrateV6ToV7() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv6.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv6.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv6.Version, srvConfig.Version)
return nil
}
@ -430,7 +429,7 @@ func migrateV7ToV8() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv7.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv7.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv7.Version, srvConfig.Version)
return nil
}
@ -500,7 +499,7 @@ func migrateV8ToV9() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv8.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv8.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv8.Version, srvConfig.Version)
return nil
}
@ -568,7 +567,7 @@ func migrateV9ToV10() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv9.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv9.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv9.Version, srvConfig.Version)
return nil
}
@ -639,7 +638,7 @@ func migrateV10ToV11() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv10.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv10.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv10.Version, srvConfig.Version)
return nil
}
@ -728,7 +727,7 @@ func migrateV11ToV12() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv11.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv11.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv11.Version, srvConfig.Version)
return nil
}
@ -808,7 +807,7 @@ func migrateV12ToV13() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv12.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv12.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv12.Version, srvConfig.Version)
return nil
}
@ -893,7 +892,7 @@ func migrateV13ToV14() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv13.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv13.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv13.Version, srvConfig.Version)
return nil
}
@ -982,7 +981,7 @@ func migrateV14ToV15() error {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv14.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv14.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv14.Version, srvConfig.Version)
return nil
}
@ -1004,7 +1003,7 @@ func migrateV15ToV16() error {
// Copy over fields from V15 into V16 config struct
srvConfig := &serverConfigV16{
Logger: &logger{},
Logger: &loggers{},
Notify: &notifier{},
}
srvConfig.Version = "16"
@ -1069,14 +1068,17 @@ func migrateV15ToV16() error {
srvConfig.Browser = cv15.Browser
// Migrate console and file fields
srvConfig.Logger.Console = consoleLogger{Enable: cv15.Logger.Console.Enable}
srvConfig.Logger.File = fileLogger{Enable: cv15.Logger.File.Enable, Filename: cv15.Logger.File.Filename}
if cv15.Logger.Console.Enable {
srvConfig.Logger.Console = NewConsoleLogger()
}
if cv15.Logger.File.Enable {
srvConfig.Logger.File = NewFileLogger(cv15.Logger.File.Filename)
}
if err = quick.Save(configFile, srvConfig); err != nil {
return fmt.Errorf("Failed to migrate config from %s to %s. %v", cv15.Version, srvConfig.Version, err)
}
console.Printf("Migration from version %s to %s completed successfully.\n", cv15.Version, srvConfig.Version)
log.Printf("Migration from version %s to %s completed successfully.\n", cv15.Version, srvConfig.Version)
return nil
}

View file

@ -44,7 +44,7 @@ type serverConfigV16 struct {
Browser string `json:"browser"`
// Additional error logging configuration.
Logger *logger `json:"logger"`
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifier `json:"notify"`
@ -54,15 +54,13 @@ func newServerConfigV16() *serverConfigV16 {
srvCfg := &serverConfigV16{
Version: v16,
Region: globalMinioDefaultRegion,
Logger: &logger{},
Logger: &loggers{},
Notify: &notifier{},
}
srvCfg.SetCredential(mustGetNewCredential())
srvCfg.SetBrowser("on")
// Enable console logger by default on a fresh run.
srvCfg.Logger.Console = consoleLogger{
Enable: true,
}
srvCfg.Logger.Console = NewConsoleLogger()
// Make sure to initialize notification configs.
srvCfg.Notify.AMQP = make(map[string]amqpNotify)

View file

@ -87,30 +87,26 @@ func TestServerConfig(t *testing.T) {
t.Errorf("Expecting Webhook config %#v found %#v", mySQLNotify{}, savedNotifyCfg6)
}
serverConfig.Logger.SetConsole(consoleLogger{
Enable: true,
})
consoleLogger := NewConsoleLogger()
serverConfig.Logger.SetConsole(consoleLogger)
consoleCfg := serverConfig.Logger.GetConsole()
if !reflect.DeepEqual(consoleCfg, consoleLogger{Enable: true}) {
t.Errorf("Expecting console logger config %#v found %#v", consoleLogger{Enable: true}, consoleCfg)
if !reflect.DeepEqual(consoleCfg, consoleLogger) {
t.Errorf("Expecting console logger config %#v found %#v", consoleLogger, consoleCfg)
}
// Set new console logger.
serverConfig.Logger.SetConsole(consoleLogger{
Enable: false,
})
consoleLogger.Enable = false
serverConfig.Logger.SetConsole(consoleLogger)
// Set new file logger.
serverConfig.Logger.SetFile(fileLogger{
Enable: true,
})
fileLogger := NewFileLogger("test-log-file")
serverConfig.Logger.SetFile(fileLogger)
fileCfg := serverConfig.Logger.GetFile()
if !reflect.DeepEqual(fileCfg, fileLogger{Enable: true}) {
t.Errorf("Expecting file logger config %#v found %#v", fileLogger{Enable: true}, consoleCfg)
if !reflect.DeepEqual(fileCfg, fileLogger) {
t.Errorf("Expecting file logger config %#v found %#v", fileLogger, fileCfg)
}
// Set new file logger.
serverConfig.Logger.SetFile(fileLogger{
Enable: false,
})
fileLogger.Enable = false
serverConfig.Logger.SetFile(fileLogger)
// Match version.
if serverConfig.GetVersion() != v16 {

74
cmd/console-logger.go Normal file
View file

@ -0,0 +1,74 @@
/*
* Minio Cloud Storage, (C) 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 (
"fmt"
"os"
"github.com/Sirupsen/logrus"
)
// ConsoleLogger - console logger which logs into stderr.
type ConsoleLogger struct {
BaseLogTarget
}
// Fire - log entry handler.
func (logger ConsoleLogger) Fire(entry *logrus.Entry) error {
if !logger.Enable {
return nil
}
msgBytes, err := logger.formatter.Format(entry)
if err == nil {
fmt.Fprintf(os.Stderr, string(msgBytes))
}
return err
}
// String - represents ConsoleLogger as string.
func (logger ConsoleLogger) String() string {
enableStr := "disabled"
if logger.Enable {
enableStr = "enabled"
}
return fmt.Sprintf("console:%s", enableStr)
}
// NewConsoleLogger - return new console logger object.
func NewConsoleLogger() (logger ConsoleLogger) {
logger.Enable = true
logger.formatter = new(logrus.TextFormatter)
return logger
}
// InitConsoleLogger - initializes console logger.
func InitConsoleLogger(logger *ConsoleLogger) {
if !logger.Enable {
return
}
if logger.formatter == nil {
logger.formatter = new(logrus.TextFormatter)
}
return
}

View file

@ -21,8 +21,6 @@ import (
"encoding/base64"
"errors"
"github.com/minio/mc/pkg/console"
"golang.org/x/crypto/bcrypt"
)
@ -41,28 +39,6 @@ var (
)
var secretKeyMaxLen = secretKeyMaxLenAmazon
func mustGetAccessKey() string {
keyBytes := make([]byte, accessKeyMaxLen)
if _, err := rand.Read(keyBytes); err != nil {
console.Fatalf("Unable to generate access key. Err: %s.\n", err)
}
for i := 0; i < accessKeyMaxLen; i++ {
keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen]
}
return string(keyBytes)
}
func mustGetSecretKey() string {
keyBytes := make([]byte, secretKeyMaxLen)
if _, err := rand.Read(keyBytes); err != nil {
console.Fatalf("Unable to generate secret key. Err: %s.\n", err)
}
return string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen])
}
// isAccessKeyValid - validate access key for right length.
func isAccessKeyValid(accessKey string) bool {
return len(accessKey) >= accessKeyMinLen && len(accessKey) <= accessKeyMaxLen
@ -127,9 +103,8 @@ func createCredential(accessKey, secretKey string) (cred credential, err error)
func mustGetNewCredential() credential {
// Generate access key.
keyBytes := make([]byte, accessKeyMaxLen)
if _, err := rand.Read(keyBytes); err != nil {
console.Fatalln("Unable to generate access key.", err)
}
_, err := rand.Read(keyBytes)
fatalIf(err, "Unable to generate access key.")
for i := 0; i < accessKeyMaxLen; i++ {
keyBytes[i] = alphaNumericTable[keyBytes[i]%alphaNumericTableLen]
}
@ -137,15 +112,12 @@ func mustGetNewCredential() credential {
// Generate secret key.
keyBytes = make([]byte, secretKeyMaxLen)
if _, err := rand.Read(keyBytes); err != nil {
console.Fatalln("Unable to generate secret key.", err)
}
_, err = rand.Read(keyBytes)
fatalIf(err, "Unable to generate secret key.")
secretKey := string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen])
cred, err := createCredential(accessKey, secretKey)
if err != nil {
console.Fatalln("Unable to generate new credential.", err)
}
fatalIf(err, "Unable to generate new credential.")
return cred
}

86
cmd/file-logger.go Normal file
View file

@ -0,0 +1,86 @@
/*
* Minio Cloud Storage, (C) 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 (
"fmt"
"os"
"github.com/Sirupsen/logrus"
)
// FileLogger - file logger which logs to a file.
type FileLogger struct {
BaseLogTarget
Filename string `json:"filename"`
file *os.File
}
// Fire - log entry handler.
func (logger FileLogger) Fire(entry *logrus.Entry) (err error) {
if !logger.Enable {
return nil
}
msgBytes, err := logger.formatter.Format(entry)
if err != nil {
return err
}
if _, err = logger.file.Write(msgBytes); err != nil {
return err
}
err = logger.file.Sync()
return err
}
// String - represents ConsoleLogger as string.
func (logger FileLogger) String() string {
enableStr := "disabled"
if logger.Enable {
enableStr = "enabled"
}
return fmt.Sprintf("file:%s:%s", enableStr, logger.Filename)
}
// NewFileLogger - creates new file logger object.
func NewFileLogger(filename string) (logger FileLogger) {
logger.Enable = true
logger.formatter = new(logrus.JSONFormatter)
logger.Filename = filename
return logger
}
// InitFileLogger - initializes file logger.
func InitFileLogger(logger *FileLogger) (err error) {
if !logger.Enable {
return err
}
if logger.formatter == nil {
logger.formatter = new(logrus.JSONFormatter)
}
if logger.file == nil {
logger.file, err = os.OpenFile(logger.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
}
return err
}

View file

@ -17,12 +17,12 @@
package cmd
import (
"errors"
"fmt"
"os"
"github.com/gorilla/mux"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
var gatewayTemplate = `NAME:
@ -73,7 +73,7 @@ func mustGetGatewayCredsFromEnv() (accessKey, secretKey string) {
accessKey = os.Getenv("MINIO_ACCESS_KEY")
secretKey = os.Getenv("MINIO_SECRET_KEY")
if accessKey == "" || secretKey == "" {
console.Fatalln("Access and secret keys are mandatory to run Minio gateway server.")
fatalIf(errors.New("Missing credentials"), "Access and secret keys are mandatory to run Minio gateway server.")
}
return accessKey, secretKey
}
@ -105,7 +105,7 @@ func newGatewayConfig(accessKey, secretKey, region string) error {
})
// Set default printing to console.
srvCfg.Logger.SetConsole(consoleLogger{true})
srvCfg.Logger.SetConsole(NewConsoleLogger())
// Set custom region.
srvCfg.SetRegion(region)
@ -140,12 +140,7 @@ func gatewayMain(ctx *cli.Context) {
// support for S3 backend storage, currently this can
// default to "us-east-1"
err := newGatewayConfig(accessKey, secretKey, "us-east-1")
if err != nil {
console.Fatalf("Unable to initialize gateway config. Error: %s", err)
}
// Enable console logging.
enableConsoleLogger()
fatalIf(err, "Unable to initialize gateway config")
// Get quiet flag from command line argument.
quietFlag := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
@ -154,9 +149,7 @@ func gatewayMain(ctx *cli.Context) {
backendType := ctx.Args().First()
newObject, err := newGatewayLayer(backendType, accessKey, secretKey)
if err != nil {
console.Fatalf("Unable to initialize gateway layer. Error: %s", err)
}
fatalIf(err, "Unable to initialize gateway layer")
initNSLock(false) // Enable local namespace lock.
@ -192,9 +185,9 @@ func gatewayMain(ctx *cli.Context) {
if globalIsSSL {
cert, key = getPublicCertFile(), getPrivateKeyFile()
}
if aerr := apiServer.ListenAndServe(cert, key); aerr != nil {
console.Fatalf("Failed to start minio server. Error: %s\n", aerr)
}
aerr := apiServer.ListenAndServe(cert, key)
fatalIf(aerr, "Failed to start minio server")
}()
apiEndPoints, err := finalizeAPIEndpoints(apiServer.Addr)

View file

@ -20,8 +20,6 @@ import (
"fmt"
"runtime"
"strings"
"github.com/minio/mc/pkg/console"
)
// Prints the formatted startup message.
@ -34,13 +32,13 @@ func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey, bac
endPoint := apiEndPoints[0]
// Configure 'mc', following block prints platform specific information for minio client.
console.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
log.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("$ mc.exe config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey)
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else {
mcMessage := fmt.Sprintf("$ mc config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey)
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
// Prints documentation message.
@ -50,9 +48,7 @@ func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey, bac
// authority and expiry.
if globalIsSSL {
certs, err := readCertificateChain()
if err != nil {
console.Fatalf("Unable to read certificate chain. Error: %s", err)
}
fatalIf(err, "Unable to read certificate chain")
printCertificateMsg(certs)
}
}
@ -61,7 +57,7 @@ func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey, bac
func printGatewayCommonMsg(apiEndpoints []string, accessKey, secretKey string) {
apiEndpointStr := strings.Join(apiEndpoints, " ")
// Colorize the message and print.
console.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
console.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", accessKey)))
console.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", secretKey)))
log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", accessKey)))
log.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", secretKey)))
}

View file

@ -1,44 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 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 "github.com/Sirupsen/logrus"
// consoleLogger - default logger if not other logging is enabled.
type consoleLogger struct {
Enable bool `json:"enable"`
}
func (c *consoleLogger) Validate() error {
return nil
}
// enable console logger.
func enableConsoleLogger() {
clogger := serverConfig.Logger.GetConsole()
if !clogger.Enable {
return
}
consoleLogger := logrus.New()
consoleLogger.Level = logrus.DebugLevel
consoleLogger.Formatter = new(logrus.TextFormatter)
log.mu.Lock()
log.loggers = append(log.loggers, consoleLogger)
log.mu.Unlock()
}

View file

@ -1,90 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 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 (
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/Sirupsen/logrus"
)
type fileLogger struct {
Enable bool `json:"enable"`
Filename string `json:"filename"`
}
func (f *fileLogger) Validate() error {
if !f.Enable {
return nil
}
if f.Filename == "" {
return errors.New("Filename field empty")
}
return nil
}
type localFile struct {
*os.File
}
func enableFileLogger() {
flogger := serverConfig.Logger.GetFile()
if !flogger.Enable || flogger.Filename == "" {
return
}
// Creates the named file with mode 0666, honors system umask.
file, err := os.OpenFile(flogger.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
fatalIf(err, "Unable to open log file.")
fileLogger := logrus.New()
// Add a local file hook.
fileLogger.Hooks.Add(&localFile{file})
// Set default JSON formatter.
fileLogger.Out = ioutil.Discard
fileLogger.Formatter = new(logrus.JSONFormatter)
fileLogger.Level = logrus.DebugLevel // Minimum log level.
log.mu.Lock()
log.loggers = append(log.loggers, fileLogger)
log.mu.Unlock()
}
// Fire fires the file logger hook and logs to the file.
func (l *localFile) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
return fmt.Errorf("Unable to read entry, %v", err)
}
l.File.Write([]byte(line))
l.File.Sync()
return nil
}
// Levels - indicate log levels supported.
func (l *localFile) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
}
}

View file

@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
* 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.
@ -17,141 +17,218 @@
package cmd
import (
"errors"
"fmt"
"io/ioutil"
"path"
"runtime"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/minio/mc/pkg/console"
)
type fields map[string]interface{}
var log = NewLogger()
var log = struct {
loggers []*logrus.Logger // All registered loggers.
mu sync.Mutex
}{}
// logger carries logging configuration for various supported loggers.
// Currently supported loggers are
//
// - console [default]
// - file
type logger struct {
type loggers struct {
sync.RWMutex
Console consoleLogger `json:"console"`
File fileLogger `json:"file"`
// Add new loggers here.
Console ConsoleLogger `json:"console"`
File FileLogger `json:"file"`
}
/// Logger related.
// Validate - Check whether loggers are valid or not.
func (l *loggers) Validate() (err error) {
if l != nil {
fileLogger := l.GetFile()
if fileLogger.Enable && fileLogger.Filename == "" {
err = errors.New("Missing filename for enabled file logger")
}
}
// Validate logger contents
func (l *logger) Validate() error {
if l == nil {
return nil
}
if err := l.Console.Validate(); err != nil {
return fmt.Errorf("`Console` field: %s", err.Error())
}
if err := l.File.Validate(); err != nil {
return fmt.Errorf("`File` field: %s", err.Error())
}
return nil
return err
}
// SetFile set new file logger.
func (l *logger) SetFile(flogger fileLogger) {
func (l *loggers) SetFile(flogger FileLogger) {
l.Lock()
defer l.Unlock()
l.File = flogger
}
// GetFileLogger get current file logger.
func (l *logger) GetFile() fileLogger {
func (l *loggers) GetFile() FileLogger {
l.RLock()
defer l.RUnlock()
return l.File
}
// SetConsole set new console logger.
func (l *logger) SetConsole(clogger consoleLogger) {
func (l *loggers) SetConsole(clogger ConsoleLogger) {
l.Lock()
defer l.Unlock()
l.Console = clogger
}
func (l *logger) GetConsole() consoleLogger {
// GetConsole get current console logger.
func (l *loggers) GetConsole() ConsoleLogger {
l.RLock()
defer l.RUnlock()
return l.Console
}
// Get file, line, function name of the caller.
func callerSource() string {
pc, file, line, success := runtime.Caller(2)
if !success {
file = "<unknown>"
line = 0
}
file = path.Base(file)
name := runtime.FuncForPC(pc).Name()
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.")
return fmt.Sprintf("[%s:%d:%s()]", file, line, name)
// LogTarget - interface for log target.
type LogTarget interface {
Fire(entry *logrus.Entry) error
String() string
}
// BaseLogTarget - base log target.
type BaseLogTarget struct {
Enable bool `json:"enable"`
formatter logrus.Formatter
}
// Logger - higher level logger.
type Logger struct {
logger *logrus.Logger
consoleTarget ConsoleLogger
targets []LogTarget
quiet bool
}
// AddTarget - add logger to this hook.
func (log *Logger) AddTarget(logTarget LogTarget) {
log.targets = append(log.targets, logTarget)
}
// SetConsoleTarget - sets console target to this hook.
func (log *Logger) SetConsoleTarget(consoleTarget ConsoleLogger) {
log.consoleTarget = consoleTarget
}
// Fire - log entry handler to save logs.
func (log *Logger) Fire(entry *logrus.Entry) (err error) {
if err = log.consoleTarget.Fire(entry); err != nil {
log.Printf("Unable to log to console target. %s\n", err)
}
for _, logTarget := range log.targets {
if err = logTarget.Fire(entry); err != nil {
log.Printf("Unable to log to target %s. %s\n", logTarget, err)
}
}
return err
}
// Levels - returns list of log levels support.
func (log *Logger) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}
// EnableQuiet - sets quiet option.
func (log *Logger) EnableQuiet() {
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...)
}
}
// NewLogger - returns new logger.
func NewLogger() *Logger {
logger := logrus.New()
logger.Out = ioutil.Discard
logger.Level = logrus.DebugLevel
log := &Logger{
logger: logger,
consoleTarget: NewConsoleLogger(),
}
logger.Hooks.Add(log)
return log
}
func getSource() string {
var funcName string
pc, filename, lineNum, ok := runtime.Caller(2)
if ok {
filename = path.Base(filename)
funcName = strings.TrimPrefix(runtime.FuncForPC(pc).Name(), "github.com/minio/minio/cmd.")
} else {
filename = "<unknown>"
lineNum = 0
}
return fmt.Sprintf("[%s:%d:%s()]", filename, lineNum, funcName)
}
func logIf(level logrus.Level, source string, err error, msg string, data ...interface{}) {
isErrIgnored := func(err error) (ok bool) {
err = errorCause(err)
switch err.(type) {
case BucketNotFound, BucketNotEmpty, BucketExists:
ok = true
case ObjectNotFound, ObjectExistsAsDirectory, BucketPolicyNotFound, InvalidUploadID, BadDigest:
ok = true
}
return ok
}
if err == nil || isErrIgnored(err) {
return
}
fields := logrus.Fields{
"source": source,
"cause": err.Error(),
}
if terr, ok := err.(*Error); ok {
fields["stack"] = strings.Join(terr.Trace(), " ")
}
switch level {
case logrus.PanicLevel:
log.logger.WithFields(fields).Panicf(msg, data...)
case logrus.FatalLevel:
log.logger.WithFields(fields).Fatalf(msg, data...)
case logrus.ErrorLevel:
log.logger.WithFields(fields).Errorf(msg, data...)
case logrus.WarnLevel:
log.logger.WithFields(fields).Warnf(msg, data...)
case logrus.InfoLevel:
log.logger.WithFields(fields).Infof(msg, data...)
default:
log.logger.WithFields(fields).Debugf(msg, data...)
}
}
// errorIf synonymous with fatalIf but doesn't exit on error != nil
func errorIf(err error, msg string, data ...interface{}) {
if err == nil || !isErrLogged(err) {
return
}
source := callerSource()
fields := logrus.Fields{
"source": source,
"cause": err.Error(),
}
if e, ok := err.(*Error); ok {
fields["stack"] = strings.Join(e.Trace(), " ")
}
for _, log := range log.loggers {
log.WithFields(fields).Errorf(msg, data...)
}
logIf(logrus.ErrorLevel, getSource(), err, msg, data...)
}
// fatalIf wrapper function which takes error and prints jsonic error messages.
func fatalIf(err error, msg string, data ...interface{}) {
if err == nil || !isErrLogged(err) {
return
}
source := callerSource()
fields := logrus.Fields{
"source": source,
"cause": err.Error(),
}
if e, ok := err.(*Error); ok {
fields["stack"] = strings.Join(e.Trace(), " ")
}
for _, log := range log.loggers {
log.WithFields(fields).Fatalf(msg, data...)
}
}
// returns false if error is not supposed to be logged.
func isErrLogged(err error) (ok bool) {
ok = true
err = errorCause(err)
switch err.(type) {
case BucketNotFound, BucketNotEmpty, BucketExists:
ok = false
case ObjectNotFound, ObjectExistsAsDirectory:
ok = false
case BucketPolicyNotFound, InvalidUploadID:
ok = false
case BadDigest:
ok = false
}
return ok
logIf(logrus.FatalLevel, getSource(), err, msg, data...)
}

View file

@ -1,5 +1,5 @@
/*
* Minio Cloud Storage (C) 2015 Minio, Inc.
* 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.
@ -17,48 +17,15 @@
package cmd
import (
"bytes"
"encoding/json"
"errors"
"testing"
"github.com/Sirupsen/logrus"
)
// Tests callerSource.
func TestCallerSource(t *testing.T) {
currentSource := func() string { return callerSource() }
// Tests getSource().
func TestGetSource(t *testing.T) {
currentSource := func() string { return getSource() }
gotSource := currentSource()
expectedSource := "[logger_test.go:31:TestCallerSource()]"
expectedSource := "[logger_test.go:26:TestGetSource()]"
if gotSource != expectedSource {
t.Errorf("expected : %s, got : %s", expectedSource, gotSource)
}
}
// Tests error logger.
func TestLogger(t *testing.T) {
var buffer bytes.Buffer
var fields logrus.Fields
testLog := logrus.New()
testLog.Out = &buffer
testLog.Formatter = new(logrus.JSONFormatter)
log.mu.Lock()
log.loggers = append(log.loggers, testLog)
log.mu.Unlock()
errorIf(errors.New("Fake error"), "Failed with error.")
err := json.Unmarshal(buffer.Bytes(), &fields)
if err != nil {
t.Fatal(err)
}
if fields["level"] != "error" {
t.Fatalf("Expected error, got %s", fields["level"])
}
msg, ok := fields["cause"]
if !ok {
t.Fatal("Cause field missing")
}
if msg != "Fake error" {
t.Fatal("Cause field has unexpected message", msg)
}
}

View file

@ -194,7 +194,7 @@ func (n *nsLockMap) unlock(volume, path, opsID string, readLock bool) {
func (n *nsLockMap) Lock(volume, path, opsID string) {
readLock := false // This is a write lock.
lockSource := callerSource() // Useful for debugging
lockSource := getSource() // Useful for debugging
n.lock(volume, path, lockSource, opsID, readLock)
}
@ -208,7 +208,7 @@ func (n *nsLockMap) Unlock(volume, path, opsID string) {
func (n *nsLockMap) RLock(volume, path, opsID string) {
readLock := true
lockSource := callerSource() // Useful for debugging
lockSource := getSource() // Useful for debugging
n.lock(volume, path, lockSource, opsID, readLock)
}
@ -269,7 +269,7 @@ func (n *nsLockMap) NewNSLock(volume, path string) RWLocker {
// Lock - block until write lock is taken.
func (li *lockInstance) Lock() {
lockSource := callerSource()
lockSource := getSource()
readLock := false
li.ns.lock(li.volume, li.path, lockSource, li.opsID, readLock)
}
@ -282,7 +282,7 @@ func (li *lockInstance) Unlock() {
// RLock - block until read lock is taken.
func (li *lockInstance) RLock() {
lockSource := callerSource()
lockSource := getSource()
readLock := true
li.ns.lock(li.volume, li.path, lockSource, li.opsID, readLock)
}

View file

@ -183,7 +183,7 @@ func printRetryMsg(sErrs []error, storageDisks []StorageAPI) {
for i, sErr := range sErrs {
switch sErr {
case errDiskNotFound, errFaultyDisk, errFaultyRemoteDisk:
console.Printf("Disk %s is still unreachable, with error %s\n", storageDisks[i], sErr)
errorIf(sErr, "Disk %s is still unreachable", storageDisks[i])
}
}
}

View file

@ -31,7 +31,6 @@ import (
"runtime"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
var serverFlags = []cli.Flag{
@ -89,7 +88,7 @@ func checkUpdate(mode string) {
// Its OK to ignore any errors during getUpdateInfo() here.
if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil {
if older > time.Duration(0) {
console.Println(colorizeUpdateMessage(downloadURL, older))
log.Println(colorizeUpdateMessage(downloadURL, older))
}
}
}
@ -109,10 +108,19 @@ func migrate() {
}
func enableLoggers() {
// Enable all loggers here.
enableConsoleLogger()
enableFileLogger()
// Add your logger here.
fileLogTarget := serverConfig.Logger.GetFile()
if fileLogTarget.Enable {
err := InitFileLogger(&fileLogTarget)
fatalIf(err, "Unable to initialize file logger")
log.AddTarget(fileLogTarget)
}
consoleLogTarget := serverConfig.Logger.GetConsole()
if consoleLogTarget.Enable {
InitConsoleLogger(&consoleLogTarget)
}
log.SetConsoleTarget(consoleLogTarget)
}
// Initializes a new config if it doesn't exist, else migrates any old config
@ -124,9 +132,8 @@ func initConfig() {
var cred credential
var err error
if accessKey != "" && secretKey != "" {
if cred, err = createCredential(accessKey, secretKey); err != nil {
console.Fatalf("Invalid access/secret Key set in environment. Err: %s.\n", err)
}
cred, err = createCredential(accessKey, secretKey)
fatalIf(err, "Invalid access/secret Key set in environment.")
// credential Envs are set globally.
globalIsEnvCreds = true
@ -135,7 +142,7 @@ func initConfig() {
browser := os.Getenv("MINIO_BROWSER")
if browser != "" {
if !(strings.EqualFold(browser, "off") || strings.EqualFold(browser, "on")) {
console.Fatalf("Invalid value %s in MINIO_BROWSER environment variable.", browser)
fatalIf(errors.New("invalid value"), "%s in MINIO_BROWSER environment variable.", browser)
}
// browser Envs are set globally, this doesn't represent
@ -150,10 +157,9 @@ func initConfig() {
// Config file does not exist, we create it fresh and return upon success.
if !isConfigFileExists() {
if err := newConfig(envs); err != nil {
console.Fatalf("Unable to initialize minio config for the first time. Error: %s.\n", err)
}
console.Println("Created minio configuration file successfully at " + getConfigDir())
err := newConfig(envs)
fatalIf(err, "Unable to initialize minio config for the first time.")
log.Println("Created minio configuration file successfully at " + getConfigDir())
return
}
@ -161,14 +167,12 @@ func initConfig() {
migrate()
// Validate config file
if err := validateConfig(); err != nil {
console.Fatalf("Cannot validate configuration file. Error: %s\n", err)
}
err = validateConfig()
fatalIf(err, "Cannot validate configuration file")
// Once we have migrated all the old config, now load them.
if err := loadConfig(envs); err != nil {
console.Fatalf("Unable to initialize minio config. Error: %s.\n", err)
}
err = loadConfig(envs)
fatalIf(err, "Unable to initialize minio config")
}
// Generic Minio initialization to create/load config, prepare loggers, etc..
@ -464,6 +468,9 @@ func serverMain(c *cli.Context) {
// Get quiet flag from command line argument.
quietFlag := c.Bool("quiet") || c.GlobalBool("quiet")
if quietFlag {
log.EnableQuiet()
}
// Get configuration directory from command line argument.
configDir := c.String("config-dir")
@ -471,7 +478,7 @@ func serverMain(c *cli.Context) {
configDir = c.GlobalString("config-dir")
}
if configDir == "" {
console.Fatalln("Configuration directory cannot be empty.")
fatalIf(errors.New("empty directory"), "Configuration directory cannot be empty.")
}
// Set configuration directory.

View file

@ -23,7 +23,6 @@ import (
"strings"
humanize "github.com/dustin/go-humanize"
"github.com/minio/mc/pkg/console"
)
// Documentation links, these are part of message printing code.
@ -78,14 +77,14 @@ func printServerCommonMsg(apiEndpoints []string) {
apiEndpointStr := strings.Join(apiEndpoints, " ")
// Colorize the message and print.
console.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
console.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey)))
console.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey)))
console.Println(colorBlue("Region: ") + colorBold(fmt.Sprintf(getFormatStr(len(region), 3), region)))
log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey)))
log.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey)))
log.Println(colorBlue("Region: ") + colorBold(fmt.Sprintf(getFormatStr(len(region), 3), region)))
printEventNotifiers()
console.Println(colorBlue("\nBrowser Access:"))
console.Println(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 3), apiEndpointStr))
log.Println(colorBlue("\nBrowser Access:"))
log.Println(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 3), apiEndpointStr))
}
// Prints bucket notification configurations.
@ -103,7 +102,7 @@ func printEventNotifiers() {
for queueArn := range externalTargets {
arnMsg += colorBold(fmt.Sprintf(getFormatStr(len(queueArn), 1), queueArn))
}
console.Println(arnMsg)
log.Println(arnMsg)
}
// Prints startup message for command line access. Prints link to our documentation
@ -113,23 +112,23 @@ func printCLIAccessMsg(endPoint string) {
cred := serverConfig.GetCredential()
// Configure 'mc', following block prints platform specific information for minio client.
console.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
log.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("$ mc.exe config host add myminio %s %s %s", endPoint, cred.AccessKey, cred.SecretKey)
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else {
mcMessage := fmt.Sprintf("$ mc config host add myminio %s %s %s", endPoint, cred.AccessKey, cred.SecretKey)
console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
}
// Prints startup message for Object API acces, prints link to our SDK documentation.
func printObjectAPIMsg() {
console.Println(colorBlue("\nObject API (Amazon S3 compatible):"))
console.Println(colorBlue(" Go: ") + fmt.Sprintf(getFormatStr(len(goQuickStartGuide), 8), goQuickStartGuide))
console.Println(colorBlue(" Java: ") + fmt.Sprintf(getFormatStr(len(javaQuickStartGuide), 6), javaQuickStartGuide))
console.Println(colorBlue(" Python: ") + fmt.Sprintf(getFormatStr(len(pyQuickStartGuide), 4), pyQuickStartGuide))
console.Println(colorBlue(" JavaScript: ") + jsQuickStartGuide)
log.Println(colorBlue("\nObject API (Amazon S3 compatible):"))
log.Println(colorBlue(" Go: ") + fmt.Sprintf(getFormatStr(len(goQuickStartGuide), 8), goQuickStartGuide))
log.Println(colorBlue(" Java: ") + fmt.Sprintf(getFormatStr(len(javaQuickStartGuide), 6), javaQuickStartGuide))
log.Println(colorBlue(" Python: ") + fmt.Sprintf(getFormatStr(len(pyQuickStartGuide), 4), pyQuickStartGuide))
log.Println(colorBlue(" JavaScript: ") + jsQuickStartGuide)
}
// Get formatted disk/storage info message.
@ -149,8 +148,8 @@ func getStorageInfoMsg(storageInfo StorageInfo) string {
// Prints startup message of storage capacity and erasure information.
func printStorageInfo(storageInfo StorageInfo) {
console.Println()
console.Println(getStorageInfoMsg(storageInfo))
log.Println()
log.Println(getStorageInfoMsg(storageInfo))
}
// Prints certificate expiry date warning
@ -173,6 +172,5 @@ func getCertificateChainMsg(certs []*x509.Certificate) string {
// Prints the certificate expiry message.
func printCertificateMsg(certs []*x509.Certificate) {
console.Println(getCertificateChainMsg(certs))
log.Println(getCertificateChainMsg(certs))
}

View file

@ -30,7 +30,6 @@ import (
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
// Check for new software updates.
@ -111,9 +110,7 @@ func isDocker(cgroupFile string) (bool, error) {
// IsDocker - returns if the environment is docker or not.
func IsDocker() bool {
found, err := isDocker("/proc/self/cgroup")
if err != nil {
console.Fatalf("Error in docker check: %s", err)
}
fatalIf(err, "Error in docker check.")
return found
}
@ -263,25 +260,23 @@ func mainUpdate(ctx *cli.Context) {
}
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
quietPrintln := func(args ...interface{}) {
if !quiet {
console.Println(args...)
}
if quiet {
log.EnableQuiet()
}
minioMode := ""
older, downloadURL, err := getUpdateInfo(10*time.Second, minioMode)
if err != nil {
quietPrintln(err)
log.Println(err)
os.Exit(-1)
}
if older != time.Duration(0) {
quietPrintln(colorizeUpdateMessage(downloadURL, older))
log.Println(colorizeUpdateMessage(downloadURL, older))
os.Exit(1)
}
colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc()
quietPrintln(colorSprintf("You are already running the most recent version of minio."))
log.Println(colorSprintf("You are already running the most recent version of minio."))
os.Exit(0)
}