feat: Access Logging on the user server

Closes: #98

Co-authored-by: Roberto Abdelkader Martínez Pérez <robertomartinezp@gmail.com>
This commit is contained in:
pancho horrillo
2020-12-16 17:51:22 +01:00
parent 41be4f9344
commit 4d05223224
7 changed files with 93 additions and 4 deletions
+33
View File
@@ -1,12 +1,45 @@
package logger package logger
import ( import (
"fmt"
"log" "log"
"os" "os"
"time"
) )
var A *log.Logger
var L *log.Logger var L *log.Logger
func init() { func init() {
A = log.New(os.Stdout, "", 0)
L = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds|log.LUTC) L = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds|log.LUTC)
} }
// See https://en.wikipedia.org/wiki/Common_Log_Format
// Tested with love against CLFParser https://pypi.org/project/clfparser/
func LogAccess(clientAddr, handlerId, user, method, resource, version string, status int, bytesSent int64, referer, userAgent string) {
clientAddr = dashIfEmpty(clientAddr)
handlerId = dashIfEmpty(handlerId)
user = dashIfEmpty(user)
referer = dashIfEmpty(referer)
userAgent = dashIfEmpty(userAgent)
firstLine := fmt.Sprintf("%s %s %s", method, resource, version)
ts := time.Now().UTC().Format("02/Jan/2006:15:04:05 -0700") // Amazing date format layout! Not.
A.Printf("%s %s %s [%s] %q %d %d %q %q\n",
clientAddr,
handlerId,
user,
ts,
firstLine,
status,
bytesSent,
referer,
userAgent)
}
func dashIfEmpty(value string) string {
if value == "" {
return "-"
}
return value
}
+5 -2
View File
@@ -192,7 +192,8 @@ func setResponseStatus(w http.ResponseWriter, r *http.Request, h *model.Handler)
} else if http.StatusText(si) == "" { } else if http.StatusText(si) == "" {
httperror.ErrorJSON(w, InvalidStatusCode, http.StatusBadRequest) httperror.ErrorJSON(w, InvalidStatusCode, http.StatusBadRequest)
} else { } else {
h.Writer.WriteHeader(int(si)) h.Status = si
h.Writer.WriteHeader(si)
} }
} }
@@ -221,10 +222,12 @@ func setResponseCookies(w http.ResponseWriter, r *http.Request, h *model.Handler
} }
func setResponseBody(w http.ResponseWriter, r *http.Request, h *model.Handler) { func setResponseBody(w http.ResponseWriter, r *http.Request, h *model.Handler) {
if n, err := io.Copy(h.Writer, r.Body); err != nil { n, err := io.Copy(h.Writer, r.Body)
if err != nil {
if n > 0 { if n > 0 {
panic(http.ErrAbortHandler) panic(http.ErrAbortHandler)
} }
httperror.ErrorJSON(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) httperror.ErrorJSON(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
} }
h.SentBytes += n
} }
+6
View File
@@ -41,4 +41,10 @@ type Handler struct {
// Writer is the original http.ResponseWriter of the request. // Writer is the original http.ResponseWriter of the request.
Writer http.ResponseWriter Writer http.ResponseWriter
// Status is the returned status code
Status int
// SentBytes is the number of sent bytes
SentBytes int64
} }
+31 -1
View File
@@ -21,6 +21,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/BBVA/kapow/internal/logger"
"github.com/BBVA/kapow/internal/server/model" "github.com/BBVA/kapow/internal/server/model"
) )
@@ -30,6 +31,35 @@ func gorillize(rs []model.Route, buildHandler func(model.Route) http.Handler) *m
for _, r := range rs { for _, r := range rs {
m.Handle(r.Pattern, buildHandler(r)).Methods(r.Method) m.Handle(r.Pattern, buildHandler(r)).Methods(r.Method)
} }
m.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
logger.LogAccess(
r.RemoteAddr,
"-",
"-",
r.Method,
r.RequestURI,
r.Proto,
http.StatusNotFound,
0,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
)
})
m.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
logger.LogAccess(
r.RemoteAddr,
"-",
"-",
r.Method,
r.RequestURI,
r.Proto,
http.StatusMethodNotAllowed,
0,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
)
})
return m return m
} }
@@ -42,6 +42,8 @@ func handleRouteIDToBody(route model.Route) http.Handler {
func TestGorillizeReturnsAnEmptyMuxWhenAnEmptyRouteList(t *testing.T) { func TestGorillizeReturnsAnEmptyMuxWhenAnEmptyRouteList(t *testing.T) {
m := gorillize([]model.Route{}, handlerStatusOK) m := gorillize([]model.Route{}, handlerStatusOK)
m.NotFoundHandler = nil
m.MethodNotAllowedHandler = nil
if !reflect.DeepEqual(*m, *mux.NewRouter()) { if !reflect.DeepEqual(*m, *mux.NewRouter()) {
t.Error("Returned mux not empty") t.Error("Returned mux not empty")
@@ -45,6 +45,7 @@ func handlerBuilder(route model.Route) http.Handler {
Route: route, Route: route,
Request: r, Request: r,
Writer: w, Writer: w,
Status: 200,
} }
data.Handlers.Add(h) data.Handlers.Add(h)
@@ -78,6 +79,20 @@ func handlerBuilder(route model.Route) http.Handler {
logger.L.Println(err) logger.L.Println(err)
} }
if r != nil {
logger.LogAccess(
r.RemoteAddr,
h.ID,
"-",
r.Method,
r.RequestURI,
r.Proto,
h.Status,
h.SentBytes,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
)
}
}) })
} }
+1 -1
View File
@@ -32,7 +32,7 @@ type SwappableMux struct {
func New() *SwappableMux { func New() *SwappableMux {
return &SwappableMux{ return &SwappableMux{
root: mux.NewRouter(), root: gorillize([]model.Route{}, handlerBuilder),
} }
} }