Allow logging targets to be configured to receive minio (#8347)

specific errors, `application` errors or `all` by default.

console logging on server by default lists all logs -
enhance admin console API to accept `type` as query parameter to
subscribe to application/minio logs.
This commit is contained in:
poornas 2019-10-11 18:50:54 -07:00 committed by Harshavardhana
parent 8964ef821f
commit d7060c4c32
28 changed files with 116 additions and 72 deletions

View File

@ -194,7 +194,7 @@ func (a adminAPIHandlers) ServiceActionHandler(w http.ResponseWriter, r *http.Re
case madmin.ServiceActionStop: case madmin.ServiceActionStop:
serviceSig = serviceStop serviceSig = serviceStop
default: default:
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action)) logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
return return
} }
@ -705,7 +705,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
if hip.clientToken == "" { if hip.clientToken == "" {
jerr := json.NewDecoder(r).Decode(&hip.hs) jerr := json.NewDecoder(r).Decode(&hip.hs)
if jerr != nil { if jerr != nil {
logger.LogIf(context.Background(), jerr) logger.LogIf(context.Background(), jerr, logger.Application)
err = ErrRequestBodyParse err = ErrRequestBodyParse
return return
} }
@ -1517,7 +1517,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
password := globalServerConfig.GetCredential().SecretKey password := globalServerConfig.GetCredential().SecretKey
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength)) configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return return
} }
@ -1525,7 +1525,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
// Validate JSON provided in the request body: check the // Validate JSON provided in the request body: check the
// client has not sent JSON objects with duplicate keys. // client has not sent JSON objects with duplicate keys.
if err = quick.CheckDuplicateKeys(string(configBytes)); err != nil { if err = quick.CheckDuplicateKeys(string(configBytes)); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
return return
} }
@ -1656,6 +1656,13 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
if err != nil { if err != nil {
limitLines = 10 limitLines = 10
} }
logKind := r.URL.Query().Get("logType")
if logKind == "" {
logKind = string(logger.All)
}
logKind = strings.ToUpper(logKind)
// Avoid reusing tcp connection if read timeout is hit // Avoid reusing tcp connection if read timeout is hit
// This is needed to make r.Context().Done() work as // This is needed to make r.Context().Done() work as
// expected in case of read timeout // expected in case of read timeout
@ -1672,7 +1679,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
return return
} }
globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, nil) globalConsoleSys.Subscribe(logCh, doneCh, node, limitLines, logKind, nil)
for _, peer := range peers { for _, peer := range peers {
if node == "" || strings.EqualFold(peer.host.Name, node) { if node == "" || strings.EqualFold(peer.host.Name, node) {
@ -1689,7 +1696,7 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
select { select {
case entry := <-logCh: case entry := <-logCh:
log := entry.(madmin.LogInfo) log := entry.(madmin.LogInfo)
if log.SendLog(node) { if log.SendLog(node, logKind) {
if err := enc.Encode(log); err != nil { if err := enc.Encode(log); err != nil {
return return
} }

View File

@ -142,7 +142,7 @@ func checkAdminRequestAuthType(ctx context.Context, r *http.Request, region stri
if s3Err != ErrNone { if s3Err != ErrNone {
reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r)) reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r))
ctx := logger.SetReqInfo(ctx, reqInfo) ctx := logger.SetReqInfo(ctx, reqInfo)
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description)) logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
} }
return s3Err return s3Err
} }
@ -235,7 +235,7 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
if err != nil { if err != nil {
// Base64 decoding fails, we should log to indicate // Base64 decoding fails, we should log to indicate
// something is malforming the request sent by client. // something is malforming the request sent by client.
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err, logger.Application)
return nil, errAuthentication return nil, errAuthentication
} }
claims[iampolicy.SessionPolicyName] = string(spBytes) claims[iampolicy.SessionPolicyName] = string(spBytes)
@ -312,7 +312,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
// To extract region from XML in request body, get copy of request body. // To extract region from XML in request body, get copy of request body.
payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize)) payload, err := ioutil.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return accessKey, owner, ErrMalformedXML return accessKey, owner, ErrMalformedXML
} }

