mirror of
https://github.com/minio/minio.git
synced 2025-01-27 06:33:18 -05:00
Add new console/http loggers (#6066)
- Add console target logging, enabled by default. - Add http target logging, which supports an endpoint with basic authentication (username/password are passed in the endpoint url itself) - HTTP target logging is asynchronous and some logs can be dropped if channel buffer (10000) is full
This commit is contained in:
parent
b1c9eb0e01
commit
9c5e971a58
@ -38,7 +38,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
configJSON = []byte(`{
|
configJSON = []byte(`{
|
||||||
"version": "26",
|
"version": "27",
|
||||||
"credential": {
|
"credential": {
|
||||||
"accessKey": "minio",
|
"accessKey": "minio",
|
||||||
"secretKey": "minio123"
|
"secretKey": "minio123"
|
||||||
@ -162,7 +162,19 @@ var (
|
|||||||
"endpoint": ""
|
"endpoint": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"logger": {
|
||||||
|
"console": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"1": {
|
||||||
|
"enabled": false,
|
||||||
|
"endpoint": "http://user:example@localhost:9001/api/endpoint"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}`)
|
}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,6 +81,20 @@ func initConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load logger targets based on user's configuration
|
||||||
|
func loadLoggers() {
|
||||||
|
if globalServerConfig.Logger.Console.Enabled {
|
||||||
|
// Enable console logging
|
||||||
|
logger.AddTarget(logger.NewConsole())
|
||||||
|
}
|
||||||
|
for _, l := range globalServerConfig.Logger.HTTP {
|
||||||
|
if l.Enabled {
|
||||||
|
// Enable http logging
|
||||||
|
logger.AddTarget(logger.NewHTTP(l.Endpoint, NewCustomHTTPTransport()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleCommonCmdArgs(ctx *cli.Context) {
|
func handleCommonCmdArgs(ctx *cli.Context) {
|
||||||
|
|
||||||
var configDir string
|
var configDir string
|
||||||
|
@ -41,9 +41,9 @@ import (
|
|||||||
// 6. Make changes in config-current_test.go for any test change
|
// 6. Make changes in config-current_test.go for any test change
|
||||||
|
|
||||||
// Config version
|
// Config version
|
||||||
const serverConfigVersion = "26"
|
const serverConfigVersion = "27"
|
||||||
|
|
||||||
type serverConfig = serverConfigV26
|
type serverConfig = serverConfigV27
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// globalServerConfig server config.
|
// globalServerConfig server config.
|
||||||
@ -260,6 +260,8 @@ func (s *serverConfig) ConfigDiff(t *serverConfig) string {
|
|||||||
return "MySQL Notification configuration differs"
|
return "MySQL Notification configuration differs"
|
||||||
case !reflect.DeepEqual(s.Notify.MQTT, t.Notify.MQTT):
|
case !reflect.DeepEqual(s.Notify.MQTT, t.Notify.MQTT):
|
||||||
return "MQTT Notification configuration differs"
|
return "MQTT Notification configuration differs"
|
||||||
|
case !reflect.DeepEqual(s.Logger, t.Logger):
|
||||||
|
return "Logger configuration differs"
|
||||||
case reflect.DeepEqual(s, t):
|
case reflect.DeepEqual(s, t):
|
||||||
return ""
|
return ""
|
||||||
default:
|
default:
|
||||||
@ -315,6 +317,13 @@ func newServerConfig() *serverConfig {
|
|||||||
srvCfg.Cache.Exclude = make([]string, 0)
|
srvCfg.Cache.Exclude = make([]string, 0)
|
||||||
srvCfg.Cache.Expiry = globalCacheExpiry
|
srvCfg.Cache.Expiry = globalCacheExpiry
|
||||||
srvCfg.Cache.MaxUse = globalCacheMaxUse
|
srvCfg.Cache.MaxUse = globalCacheMaxUse
|
||||||
|
|
||||||
|
// Console logging is on by default
|
||||||
|
srvCfg.Logger.Console.Enabled = true
|
||||||
|
// Create an example of HTTP logger
|
||||||
|
srvCfg.Logger.HTTP = make(map[string]loggerHTTP)
|
||||||
|
srvCfg.Logger.HTTP["target1"] = loggerHTTP{Endpoint: "https://username:password@example.com/api"}
|
||||||
|
|
||||||
return srvCfg
|
return srvCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +319,18 @@ func TestConfigDiff(t *testing.T) {
|
|||||||
&serverConfig{Notify: notifier{MQTT: map[string]target.MQTTArgs{"1": {Enable: false}}}},
|
&serverConfig{Notify: notifier{MQTT: map[string]target.MQTTArgs{"1": {Enable: false}}}},
|
||||||
"MQTT Notification configuration differs",
|
"MQTT Notification configuration differs",
|
||||||
},
|
},
|
||||||
|
// 16
|
||||||
|
{
|
||||||
|
&serverConfig{Logger: loggerConfig{
|
||||||
|
Console: loggerConsole{Enabled: true},
|
||||||
|
HTTP: map[string]loggerHTTP{"1": {Endpoint: "http://address1"}},
|
||||||
|
}},
|
||||||
|
&serverConfig{Logger: loggerConfig{
|
||||||
|
Console: loggerConsole{Enabled: true},
|
||||||
|
HTTP: map[string]loggerHTTP{"1": {Endpoint: "http://address2"}},
|
||||||
|
}},
|
||||||
|
"Logger configuration differs",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
|
@ -188,6 +188,11 @@ func migrateConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case "26":
|
||||||
|
if err = migrateV26ToV27(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
case serverConfigVersion:
|
case serverConfigVersion:
|
||||||
// No migration needed. this always points to current version.
|
// No migration needed. this always points to current version.
|
||||||
err = nil
|
err = nil
|
||||||
@ -2317,3 +2322,36 @@ func migrateV25ToV26() error {
|
|||||||
logger.Info(configMigrateMSGTemplate, configFile, cv25.Version, srvConfig.Version)
|
logger.Info(configMigrateMSGTemplate, configFile, cv25.Version, srvConfig.Version)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateV26ToV27() error {
|
||||||
|
configFile := getConfigFile()
|
||||||
|
|
||||||
|
// config V27 is backward compatible with V26, load the old
|
||||||
|
// config file in serverConfigV27 struct and put some examples
|
||||||
|
// in the new `logger` field
|
||||||
|
srvConfig := &serverConfigV27{}
|
||||||
|
_, err := quick.LoadConfig(configFile, globalEtcdClient, srvConfig)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("Unable to load config file. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srvConfig.Version != "26" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
srvConfig.Version = "27"
|
||||||
|
// Enable console logging by default to avoid breaking users
|
||||||
|
// current deployments
|
||||||
|
srvConfig.Logger.Console.Enabled = true
|
||||||
|
srvConfig.Logger.HTTP = make(map[string]loggerHTTP)
|
||||||
|
srvConfig.Logger.HTTP["1"] = loggerHTTP{}
|
||||||
|
|
||||||
|
if err = quick.SaveConfig(srvConfig, configFile, globalEtcdClient); err != nil {
|
||||||
|
return fmt.Errorf("Failed to migrate config from ‘26’ to ‘27’. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(configMigrateMSGTemplate, configFile, "26", "27")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -134,10 +134,25 @@ func TestServerConfigMigrateInexistentConfig(t *testing.T) {
|
|||||||
if err := migrateV21ToV22(); err != nil {
|
if err := migrateV21ToV22(); err != nil {
|
||||||
t.Fatal("migrate v21 to v22 should succeed when no config file is found")
|
t.Fatal("migrate v21 to v22 should succeed when no config file is found")
|
||||||
}
|
}
|
||||||
|
if err := migrateV22ToV23(); err != nil {
|
||||||
|
t.Fatal("migrate v22 to v23 should succeed when no config file is found")
|
||||||
|
}
|
||||||
|
if err := migrateV23ToV24(); err != nil {
|
||||||
|
t.Fatal("migrate v23 to v24 should succeed when no config file is found")
|
||||||
|
}
|
||||||
|
if err := migrateV24ToV25(); err != nil {
|
||||||
|
t.Fatal("migrate v24 to v25 should succeed when no config file is found")
|
||||||
|
}
|
||||||
|
if err := migrateV25ToV26(); err != nil {
|
||||||
|
t.Fatal("migrate v25 to v26 should succeed when no config file is found")
|
||||||
|
}
|
||||||
|
if err := migrateV26ToV27(); err != nil {
|
||||||
|
t.Fatal("migrate v26 to v27 should succeed when no config file is found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test if a config migration from v2 to v23 is successfully done
|
// Test if a config migration from v2 to v27 is successfully done
|
||||||
func TestServerConfigMigrateV2toV23(t *testing.T) {
|
func TestServerConfigMigrateV2toV27(t *testing.T) {
|
||||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Init Test config failed")
|
t.Fatalf("Init Test config failed")
|
||||||
@ -272,6 +287,18 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
|
|||||||
if err := migrateV22ToV23(); err == nil {
|
if err := migrateV22ToV23(); err == nil {
|
||||||
t.Fatal("migrateConfigV22ToV23() should fail with a corrupted json")
|
t.Fatal("migrateConfigV22ToV23() should fail with a corrupted json")
|
||||||
}
|
}
|
||||||
|
if err := migrateV23ToV24(); err == nil {
|
||||||
|
t.Fatal("migrateConfigV23ToV24() should fail with a corrupted json")
|
||||||
|
}
|
||||||
|
if err := migrateV24ToV25(); err == nil {
|
||||||
|
t.Fatal("migrateConfigV24ToV25() should fail with a corrupted json")
|
||||||
|
}
|
||||||
|
if err := migrateV25ToV26(); err == nil {
|
||||||
|
t.Fatal("migrateConfigV25ToV26() should fail with a corrupted json")
|
||||||
|
}
|
||||||
|
if err := migrateV26ToV27(); err == nil {
|
||||||
|
t.Fatal("migrateConfigV26ToV27() should fail with a corrupted json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test if all migrate code returns error with corrupted config files
|
// Test if all migrate code returns error with corrupted config files
|
||||||
|
@ -657,9 +657,6 @@ type serverConfigV25 struct {
|
|||||||
|
|
||||||
// serverConfigV26 is just like version '25', stores additionally
|
// serverConfigV26 is just like version '25', stores additionally
|
||||||
// cache max use value in 'CacheConfig'.
|
// cache max use value in 'CacheConfig'.
|
||||||
//
|
|
||||||
// IMPORTANT NOTE: When updating this struct make sure that
|
|
||||||
// serverConfig.ConfigDiff() is updated as necessary.
|
|
||||||
type serverConfigV26 struct {
|
type serverConfigV26 struct {
|
||||||
quick.Config `json:"-"` // ignore interfaces
|
quick.Config `json:"-"` // ignore interfaces
|
||||||
|
|
||||||
@ -681,3 +678,47 @@ type serverConfigV26 struct {
|
|||||||
// Notification queue configuration.
|
// Notification queue configuration.
|
||||||
Notify notifier `json:"notify"`
|
Notify notifier `json:"notify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loggerConsole struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerHTTP struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerConfig struct {
|
||||||
|
Console loggerConsole `json:"console"`
|
||||||
|
HTTP map[string]loggerHTTP `json:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverConfigV27 is just like version '26', stores additionally
|
||||||
|
// the logger field
|
||||||
|
//
|
||||||
|
// IMPORTANT NOTE: When updating this struct make sure that
|
||||||
|
// serverConfig.ConfigDiff() is updated as necessary.
|
||||||
|
type serverConfigV27 struct {
|
||||||
|
quick.Config `json:"-"` // ignore interfaces
|
||||||
|
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// S3 API configuration.
|
||||||
|
Credential auth.Credentials `json:"credential"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
Browser BoolFlag `json:"browser"`
|
||||||
|
Worm BoolFlag `json:"worm"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
|
||||||
|
// Storage class configuration
|
||||||
|
StorageClass storageClassConfig `json:"storageclass"`
|
||||||
|
|
||||||
|
// Cache configuration
|
||||||
|
Cache CacheConfig `json:"cache"`
|
||||||
|
|
||||||
|
// Notification queue configuration.
|
||||||
|
Notify notifier `json:"notify"`
|
||||||
|
|
||||||
|
// Logger configuration
|
||||||
|
Logger loggerConfig `json:"logger"`
|
||||||
|
}
|
||||||
|
@ -159,6 +159,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
// Initialize gateway config.
|
// Initialize gateway config.
|
||||||
initConfig()
|
initConfig()
|
||||||
|
|
||||||
|
// Load logger subsystem
|
||||||
|
loadLoggers()
|
||||||
|
|
||||||
// Check and load SSL certificates.
|
// Check and load SSL certificates.
|
||||||
var err error
|
var err error
|
||||||
globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig()
|
globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig()
|
||||||
|
101
cmd/logger/console.go
Normal file
101
cmd/logger/console.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsoleTarget implements loggerTarget to send log
|
||||||
|
// in plain or json format to the standard output.
|
||||||
|
type ConsoleTarget struct{}
|
||||||
|
|
||||||
|
func (c *ConsoleTarget) send(entry logEntry) error {
|
||||||
|
if jsonFlag {
|
||||||
|
logJSON, err := json.Marshal(&entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(logJSON))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := make([]string, len(entry.Trace.Source))
|
||||||
|
|
||||||
|
// Add a sequence number and formatting for each stack trace
|
||||||
|
// No formatting is required for the first entry
|
||||||
|
for i, element := range entry.Trace.Source {
|
||||||
|
trace[i] = fmt.Sprintf("%8v: %s", i+1, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagString := ""
|
||||||
|
for key, value := range entry.Trace.Variables {
|
||||||
|
if value != "" {
|
||||||
|
if tagString != "" {
|
||||||
|
tagString += ", "
|
||||||
|
}
|
||||||
|
tagString += key + "=" + value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiString := "API: " + entry.API.Name + "("
|
||||||
|
if entry.API.Args != nil && entry.API.Args.Bucket != "" {
|
||||||
|
apiString = apiString + "bucket=" + entry.API.Args.Bucket
|
||||||
|
}
|
||||||
|
if entry.API.Args != nil && entry.API.Args.Object != "" {
|
||||||
|
apiString = apiString + ", object=" + entry.API.Args.Object
|
||||||
|
}
|
||||||
|
apiString += ")"
|
||||||
|
timeString := "Time: " + time.Now().Format(loggerTimeFormat)
|
||||||
|
|
||||||
|
var requestID string
|
||||||
|
if entry.RequestID != "" {
|
||||||
|
requestID = "\nRequestID: " + entry.RequestID
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteHost string
|
||||||
|
if entry.RemoteHost != "" {
|
||||||
|
remoteHost = "\nRemoteHost: " + entry.RemoteHost
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAgent string
|
||||||
|
if entry.UserAgent != "" {
|
||||||
|
userAgent = "\nUserAgent: " + entry.UserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entry.Trace.Variables) > 0 {
|
||||||
|
tagString = "\n " + tagString
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = colorFgRed(colorBold(entry.Trace.Message))
|
||||||
|
var output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s",
|
||||||
|
apiString, timeString, requestID, remoteHost, userAgent,
|
||||||
|
msg, tagString, strings.Join(trace, "\n"))
|
||||||
|
|
||||||
|
fmt.Println(output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConsole initializes a new logger target
|
||||||
|
// which prints log directly in the standard
|
||||||
|
// output.
|
||||||
|
func NewConsole() LoggingTarget {
|
||||||
|
return &ConsoleTarget{}
|
||||||
|
}
|
88
cmd/logger/http.go
Normal file
88
cmd/logger/http.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPTarget implements loggerTarget and sends the json
|
||||||
|
// format of a log entry to the configured http endpoint.
|
||||||
|
// An internal buffer of logs is maintained but when the
|
||||||
|
// buffer is full, new logs are just ignored and an error
|
||||||
|
// is returned to the caller.
|
||||||
|
type HTTPTarget struct {
|
||||||
|
// Channel of log entries
|
||||||
|
logCh chan logEntry
|
||||||
|
// HTTP(s) endpoint
|
||||||
|
endpoint string
|
||||||
|
client http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPTarget) startHTTPLogger() {
|
||||||
|
// Create a routine which sends json logs received
|
||||||
|
// from an internal channel.
|
||||||
|
go func() {
|
||||||
|
for entry := range h.logCh {
|
||||||
|
logJSON, err := json.Marshal(&entry)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", h.endpoint, bytes.NewBuffer(logJSON))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := h.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp.Body != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTP initializes a new logger target which
|
||||||
|
// sends log over http to the specified endpoint
|
||||||
|
func NewHTTP(endpoint string, transport *http.Transport) LoggingTarget {
|
||||||
|
h := HTTPTarget{
|
||||||
|
endpoint: endpoint,
|
||||||
|
client: http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
},
|
||||||
|
logCh: make(chan logEntry, 10000),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.startHTTPLogger()
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPTarget) send(entry logEntry) error {
|
||||||
|
select {
|
||||||
|
case h.logCh <- entry:
|
||||||
|
default:
|
||||||
|
// log channel is full, do not wait and return
|
||||||
|
// an error immediately to the caller
|
||||||
|
return errors.New("log buffer full")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -299,10 +299,8 @@ func LogIf(ctx context.Context, err error) {
|
|||||||
// Get the cause for the Error
|
// Get the cause for the Error
|
||||||
message := err.Error()
|
message := err.Error()
|
||||||
|
|
||||||
// Output the formatted log message at console
|
entry := logEntry{
|
||||||
var output string
|
DeploymentID: deploymentID,
|
||||||
if jsonFlag {
|
|
||||||
logJSON, err := json.Marshal(&logEntry{
|
|
||||||
Level: ErrorLvl.String(),
|
Level: ErrorLvl.String(),
|
||||||
RemoteHost: req.RemoteHost,
|
RemoteHost: req.RemoteHost,
|
||||||
RequestID: req.RequestID,
|
RequestID: req.RequestID,
|
||||||
@ -310,63 +308,12 @@ func LogIf(ctx context.Context, err error) {
|
|||||||
Time: time.Now().UTC().Format(time.RFC3339Nano),
|
Time: time.Now().UTC().Format(time.RFC3339Nano),
|
||||||
API: &api{Name: API, Args: &args{Bucket: req.BucketName, Object: req.ObjectName}},
|
API: &api{Name: API, Args: &args{Bucket: req.BucketName, Object: req.ObjectName}},
|
||||||
Trace: &traceEntry{Message: message, Source: trace, Variables: tags},
|
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 := ""
|
// Iterate over all logger targets to send the log entry
|
||||||
for key, value := range tags {
|
for _, t := range Targets {
|
||||||
if value != "" {
|
t.send(entry)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg = colorFgRed(colorBold(message))
|
|
||||||
output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s",
|
|
||||||
apiString, timeString, requestID, remoteHost, userAgent,
|
|
||||||
msg, tagString, strings.Join(trace, "\n"))
|
|
||||||
}
|
|
||||||
fmt.Println(output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrCritical is the value panic'd whenever CriticalIf is called.
|
// ErrCritical is the value panic'd whenever CriticalIf is called.
|
||||||
|
33
cmd/logger/targets.go
Normal file
33
cmd/logger/targets.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 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
|
||||||
|
|
||||||
|
// LoggingTarget is the entity that we will receive
|
||||||
|
// a single log entry and send it to the log target
|
||||||
|
// e.g. send the log to a http server
|
||||||
|
type LoggingTarget interface {
|
||||||
|
send(entry logEntry) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Targets is the set of enabled loggers
|
||||||
|
var Targets = []LoggingTarget{}
|
||||||
|
|
||||||
|
// AddTarget adds a new logger target to the
|
||||||
|
// list of enabled loggers
|
||||||
|
func AddTarget(t LoggingTarget) {
|
||||||
|
Targets = append(Targets, t)
|
||||||
|
}
|
@ -221,6 +221,9 @@ func serverMain(ctx *cli.Context) {
|
|||||||
// Initialize server config.
|
// Initialize server config.
|
||||||
initConfig()
|
initConfig()
|
||||||
|
|
||||||
|
// Load logger subsystem
|
||||||
|
loadLoggers()
|
||||||
|
|
||||||
// Check and load SSL certificates.
|
// Check and load SSL certificates.
|
||||||
var err error
|
var err error
|
||||||
globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig()
|
globalPublicCerts, globalRootCAs, globalTLSCerts, globalIsSSL, err = getSSLConfig()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user