diff --git a/pkg/apiserver/rest/rest_server.go b/pkg/apiserver/rest/rest_server.go index 7f44de940..52ad0562b 100644 --- a/pkg/apiserver/rest/rest_server.go +++ b/pkg/apiserver/rest/rest_server.go @@ -37,6 +37,7 @@ import ( "github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb" "github.com/oam-dev/kubevela/pkg/apiserver/log" "github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils" "github.com/oam-dev/kubevela/pkg/apiserver/rest/webservice" ) @@ -190,6 +191,9 @@ func (s *restServer) RegisterServices() restfulspec.Config { // Add container filter to respond to OPTIONS s.webContainer.Filter(s.webContainer.OPTIONSFilter) + // Add request log + s.webContainer.Filter(s.requestLog) + // Regist all custom webservice for _, handler := range webservice.GetRegisteredWebService() { s.webContainer.Add(handler.GetWebService()) @@ -203,6 +207,22 @@ func (s *restServer) RegisterServices() restfulspec.Config { return config } +func (s *restServer) requestLog(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + start := time.Now() + c := utils.NewResponseCapture(resp.ResponseWriter) + resp.ResponseWriter = c + chain.ProcessFilter(req, resp) + takeTime := time.Since(start) + log.Logger.With( + "clientIP", utils.ClientIP(req.Request), + "path", req.Request.URL.Path, + "method", req.Request.Method, + "status", c.StatusCode(), + "time", takeTime.String(), + "responseSize", len(c.Bytes()), + ).Infof("request log") +} + func enrichSwaggerObject(swo *spec.Swagger) { swo.Info = &spec.Info{ InfoProps: spec.InfoProps{ diff --git a/pkg/apiserver/rest/utils/http.go b/pkg/apiserver/rest/utils/http.go new file mode 100644 index 000000000..131821218 --- /dev/null +++ b/pkg/apiserver/rest/utils/http.go @@ -0,0 +1,92 @@ +/* +Copyright 2021 The KubeVela Authors. + +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 utils + +import ( + "bytes" + "net" + "net/http" + "strings" +) + +// ClientIP get client ip +func ClientIP(r *http.Request) string { + xForwardedFor := r.Header.Get("X-Forwarded-For") + ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0]) + if ip != "" { + return ip + } + + ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")) + if ip != "" { + return ip + } + + if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { + return ip + } + + return "" +} + +// ResponseCapture capture response and get response info +type ResponseCapture struct { + http.ResponseWriter + wroteHeader bool + status int + body *bytes.Buffer +} + +// NewResponseCapture new response capture +func NewResponseCapture(w http.ResponseWriter) *ResponseCapture { + return &ResponseCapture{ + ResponseWriter: w, + wroteHeader: false, + body: new(bytes.Buffer), + } +} + +// Header return response writer header +func (c ResponseCapture) Header() http.Header { + return c.ResponseWriter.Header() +} + +// Write write data to response writer and body +func (c ResponseCapture) Write(data []byte) (int, error) { + if !c.wroteHeader { + c.WriteHeader(http.StatusOK) + } + c.body.Write(data) + return c.ResponseWriter.Write(data) +} + +// WriteHeader write header to response writer +func (c *ResponseCapture) WriteHeader(statusCode int) { + c.status = statusCode + c.wroteHeader = true + c.ResponseWriter.WriteHeader(statusCode) +} + +// Bytes return response body bytes +func (c ResponseCapture) Bytes() []byte { + return c.body.Bytes() +} + +// StatusCode return status code +func (c ResponseCapture) StatusCode() int { + return c.status +}