View File

@ -354,7 +354,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Read incoming body XML bytes. // Read incoming body XML bytes.
if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil { if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, toAdminAPIErr(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, toAdminAPIErr(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@ -362,7 +362,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Unmarshal list of keys to be deleted. // Unmarshal list of keys to be deleted.
deleteObjects := &DeleteObjectsRequest{} deleteObjects := &DeleteObjectsRequest{}
if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil { if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r))
return return
} }
@ -596,7 +596,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Read multipart data and save in memory and in the disk if needed // Read multipart data and save in memory and in the disk if needed
form, err := reader.ReadForm(maxFormMemory) form, err := reader.ReadForm(maxFormMemory)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@ -607,7 +607,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Extract all form fields // Extract all form fields
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form) fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }

View File

@ -254,7 +254,7 @@ func handleCommonEnvVars() {
func logStartupMessage(msg string, data ...interface{}) { func logStartupMessage(msg string, data ...interface{}) {
if globalConsoleSys != nil { if globalConsoleSys != nil {
globalConsoleSys.Send(msg) globalConsoleSys.Send(msg, string(logger.All))
} }
logger.StartupMessage(msg, data...) logger.StartupMessage(msg, data...)
} }

View File

@ -336,14 +336,14 @@ func (s *serverConfig) lookupConfigs() {
for _, l := range s.Logger.HTTP { for _, l := range s.Logger.HTTP {
if l.Enabled { if l.Enabled {
// Enable http logging // Enable http logging
logger.AddTarget(http.New(l.Endpoint, loggerUserAgent, NewCustomHTTPTransport())) logger.AddTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewCustomHTTPTransport()))
} }
} }
for _, l := range s.Logger.Audit { for _, l := range s.Logger.Audit {
if l.Enabled { if l.Enabled {
// Enable http audit logging // Enable http audit logging
logger.AddAuditTarget(http.New(l.Endpoint, loggerUserAgent, NewCustomHTTPTransport())) logger.AddAuditTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewCustomHTTPTransport()))
} }
} }

View File

