From b9ea18b8b8ecaec9e7fac72331c1a9bce7ac2d06 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 19 Oct 2015 12:15:19 -0700 Subject: [PATCH] Implement accessLog handler --- accesslog-handler.go | 120 +++++++++++++++++++++++++++++++++++++++++++ flags.go | 6 +++ main.go | 2 + routers.go | 17 +++--- server-main.go | 1 + 5 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 accesslog-handler.go diff --git a/accesslog-handler.go b/accesslog-handler.go new file mode 100644 index 000000000..13e703721 --- /dev/null +++ b/accesslog-handler.go @@ -0,0 +1,120 @@ +/* + * Minimalist Object Storage, (C) 2015 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 main + +import ( + "encoding/json" + "net/http" + "net/url" + "os" + "time" + + "github.com/minio/minio-xl/pkg/probe" +) + +type accessLogHandler struct { + http.Handler + accessLogFile *os.File +} + +// LogMessage is a serializable json log message +type LogMessage struct { + StartTime time.Time + Duration time.Duration + StatusMessage string // human readable http status message + ContentLength string // human readable content length + + // HTTP detailed message + HTTP struct { + ResponseHeaders http.Header + Request struct { + Method string + URL *url.URL + Proto string // "HTTP/1.0" + ProtoMajor int // 1 + ProtoMinor int // 0 + Header http.Header + Host string + Form url.Values + PostForm url.Values + Trailer http.Header + RemoteAddr string + RequestURI string + } + } +} + +func (h *accessLogHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + message, perr := getLogMessage(w, req) + fatalIf(perr.Trace(), "Unable to extract http message.", nil) + _, err := h.accessLogFile.Write(message) + fatalIf(probe.NewError(err), "Writing to log file failed.", nil) + + h.Handler.ServeHTTP(w, req) +} + +func getLogMessage(w http.ResponseWriter, req *http.Request) ([]byte, *probe.Error) { + logMessage := &LogMessage{ + StartTime: time.Now().UTC(), + } + // store lower level details + logMessage.HTTP.ResponseHeaders = w.Header() + logMessage.HTTP.Request = struct { + Method string + URL *url.URL + Proto string // "HTTP/1.0" + ProtoMajor int // 1 + ProtoMinor int // 0 + Header http.Header + Host string + Form url.Values + PostForm url.Values + Trailer http.Header + RemoteAddr string + RequestURI string + }{ + Method: req.Method, + URL: req.URL, + Proto: req.Proto, + ProtoMajor: req.ProtoMajor, + ProtoMinor: req.ProtoMinor, + Header: req.Header, + Host: req.Host, + Form: req.Form, + PostForm: req.PostForm, + Trailer: req.Header, + RemoteAddr: req.RemoteAddr, + RequestURI: req.RequestURI, + } + + // logMessage.HTTP.Request = req + logMessage.Duration = time.Now().UTC().Sub(logMessage.StartTime) + js, err := json.Marshal(logMessage) + if err != nil { + return nil, probe.NewError(err) + } + js = append(js, byte('\n')) // append a new line + return js, nil +} + +// AccessLogHandler logs requests +func AccessLogHandler(h http.Handler) http.Handler { + file, err := os.OpenFile("access.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + fatalIf(probe.NewError(err), "Unable to open access log.", nil) + + return &accessLogHandler{Handler: h, accessLogFile: file} +} diff --git a/flags.go b/flags.go index f810dc422..2709b9d5f 100644 --- a/flags.go +++ b/flags.go @@ -28,6 +28,12 @@ var ( Usage: "ADDRESS:PORT for cloud storage access.", } + accessLogFlag = cli.BoolFlag{ + Name: "enable-accesslog", + Hide: true, + Usage: "Enable access logs for all incoming HTTP request.", + } + ratelimitFlag = cli.IntFlag{ Name: "ratelimit", Hide: true, diff --git a/main.go b/main.go index a4dc84ffa..797edc0af 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ import ( type serverConfig struct { /// HTTP server options Address string // Address:Port listening + AccessLog bool // Enable access log handler Anonymous bool // No signature turn off /// FS options @@ -105,6 +106,7 @@ func registerApp() *cli.App { // register all flags registerFlag(addressFlag) + registerFlag(accessLogFlag) registerFlag(ratelimitFlag) registerFlag(anonymousFlag) registerFlag(certFlag) diff --git a/routers.go b/routers.go index 5caec2f2b..96ccd0be0 100644 --- a/routers.go +++ b/routers.go @@ -23,6 +23,13 @@ import ( "github.com/minio/minio/pkg/fs" ) +// CloudStorageAPI container for API and also carries OP (operation) channel +type CloudStorageAPI struct { + Filesystem fs.Filesystem + Anonymous bool // do not checking for incoming signatures, allow all requests + AccessLog bool // if true log all incoming request +} + // registerCloudStorageAPI - register all the handlers to their respective paths func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI) { mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") @@ -46,12 +53,6 @@ func registerCloudStorageAPI(mux *router.Router, a CloudStorageAPI) { mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE") } -// CloudStorageAPI container for API and also carries OP (operation) channel -type CloudStorageAPI struct { - Filesystem fs.Filesystem - Anonymous bool // do not checking for incoming signatures, allow all requests -} - // getNewCloudStorageAPI instantiate a new CloudStorageAPI func getNewCloudStorageAPI(conf serverConfig) CloudStorageAPI { fs, err := fs.New() @@ -65,6 +66,7 @@ func getNewCloudStorageAPI(conf serverConfig) CloudStorageAPI { return CloudStorageAPI{ Filesystem: fs, Anonymous: conf.Anonymous, + AccessLog: conf.AccessLog, } } @@ -77,6 +79,9 @@ func getCloudStorageAPIHandler(api CloudStorageAPI) http.Handler { if !api.Anonymous { mwHandlers = append(mwHandlers, SignatureHandler) } + if api.AccessLog { + mwHandlers = append(mwHandlers, AccessLogHandler) + } mux := router.NewRouter() registerCloudStorageAPI(mux, api) return registerCustomMiddleware(mux, mwHandlers...) diff --git a/server-main.go b/server-main.go index 50aeee7c4..0960130f2 100644 --- a/server-main.go +++ b/server-main.go @@ -289,6 +289,7 @@ func serverMain(c *cli.Context) { tls := (certFile != "" && keyFile != "") apiServerConfig := serverConfig{ Address: c.GlobalString("address"), + AccessLog: c.GlobalBool("enable-accesslog"), Anonymous: c.GlobalBool("anonymous"), Path: path, MinFreeDisk: minFreeDisk,