Refactor logging in more Go idiomatic style (#6816)
This refactor brings a change which allows targets to be added in a cleaner way and also audit is now moved out. This PR also simplifies logger dependency for auditingmaster
parent
d732b1ff9d
commit
bfb505aa8e
@ -1,97 +0,0 @@ |
||||
/* |
||||
* 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 ( |
||||
"context" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// Represents the current version of audit log structure.
|
||||
const auditLogVersion = "1" |
||||
|
||||
// AuditEntry - audit entry logs.
|
||||
type AuditEntry struct { |
||||
Version string `json:"version"` |
||||
DeploymentID string `json:"deploymentid,omitempty"` |
||||
Time string `json:"time"` |
||||
API *api `json:"api,omitempty"` |
||||
RemoteHost string `json:"remotehost,omitempty"` |
||||
RequestID string `json:"requestID,omitempty"` |
||||
UserAgent string `json:"userAgent,omitempty"` |
||||
ReqQuery map[string]string `json:"requestQuery,omitempty"` |
||||
ReqHeader map[string]string `json:"requestHeader,omitempty"` |
||||
RespHeader map[string]string `json:"responseHeader,omitempty"` |
||||
} |
||||
|
||||
// AuditTargets is the list of enabled audit loggers
|
||||
var AuditTargets = []LoggingTarget{} |
||||
|
||||
// AddAuditTarget adds a new audit logger target to the
|
||||
// list of enabled loggers
|
||||
func AddAuditTarget(t LoggingTarget) { |
||||
AuditTargets = append(AuditTargets, t) |
||||
} |
||||
|
||||
// AuditLog - logs audit logs to all targets.
|
||||
func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
if Disable { |
||||
return |
||||
} |
||||
|
||||
req := GetReqInfo(ctx) |
||||
if req == nil { |
||||
req = &ReqInfo{API: "SYSTEM"} |
||||
} |
||||
|
||||
reqQuery := make(map[string]string) |
||||
for k, v := range r.URL.Query() { |
||||
reqQuery[k] = strings.Join(v, ",") |
||||
} |
||||
reqHeader := make(map[string]string) |
||||
for k, v := range r.Header { |
||||
reqHeader[k] = strings.Join(v, ",") |
||||
} |
||||
respHeader := make(map[string]string) |
||||
for k, v := range w.Header() { |
||||
respHeader[k] = strings.Join(v, ",") |
||||
} |
||||
|
||||
// Send audit logs only to http targets.
|
||||
for _, t := range AuditTargets { |
||||
t.send(AuditEntry{ |
||||
Version: auditLogVersion, |
||||
DeploymentID: deploymentID, |
||||
RemoteHost: req.RemoteHost, |
||||
RequestID: req.RequestID, |
||||
UserAgent: req.UserAgent, |
||||
Time: time.Now().UTC().Format(time.RFC3339Nano), |
||||
API: &api{ |
||||
Name: req.API, |
||||
Args: &args{ |
||||
Bucket: req.BucketName, |
||||
Object: req.ObjectName, |
||||
}, |
||||
}, |
||||
ReqQuery: reqQuery, |
||||
ReqHeader: reqHeader, |
||||
RespHeader: respHeader, |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
/* |
||||
* 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 ( |
||||
"net/http" |
||||
|
||||
"github.com/minio/minio/cmd/logger/message/audit" |
||||
) |
||||
|
||||
// ResponseWriter - is a wrapper to trap the http response status code.
|
||||
type ResponseWriter struct { |
||||
http.ResponseWriter |
||||
statusCode int |
||||
} |
||||
|
||||
// NewResponseWriter - returns a wrapped response writer to trap
|
||||
// http status codes for auditiing purposes.
|
||||
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { |
||||
return &ResponseWriter{w, http.StatusOK} |
||||
} |
||||
|
||||
// WriteHeader - writes http status code
|
||||
func (lrw *ResponseWriter) WriteHeader(code int) { |
||||
lrw.statusCode = code |
||||
lrw.ResponseWriter.WriteHeader(code) |
||||
} |
||||
|
||||
// Flush - Calls the underlying Flush.
|
||||
func (lrw *ResponseWriter) Flush() { |
||||
lrw.ResponseWriter.(http.Flusher).Flush() |
||||
} |
||||
|
||||
// AuditTargets is the list of enabled audit loggers
|
||||
var AuditTargets = []Target{} |
||||
|
||||
// AddAuditTarget adds a new audit logger target to the
|
||||
// list of enabled loggers
|
||||
func AddAuditTarget(t Target) { |
||||
AuditTargets = append(AuditTargets, t) |
||||
} |
||||
|
||||
// AuditLog - logs audit logs to all audit targets.
|
||||
func AuditLog(w http.ResponseWriter, r *http.Request, api string) { |
||||
var statusCode int |
||||
lrw, ok := w.(*ResponseWriter) |
||||
if ok { |
||||
statusCode = lrw.statusCode |
||||
} |
||||
// Send audit logs only to http targets.
|
||||
for _, t := range AuditTargets { |
||||
t.Send(audit.ToEntry(w, r, api, statusCode)) |
||||
} |
||||
} |
@ -0,0 +1,90 @@ |
||||
/* |
||||
* 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 audit |
||||
|
||||
import ( |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/gorilla/mux" |
||||
"github.com/minio/minio/pkg/handlers" |
||||
) |
||||
|
||||
// Version - represents the current version of audit log structure.
|
||||
const Version = "1" |
||||
|
||||
// Entry - audit entry logs.
|
||||
type Entry struct { |
||||
Version string `json:"version"` |
||||
DeploymentID string `json:"deploymentid,omitempty"` |
||||
Time string `json:"time"` |
||||
API struct { |
||||
Name string `json:"name,omitempty"` |
||||
Bucket string `json:"bucket,omitempty"` |
||||
Object string `json:"object,omitempty"` |
||||
Status string `json:"status,omitempty"` |
||||
StatusCode int `json:"statusCode,omitempty"` |
||||
} `json:"api"` |
||||
RemoteHost string `json:"remotehost,omitempty"` |
||||
RequestID string `json:"requestID,omitempty"` |
||||
UserAgent string `json:"userAgent,omitempty"` |
||||
ReqQuery map[string]string `json:"requestQuery,omitempty"` |
||||
ReqHeader map[string]string `json:"requestHeader,omitempty"` |
||||
RespHeader map[string]string `json:"responseHeader,omitempty"` |
||||
} |
||||
|
||||
// ToEntry - constructs an audit entry object.
|
||||
func ToEntry(w http.ResponseWriter, r *http.Request, api string, statusCode int) Entry { |
||||
vars := mux.Vars(r) |
||||
bucket := vars["bucket"] |
||||
object := vars["object"] |
||||
|
||||
reqQuery := make(map[string]string) |
||||
for k, v := range r.URL.Query() { |
||||
reqQuery[k] = strings.Join(v, ",") |
||||
} |
||||
reqHeader := make(map[string]string) |
||||
for k, v := range r.Header { |
||||
reqHeader[k] = strings.Join(v, ",") |
||||
} |
||||
respHeader := make(map[string]string) |
||||
for k, v := range w.Header() { |
||||
respHeader[k] = strings.Join(v, ",") |
||||
} |
||||
respHeader["Etag"] = strings.Trim(respHeader["Etag"], `"`) |
||||
|
||||
entry := Entry{ |
||||
Version: Version, |
||||
DeploymentID: w.Header().Get("x-minio-deployment-id"), |
||||
RemoteHost: handlers.GetSourceIP(r), |
||||
RequestID: w.Header().Get("x-amz-request-id"), |
||||
UserAgent: r.UserAgent(), |
||||
Time: time.Now().UTC().Format(time.RFC3339Nano), |
||||
ReqQuery: reqQuery, |
||||
ReqHeader: reqHeader, |
||||
RespHeader: respHeader, |
||||
} |
||||
|
||||
entry.API.Name = api |
||||
entry.API.Bucket = bucket |
||||
entry.API.Object = object |
||||
entry.API.Status = http.StatusText(statusCode) |
||||
entry.API.StatusCode = statusCode |
||||
|
||||
return entry |
||||
} |
@ -0,0 +1,50 @@ |
||||
/* |
||||
* 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 log |
||||
|
||||
// Args - defines the arguments for the API.
|
||||
type Args struct { |
||||
Bucket string `json:"bucket,omitempty"` |
||||
Object string `json:"object,omitempty"` |
||||
Metadata map[string]string `json:"metadata,omitempty"` |
||||
} |
||||
|
||||
// Trace - defines the trace.
|
||||
type Trace struct { |
||||
Message string `json:"message,omitempty"` |
||||
Source []string `json:"source,omitempty"` |
||||
Variables map[string]string `json:"variables,omitempty"` |
||||
} |
||||
|
||||
// API - defines the api type and its args.
|
||||
type API struct { |
||||
Name string `json:"name,omitempty"` |
||||
Args *Args `json:"args,omitempty"` |
||||
} |
||||
|
||||
// Entry - defines fields and values of each log entry.
|
||||
type Entry struct { |
||||
DeploymentID string `json:"deploymentid,omitempty"` |
||||
Level string `json:"level"` |
||||
Time string `json:"time"` |
||||
API *API `json:"api,omitempty"` |
||||
RemoteHost string `json:"remotehost,omitempty"` |
||||
RequestID string `json:"requestID,omitempty"` |
||||
UserAgent string `json:"userAgent,omitempty"` |
||||
Message string `json:"message,omitempty"` |
||||
Trace *Trace `json:"error,omitempty"` |
||||
} |
@ -0,0 +1,109 @@ |
||||
/* |
||||
* 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 console |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/minio/minio/cmd/logger" |
||||
"github.com/minio/minio/cmd/logger/message/log" |
||||
) |
||||
|
||||
// Target implements loggerTarget to send log
|
||||
// in plain or json format to the standard output.
|
||||
type Target struct{} |
||||
|
||||
// Send log message 'e' to console
|
||||
func (c *Target) Send(e interface{}) error { |
||||
entry, ok := e.(log.Entry) |
||||
if !ok { |
||||
return fmt.Errorf("Uexpected log entry structure %#v", e) |
||||
} |
||||
if logger.IsJSON() { |
||||
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(logger.TimeFormat) |
||||
|
||||
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 = logger.ColorFgRed(logger.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 |
||||
} |
||||
|
||||
// New initializes a new logger target
|
||||
// which prints log directly in the standard
|
||||
// output.
|
||||
func New() *Target { |
||||
return &Target{} |
||||
} |
Loading…
Reference in new issue