@ -66,7 +66,7 @@ func (sys *HTTPConsoleLoggerSys) HasLogListeners() bool {
} }
// Subscribe starts console logging for this node. // Subscribe starts console logging for this node.
func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh chan struct{}, node string, last int, filter func(entry interface{}) bool) { func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh chan struct{}, node string, last int, logKind string, filter func(entry interface{}) bool) {
// Enable console logging for remote client even if local console logging is disabled in the config. // Enable console logging for remote client even if local console logging is disabled in the config.
if !globalServerConfig.Logger.Console.Enabled && !sys.pubsub.HasSubscribers() { if !globalServerConfig.Logger.Console.Enabled && !sys.pubsub.HasSubscribers() {
logger.AddTarget(globalConsoleSys.Console()) logger.AddTarget(globalConsoleSys.Console())
@ -83,7 +83,7 @@ func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh chan s
lastN = make([]madmin.LogInfo, last) lastN = make([]madmin.LogInfo, last)
sys.logBufLk.RLock() sys.logBufLk.RLock()
sys.logBuf.Do(func(p interface{}) { sys.logBuf.Do(func(p interface{}) {
if p != nil && (p.(madmin.LogInfo)).SendLog(node) { if p != nil && (p.(madmin.LogInfo)).SendLog(node, logKind) {
lastN[cnt%last] = p.(madmin.LogInfo) lastN[cnt%last] = p.(madmin.LogInfo)
cnt++ cnt++
} }
@ -119,7 +119,7 @@ func (sys *HTTPConsoleLoggerSys) Console() *HTTPConsoleLoggerSys {
// Send log message 'e' to console and publish to console // Send log message 'e' to console and publish to console
// log pubsub system // log pubsub system
func (sys *HTTPConsoleLoggerSys) Send(e interface{}) error { func (sys *HTTPConsoleLoggerSys) Send(e interface{}, logKind string) error {
var lg madmin.LogInfo var lg madmin.LogInfo
switch e := e.(type) { switch e := e.(type) {
case log.Entry: case log.Entry:
@ -136,7 +136,7 @@ func (sys *HTTPConsoleLoggerSys) Send(e interface{}) error {
sys.logBufLk.Unlock() sys.logBufLk.Unlock()
if globalServerConfig.Logger.Console.Enabled { if globalServerConfig.Logger.Console.Enabled {
return sys.console.Send(e) return sys.console.Send(e, string(logger.All))
} }
return nil return nil
} }

View File

@ -260,7 +260,7 @@ func (endpoints EndpointList) UpdateIsLocal() error {
reqInfo := (&logger.ReqInfo{}).AppendTags("host", endpoints[i].HostName) reqInfo := (&logger.ReqInfo{}).AppendTags("host", endpoints[i].HostName)
reqInfo.AppendTags("elapsedTime", humanize.RelTime(startTime, startTime.Add(timeElapsed), "elapsed", "")) reqInfo.AppendTags("elapsedTime", humanize.RelTime(startTime, startTime.Add(timeElapsed), "elapsed", ""))
ctx := logger.SetReqInfo(context.Background(), reqInfo) ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
} }
} else { } else {
resolvedList[i] = true resolvedList[i] = true

View File

@ -138,12 +138,12 @@ func initFormatCache(ctx context.Context, drives []string) (formats []*formatCac
} }
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
logger.GetReqInfo(ctx).AppendTags("drive", drive) logger.GetReqInfo(ctx).AppendTags("drive", drive)
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return nil, err return nil, err
} }
if err = os.Mkdir(drive, 0777); err != nil { if err = os.Mkdir(drive, 0777); err != nil {
logger.GetReqInfo(ctx).AppendTags("drive", drive) logger.GetReqInfo(ctx).AppendTags("drive", drive)
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return nil, err return nil, err
} }
} }

View File

@ -280,7 +280,7 @@ func (fs *FSObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID
// Validate input data size and it can never be less than -1. // Validate input data size and it can never be less than -1.
if data.Size() < -1 { if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument) logger.LogIf(ctx, errInvalidArgument, logger.Application)
return pi, toObjectErr(errInvalidArgument) return pi, toObjectErr(errInvalidArgument)
} }

View File

@ -548,7 +548,7 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string,
// Check if range is valid // Check if range is valid
if off > size || off+length > size { if off > size || off+length > size {
err = InvalidRange{off, length, size} err = InvalidRange{off, length, size}
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
closeFn() closeFn()
rwPoolUnlocker() rwPoolUnlocker()
nsUnlocker() nsUnlocker()
@ -587,13 +587,13 @@ func (fs *FSObjects) getObject(ctx context.Context, bucket, object string, offse
// Offset cannot be negative. // Offset cannot be negative.
if offset < 0 { if offset < 0 {
logger.LogIf(ctx, errUnexpected) logger.LogIf(ctx, errUnexpected, logger.Application)
return toObjectErr(errUnexpected, bucket, object) return toObjectErr(errUnexpected, bucket, object)
} }
// Writer cannot be nil. // Writer cannot be nil.
if writer == nil { if writer == nil {
logger.LogIf(ctx, errUnexpected) logger.LogIf(ctx, errUnexpected, logger.Application)
return toObjectErr(errUnexpected, bucket, object) return toObjectErr(errUnexpected, bucket, object)
} }
@ -622,7 +622,7 @@ func (fs *FSObjects) getObject(ctx context.Context, bucket, object string, offse
return toObjectErr(perr, bucket, object) return toObjectErr(perr, bucket, object)
} }
if objEtag != etag { if objEtag != etag {
logger.LogIf(ctx, InvalidETag{}) logger.LogIf(ctx, InvalidETag{}, logger.Application)
return toObjectErr(InvalidETag{}, bucket, object) return toObjectErr(InvalidETag{}, bucket, object)
} }
} }
@ -648,7 +648,7 @@ func (fs *FSObjects) getObject(ctx context.Context, bucket, object string, offse
// Reply back invalid range if the input offset and length fall out of range. // Reply back invalid range if the input offset and length fall out of range.
if offset > size || offset+length > size { if offset > size || offset+length > size {
err = InvalidRange{offset, length, size} err = InvalidRange{offset, length, size}
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return err return err
} }
@ -864,7 +864,7 @@ func (fs *FSObjects) putObject(ctx context.Context, bucket string, object string
// Validate input data size and it can never be less than zero. // Validate input data size and it can never be less than zero.
if data.Size() < -1 { if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument) logger.LogIf(ctx, errInvalidArgument, logger.Application)
return ObjectInfo{}, errInvalidArgument return ObjectInfo{}, errInvalidArgument
} }

View File

@ -27,6 +27,7 @@ import (
"io/ioutil" "io/ioutil"
"math" "math"
"net/http" "net/http"
"os"
"path" "path"
"strconv" "strconv"
@ -158,14 +159,14 @@ EXAMPLES:
// Handler for 'minio gateway gcs' command line. // Handler for 'minio gateway gcs' command line.
func gcsGatewayMain(ctx *cli.Context) { func gcsGatewayMain(ctx *cli.Context) {
projectID := ctx.Args().First() projectID := ctx.Args().First()
if projectID == "" && env.Get("GOOGLE_APPLICATION_CREDENTIALS", "") == "" { if projectID == "" && os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
logger.LogIf(context.Background(), errGCSProjectIDNotFound) logger.LogIf(context.Background(), errGCSProjectIDNotFound, logger.Application)
cli.ShowCommandHelpAndExit(ctx, "gcs", 1) cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
} }
if projectID != "" && !isValidGCSProjectIDFormat(projectID) { if projectID != "" && !isValidGCSProjectIDFormat(projectID) {
reqInfo := (&logger.ReqInfo{}).AppendTags("projectID", ctx.Args().First()) reqInfo := (&logger.ReqInfo{}).AppendTags("projectID", ctx.Args().First())
contxt := logger.SetReqInfo(context.Background(), reqInfo) contxt := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(contxt, errGCSInvalidProjectID) logger.LogIf(contxt, errGCSInvalidProjectID, logger.Application)
cli.ShowCommandHelpAndExit(ctx, "gcs", 1) cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
} }
@ -762,7 +763,7 @@ func (l *gcsGateway) GetObject(ctx context.Context, bucket string, key string, s
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
// otherwise gcs will just return object not exist in case of non-existing bucket // otherwise gcs will just return object not exist in case of non-existing bucket
if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil { if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return gcsToObjectError(err, bucket) return gcsToObjectError(err, bucket)
} }
@ -775,7 +776,7 @@ func (l *gcsGateway) GetObject(ctx context.Context, bucket string, key string, s
r, err := object.NewRangeReader(ctx, startOffset, length) r, err := object.NewRangeReader(ctx, startOffset, length)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return gcsToObjectError(err, bucket, key) return gcsToObjectError(err, bucket, key)
} }
defer r.Close() defer r.Close()
@ -873,7 +874,7 @@ func (l *gcsGateway) GetObjectInfo(ctx context.Context, bucket string, object st
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
// otherwise gcs will just return object not exist in case of non-existing bucket // otherwise gcs will just return object not exist in case of non-existing bucket
if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil { if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return minio.ObjectInfo{}, gcsToObjectError(err, bucket) return minio.ObjectInfo{}, gcsToObjectError(err, bucket)
} }
@ -893,7 +894,7 @@ func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, r
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
// otherwise gcs will just return object not exist in case of non-existing bucket // otherwise gcs will just return object not exist in case of non-existing bucket
if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil { if _, err := l.client.Bucket(bucket).Attrs(ctx); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return minio.ObjectInfo{}, gcsToObjectError(err, bucket) return minio.ObjectInfo{}, gcsToObjectError(err, bucket)
} }

View File

@ -519,13 +519,13 @@ func (l *ossObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continua
// length indicates the total length of the object. // length indicates the total length of the object.
func ossGetObject(ctx context.Context, client *oss.Client, bucket, key string, startOffset, length int64, writer io.Writer, etag string) error { func ossGetObject(ctx context.Context, client *oss.Client, bucket, key string, startOffset, length int64, writer io.Writer, etag string) error {
if length < 0 && length != -1 { if length < 0 && length != -1 {
logger.LogIf(ctx, fmt.Errorf("Invalid argument")) logger.LogIf(ctx, fmt.Errorf("Invalid argument"), logger.Application)
return ossToObjectError(fmt.Errorf("Invalid argument"), bucket, key) return ossToObjectError(fmt.Errorf("Invalid argument"), bucket, key)
} }
bkt, err := client.Bucket(bucket) bkt, err := client.Bucket(bucket)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
return ossToObjectError(err, bucket, key) return ossToObjectError(err, bucket, key)
} }

View File

@ -155,6 +155,6 @@ func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[
entry.API.StatusCode = statusCode entry.API.StatusCode = statusCode
entry.API.TimeToFirstByte = timeToFirstByte.String() entry.API.TimeToFirstByte = timeToFirstByte.String()
entry.API.TimeToResponse = timeToResponse.String() entry.API.TimeToResponse = timeToResponse.String()
_ = t.Send(entry) _ = t.Send(entry, string(All))
} }
} }

View File

@ -273,36 +273,53 @@ func hashString(input string) string {
return hex.EncodeToString(checksum) return hex.EncodeToString(checksum)
} }
// Kind specifies the kind of error log
type Kind string
const (
// Minio errors
Minio Kind = "MINIO"
// Application errors
Application Kind = "APPLICATION"
// All errors
All Kind = "ALL"
)
// LogAlwaysIf prints a detailed error message during // LogAlwaysIf prints a detailed error message during
// the execution of the server. // the execution of the server.
func LogAlwaysIf(ctx context.Context, err error) { func LogAlwaysIf(ctx context.Context, err error, errKind ...interface{}) {
if err == nil { if err == nil {
return return
} }
logIf(ctx, err) logIf(ctx, err, errKind...)
} }
// LogIf prints a detailed error message during // LogIf prints a detailed error message during
// the execution of the server, if it is not an // the execution of the server, if it is not an
// ignored error. // ignored error.
func LogIf(ctx context.Context, err error) { func LogIf(ctx context.Context, err error, errKind ...interface{}) {
if err == nil { if err == nil {
return return
} }
if err.Error() != diskNotFoundError { if err.Error() != diskNotFoundError {
logIf(ctx, err) logIf(ctx, err, errKind...)
} }
} }
// logIf prints a detailed error message during // logIf prints a detailed error message during
// the execution of the server. // the execution of the server.
func logIf(ctx context.Context, err error) { func logIf(ctx context.Context, err error, errKind ...interface{}) {
if Disable { if Disable {
return return
} }
logKind := string(Minio)
if len(errKind) > 0 {
if ek, ok := errKind[0].(Kind); ok {
logKind = string(ek)
}
}
req := GetReqInfo(ctx) req := GetReqInfo(ctx)
if req == nil { if req == nil {
@ -330,6 +347,7 @@ func logIf(ctx context.Context, err error) {
entry := log.Entry{ entry := log.Entry{
DeploymentID: req.DeploymentID, DeploymentID: req.DeploymentID,
Level: ErrorLvl.String(), Level: ErrorLvl.String(),
LogKind: logKind,
RemoteHost: req.RemoteHost, RemoteHost: req.RemoteHost,
Host: req.Host, Host: req.Host,
RequestID: req.RequestID, RequestID: req.RequestID,
@ -359,7 +377,7 @@ func logIf(ctx context.Context, err error) {
// Iterate over all logger targets to send the log entry // Iterate over all logger targets to send the log entry
for _, t := range Targets { for _, t := range Targets {
t.Send(entry) t.Send(entry, entry.LogKind)
} }
} }
@ -368,9 +386,9 @@ var ErrCritical struct{}
// CriticalIf logs the provided error on the console. It fails the // CriticalIf logs the provided error on the console. It fails the
// current go-routine by causing a `panic(ErrCritical)`. // current go-routine by causing a `panic(ErrCritical)`.
func CriticalIf(ctx context.Context, err error) { func CriticalIf(ctx context.Context, err error, errKind ...interface{}) {
if err != nil { if err != nil {
LogIf(ctx, err) LogIf(ctx, err, errKind...)
panic(ErrCritical) panic(ErrCritical)
} }
} }

View File

@ -30,7 +30,7 @@ type logOnceType struct {
} }
// One log message per error. // One log message per error.
func (l *logOnceType) logOnceIf(ctx context.Context, err error, id interface{}) { func (l *logOnceType) logOnceIf(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
if err == nil { if err == nil {
return return
} }
@ -49,7 +49,7 @@ func (l *logOnceType) logOnceIf(ctx context.Context, err error, id interface{})
l.Unlock() l.Unlock()
if shouldLog { if shouldLog {
LogIf(ctx, err) LogIf(ctx, err, errKind...)
} }
} }
@ -76,6 +76,6 @@ var logOnce = newLogOnceType()
// LogOnceIf - Logs notification errors - once per error. // LogOnceIf - Logs notification errors - once per error.
// id is a unique identifier for related log messages, refer to cmd/notification.go // id is a unique identifier for related log messages, refer to cmd/notification.go
// on how it is used. // on how it is used.
func LogOnceIf(ctx context.Context, err error, id interface{}) { func LogOnceIf(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
logOnce.logOnceIf(ctx, err, id) logOnce.logOnceIf(ctx, err, id, errKind...)
} }

View File

@ -40,6 +40,7 @@ type API struct {
type Entry struct { type Entry struct {
DeploymentID string `json:"deploymentid,omitempty"` DeploymentID string `json:"deploymentid,omitempty"`
Level string `json:"level"` Level string `json:"level"`
LogKind string `json:"errKind"`
Time string `json:"time"` Time string `json:"time"`
API *API `json:"api,omitempty"` API *API `json:"api,omitempty"`
RemoteHost string `json:"remotehost,omitempty"` RemoteHost string `json:"remotehost,omitempty"`

View File

@ -32,7 +32,7 @@ import (
type Target struct{} type Target struct{}
// Send log message 'e' to console // Send log message 'e' to console
func (c *Target) Send(e interface{}) error { func (c *Target) Send(e interface{}, logKind string) error {
entry, ok := e.(log.Entry) entry, ok := e.(log.Entry)
if !ok { if !ok {
return fmt.Errorf("Uexpected log entry structure %#v", e) return fmt.Errorf("Uexpected log entry structure %#v", e)

View File

@ -22,6 +22,7 @@ import (
"errors" "errors"
"net/http" "net/http"
gohttp "net/http" gohttp "net/http"
"strings"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
) )
@ -39,6 +40,7 @@ type Target struct {
endpoint string endpoint string
// User-Agent to be set on each log request sent to the `endpoint` // User-Agent to be set on each log request sent to the `endpoint`
userAgent string userAgent string
logKind string
client gohttp.Client client gohttp.Client
} }
@ -75,10 +77,11 @@ func (h *Target) startHTTPLogger() {
// New initializes a new logger target which // New initializes a new logger target which
// sends log over http to the specified endpoint // sends log over http to the specified endpoint
func New(endpoint, userAgent string, transport *gohttp.Transport) *Target { func New(endpoint, userAgent, logKind string, transport *gohttp.Transport) *Target {
h := Target{ h := Target{
endpoint: endpoint, endpoint: endpoint,
userAgent: userAgent, userAgent: userAgent,
logKind: strings.ToUpper(logKind),
client: gohttp.Client{ client: gohttp.Client{
Transport: transport, Transport: transport,
}, },
@ -90,7 +93,10 @@ func New(endpoint, userAgent string, transport *gohttp.Transport) *Target {
} }
// Send log message 'e' to http target. // Send log message 'e' to http target.
func (h *Target) Send(entry interface{}) error { func (h *Target) Send(entry interface{}, errKind string) error {
if h.logKind != errKind && h.logKind != "ALL" {
return nil
}
select { select {
case h.logCh <- entry: case h.logCh <- entry:
default: default:

View File

@ -20,7 +20,7 @@ package logger
// a single log entry and Send it to the log target // a single log entry and Send it to the log target
// e.g. Send the log to a http server // e.g. Send the log to a http server
type Target interface { type Target interface {
Send(entry interface{}) error Send(entry interface{}, errKind string) error
} }
// Targets is the set of enabled loggers // Targets is the set of enabled loggers

View File

@ -329,7 +329,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
return return
} }
logger.LogIf(ctx, err) logger.LogIf(ctx, err, logger.Application)
} }
} }

View File

@ -948,7 +948,7 @@ func (s *peerRESTServer) ConsoleLogHandler(w http.ResponseWriter, r *http.Reques
defer close(doneCh) defer close(doneCh)
ch := make(chan interface{}, 2000) ch := make(chan interface{}, 2000)
globalConsoleSys.Subscribe(ch, doneCh, "", 0, nil) globalConsoleSys.Subscribe(ch, doneCh, "", 0, string(logger.All), nil)
enc := gob.NewEncoder(w) enc := gob.NewEncoder(w)
for { for {

View File

@ -37,7 +37,14 @@ func writeSTSErrorResponse(ctx context.Context, w http.ResponseWriter, errCode S
if errCtxt != nil { if errCtxt != nil {
stsErrorResponse.Error.Message = fmt.Sprintf("%v", errCtxt) stsErrorResponse.Error.Message = fmt.Sprintf("%v", errCtxt)
} }
logger.LogIf(ctx, errCtxt) logKind := logger.All
switch errCode {
case ErrSTSInternalError, ErrSTSNotInitialized:
logKind = logger.Minio
default:
logKind = logger.Application
}
logger.LogIf(ctx, errCtxt, logKind)
encodedErrorResponse := encodeResponse(stsErrorResponse) encodedErrorResponse := encodeResponse(stsErrorResponse)
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
} }

View File

@ -297,7 +297,7 @@ func (xl xlObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID
// Validate input data size and it can never be less than zero. // Validate input data size and it can never be less than zero.
if data.Size() < -1 { if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument) logger.LogIf(ctx, errInvalidArgument, logger.Application)
return pi, toObjectErr(errInvalidArgument) return pi, toObjectErr(errInvalidArgument)
} }

View File

@ -206,7 +206,7 @@ func (xl xlObjects) getObject(ctx context.Context, bucket, object string, startO
// Start offset cannot be negative. // Start offset cannot be negative.
if startOffset < 0 { if startOffset < 0 {
logger.LogIf(ctx, errUnexpected) logger.LogIf(ctx, errUnexpected, logger.Application)
return errUnexpected return errUnexpected
} }
@ -258,7 +258,7 @@ func (xl xlObjects) getObject(ctx context.Context, bucket, object string, startO
// Reply back invalid range if the input offset and length fall out of range. // Reply back invalid range if the input offset and length fall out of range.
if startOffset > xlMeta.Stat.Size || startOffset+length > xlMeta.Stat.Size { if startOffset > xlMeta.Stat.Size || startOffset+length > xlMeta.Stat.Size {
logger.LogIf(ctx, InvalidRange{startOffset, length, xlMeta.Stat.Size}) logger.LogIf(ctx, InvalidRange{startOffset, length, xlMeta.Stat.Size}, logger.Application)
return InvalidRange{startOffset, length, xlMeta.Stat.Size} return InvalidRange{startOffset, length, xlMeta.Stat.Size}
} }
@ -570,7 +570,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
// Validate input data size and it can never be less than zero. // Validate input data size and it can never be less than zero.
if data.Size() < -1 { if data.Size() < -1 {
logger.LogIf(ctx, errInvalidArgument) logger.LogIf(ctx, errInvalidArgument, logger.Application)
return ObjectInfo{}, toObjectErr(errInvalidArgument) return ObjectInfo{}, toObjectErr(errInvalidArgument)
} }
@ -636,7 +636,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
// Should return IncompleteBody{} error when reader has fewer bytes // Should return IncompleteBody{} error when reader has fewer bytes
// than specified in request header. // than specified in request header.
if n < data.Size() { if n < data.Size() {
logger.LogIf(ctx, IncompleteBody{}) logger.LogIf(ctx, IncompleteBody{}, logger.Application)
return ObjectInfo{}, IncompleteBody{} return ObjectInfo{}, IncompleteBody{}
} }

View File

@ -186,6 +186,7 @@
"http": { "http": {
"target1": { "target1": {
"enabled": false, "enabled": false,
"type": "minio",
"endpoint": "https://username:password@example.com/api" "endpoint": "https://username:password@example.com/api"
} }
} }

View File

@ -76,7 +76,7 @@ type AMQPTarget struct {
conn *amqp.Connection conn *amqp.Connection
connMutex sync.Mutex connMutex sync.Mutex
store Store store Store
loggerOnce func(ctx context.Context, err error, id interface{}) loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})
} }
// ID - returns TargetID. // ID - returns TargetID.
@ -215,7 +215,7 @@ func (target *AMQPTarget) Close() error {
} }
// NewAMQPTarget - creates new AMQP target. // NewAMQPTarget - creates new AMQP target.
func NewAMQPTarget(id string, args AMQPArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{})) (*AMQPTarget, error) { func NewAMQPTarget(id string, args AMQPArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})) (*AMQPTarget, error) {
var conn *amqp.Connection var conn *amqp.Connection
var err error var err error

View File

@ -99,7 +99,7 @@ type RedisTarget struct {
pool *redis.Pool pool *redis.Pool
store Store store Store
firstPing bool firstPing bool
loggerOnce func(ctx context.Context, err error, id interface{}) loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})
} }
// ID - returns target ID. // ID - returns target ID.
@ -222,7 +222,7 @@ func (target *RedisTarget) Close() error {
} }
// NewRedisTarget - creates new Redis target. // NewRedisTarget - creates new Redis target.
func NewRedisTarget(id string, args RedisArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{})) (*RedisTarget, error) { func NewRedisTarget(id string, args RedisArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})) (*RedisTarget, error) {
pool := &redis.Pool{ pool := &redis.Pool{
MaxIdle: 3, MaxIdle: 3,
IdleTimeout: 2 * 60 * time.Second, IdleTimeout: 2 * 60 * time.Second,

View File

@ -35,12 +35,14 @@ type LogInfo struct {
} }
// SendLog returns true if log pertains to node specified in args. // SendLog returns true if log pertains to node specified in args.
func (l LogInfo) SendLog(node string) bool { func (l LogInfo) SendLog(node, logKind string) bool {
return node == "" || strings.EqualFold(node, l.NodeName) nodeFltr := (node == "" || strings.EqualFold(node, l.NodeName))
typeFltr := strings.EqualFold(logKind, "all") || strings.EqualFold(l.LogKind, logKind)
return nodeFltr && typeFltr
} }
// GetLogs - listen on console log messages. // GetLogs - listen on console log messages.
func (adm AdminClient) GetLogs(node string, lineCnt int, doneCh <-chan struct{}) <-chan LogInfo { func (adm AdminClient) GetLogs(node string, lineCnt int, logKind string, doneCh <-chan struct{}) <-chan LogInfo {
logCh := make(chan LogInfo, 1) logCh := make(chan LogInfo, 1)
// Only success, start a routine to start reading line by line. // Only success, start a routine to start reading line by line.
@ -49,6 +51,7 @@ func (adm AdminClient) GetLogs(node string, lineCnt int, doneCh <-chan struct{})
urlValues := make(url.Values) urlValues := make(url.Values)
urlValues.Set("node", node) urlValues.Set("node", node)
urlValues.Set("limit", strconv.Itoa(lineCnt)) urlValues.Set("limit", strconv.Itoa(lineCnt))
urlValues.Set("logType", logKind)
for { for {
reqData := requestData{ reqData := requestData{
relPath: "/v1/log", relPath: "/v1/log",