|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* MinIO Cloud Storage, (C) 2018 MinIO, Inc. |
|
|
|
* MinIO Cloud Storage, (C) 2018, 2019 MinIO, Inc. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
@ -17,6 +17,9 @@ |
|
|
|
package logger |
|
|
|
package logger |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
|
|
|
|
"bytes" |
|
|
|
|
|
|
|
"fmt" |
|
|
|
|
|
|
|
"io" |
|
|
|
"net/http" |
|
|
|
"net/http" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
@ -27,9 +30,18 @@ import ( |
|
|
|
// ResponseWriter - is a wrapper to trap the http response status code.
|
|
|
|
// ResponseWriter - is a wrapper to trap the http response status code.
|
|
|
|
type ResponseWriter struct { |
|
|
|
type ResponseWriter struct { |
|
|
|
http.ResponseWriter |
|
|
|
http.ResponseWriter |
|
|
|
statusCode int |
|
|
|
StatusCode int |
|
|
|
startTime time.Time |
|
|
|
// Response body should be logged
|
|
|
|
timeToFirstByte time.Duration |
|
|
|
LogBody bool |
|
|
|
|
|
|
|
TimeToFirstByte time.Duration |
|
|
|
|
|
|
|
StartTime time.Time |
|
|
|
|
|
|
|
// number of bytes written
|
|
|
|
|
|
|
|
bytesWritten int |
|
|
|
|
|
|
|
// Internal recording buffer
|
|
|
|
|
|
|
|
headers bytes.Buffer |
|
|
|
|
|
|
|
body bytes.Buffer |
|
|
|
|
|
|
|
// Indicate if headers are written in the log
|
|
|
|
|
|
|
|
headersLogged bool |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// NewResponseWriter - returns a wrapped response writer to trap
|
|
|
|
// NewResponseWriter - returns a wrapped response writer to trap
|
|
|
@ -37,25 +49,64 @@ type ResponseWriter struct { |
|
|
|
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { |
|
|
|
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { |
|
|
|
return &ResponseWriter{ |
|
|
|
return &ResponseWriter{ |
|
|
|
ResponseWriter: w, |
|
|
|
ResponseWriter: w, |
|
|
|
statusCode: http.StatusOK, |
|
|
|
StatusCode: http.StatusOK, |
|
|
|
startTime: time.Now().UTC(), |
|
|
|
StartTime: time.Now().UTC(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (lrw *ResponseWriter) Write(p []byte) (int, error) { |
|
|
|
func (lrw *ResponseWriter) Write(p []byte) (int, error) { |
|
|
|
n, err := lrw.ResponseWriter.Write(p) |
|
|
|
n, err := lrw.ResponseWriter.Write(p) |
|
|
|
|
|
|
|
lrw.bytesWritten += n |
|
|
|
|
|
|
|
if lrw.TimeToFirstByte == 0 { |
|
|
|
|
|
|
|
lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if !lrw.headersLogged { |
|
|
|
|
|
|
|
// We assume the response code to be '200 OK' when WriteHeader() is not called,
|
|
|
|
|
|
|
|
// that way following Golang HTTP response behavior.
|
|
|
|
|
|
|
|
lrw.writeHeaders(&lrw.headers, http.StatusOK, lrw.Header()) |
|
|
|
|
|
|
|
lrw.headersLogged = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if lrw.StatusCode >= http.StatusBadRequest || lrw.LogBody { |
|
|
|
|
|
|
|
// Always logging error responses.
|
|
|
|
|
|
|
|
lrw.body.Write(p) |
|
|
|
|
|
|
|
} |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return n, err |
|
|
|
return n, err |
|
|
|
} |
|
|
|
} |
|
|
|
if lrw.timeToFirstByte == 0 { |
|
|
|
|
|
|
|
lrw.timeToFirstByte = time.Now().UTC().Sub(lrw.startTime) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return n, err |
|
|
|
return n, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Write the headers into the given buffer
|
|
|
|
|
|
|
|
func (lrw *ResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) { |
|
|
|
|
|
|
|
n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode)) |
|
|
|
|
|
|
|
lrw.bytesWritten += n |
|
|
|
|
|
|
|
for k, v := range headers { |
|
|
|
|
|
|
|
n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0]) |
|
|
|
|
|
|
|
lrw.bytesWritten += n |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// BodyPlaceHolder returns a dummy body placeholder
|
|
|
|
|
|
|
|
var BodyPlaceHolder = []byte("<BODY>") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Body - Return response body.
|
|
|
|
|
|
|
|
func (lrw *ResponseWriter) Body() []byte { |
|
|
|
|
|
|
|
// If there was an error response or body logging is enabled
|
|
|
|
|
|
|
|
// then we return the body contents
|
|
|
|
|
|
|
|
if lrw.StatusCode >= http.StatusBadRequest || lrw.LogBody { |
|
|
|
|
|
|
|
return lrw.body.Bytes() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// ... otherwise we return the <BODY> place holder
|
|
|
|
|
|
|
|
return BodyPlaceHolder |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// WriteHeader - writes http status code
|
|
|
|
// WriteHeader - writes http status code
|
|
|
|
func (lrw *ResponseWriter) WriteHeader(code int) { |
|
|
|
func (lrw *ResponseWriter) WriteHeader(code int) { |
|
|
|
lrw.statusCode = code |
|
|
|
lrw.StatusCode = code |
|
|
|
|
|
|
|
if !lrw.headersLogged { |
|
|
|
|
|
|
|
lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header()) |
|
|
|
|
|
|
|
lrw.headersLogged = true |
|
|
|
|
|
|
|
} |
|
|
|
lrw.ResponseWriter.WriteHeader(code) |
|
|
|
lrw.ResponseWriter.WriteHeader(code) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -64,6 +115,11 @@ func (lrw *ResponseWriter) Flush() { |
|
|
|
lrw.ResponseWriter.(http.Flusher).Flush() |
|
|
|
lrw.ResponseWriter.(http.Flusher).Flush() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Size - reutrns the number of bytes written
|
|
|
|
|
|
|
|
func (lrw *ResponseWriter) Size() int { |
|
|
|
|
|
|
|
return lrw.bytesWritten |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// AuditTargets is the list of enabled audit loggers
|
|
|
|
// AuditTargets is the list of enabled audit loggers
|
|
|
|
var AuditTargets = []Target{} |
|
|
|
var AuditTargets = []Target{} |
|
|
|
|
|
|
|
|
|
|
@ -80,9 +136,9 @@ func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[ |
|
|
|
var timeToFirstByte time.Duration |
|
|
|
var timeToFirstByte time.Duration |
|
|
|
lrw, ok := w.(*ResponseWriter) |
|
|
|
lrw, ok := w.(*ResponseWriter) |
|
|
|
if ok { |
|
|
|
if ok { |
|
|
|
statusCode = lrw.statusCode |
|
|
|
statusCode = lrw.StatusCode |
|
|
|
timeToResponse = time.Now().UTC().Sub(lrw.startTime) |
|
|
|
timeToResponse = time.Now().UTC().Sub(lrw.StartTime) |
|
|
|
timeToFirstByte = lrw.timeToFirstByte |
|
|
|
timeToFirstByte = lrw.TimeToFirstByte |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vars := mux.Vars(r) |
|
|
|
vars := mux.Vars(r) |
|
|
|