1
0
mirror of https://github.com/containous/traefik.git synced 2024-12-27 03:21:40 +03:00
traefik/middlewares/logger.go

198 lines
4.8 KiB
Go
Raw Normal View History

2015-09-12 16:10:03 +03:00
package middlewares
2016-04-20 02:45:59 +03:00
/*
Middleware Logger writes each request and its response to the access log.
It gets some information from the logInfoResponseWriter set up by previous middleware.
*/
2015-09-12 16:10:03 +03:00
import (
2016-04-20 02:45:59 +03:00
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/streamrail/concurrent-map"
"io"
"net"
2015-09-12 16:10:03 +03:00
"net/http"
"os"
2016-04-20 02:45:59 +03:00
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
2016-04-20 02:45:59 +03:00
const (
loggerReqidHeader = "X-Traefik-Reqid"
2015-09-12 16:10:03 +03:00
)
2016-04-20 02:45:59 +03:00
// Logger holds the File defining the access log
2015-09-12 16:10:03 +03:00
type Logger struct {
file *os.File
}
2016-04-20 02:45:59 +03:00
// Logging handler to log frontend name, backend name, and elapsed time
type frontendBackendLoggingHandler struct {
reqid string
writer io.Writer
handlerFunc http.HandlerFunc
}
var (
reqidCounter uint64 // Request ID
infoRwMap = cmap.New() // Map of reqid to response writer
backend2FrontendMap map[string]string
)
// logInfoResponseWriter is a wrapper of type http.ResponseWriter
// that tracks frontend and backend names and request status and size
type logInfoResponseWriter struct {
rw http.ResponseWriter
backend string
frontend string
status int
size int
}
// NewLogger returns a new Logger instance.
2015-09-12 16:10:03 +03:00
func NewLogger(file string) *Logger {
if len(file) > 0 {
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
2015-09-12 16:10:03 +03:00
if err != nil {
log.Fatal("Error opening file", err)
}
return &Logger{fi}
}
return &Logger{nil}
2015-09-12 16:10:03 +03:00
}
2016-04-20 02:45:59 +03:00
// SetBackend2FrontendMap is called by server.go to set up frontend translation
func SetBackend2FrontendMap(newMap *map[string]string) {
backend2FrontendMap = *newMap
}
2015-09-12 16:10:03 +03:00
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if l.file == nil {
2015-09-12 16:10:03 +03:00
next(rw, r)
} else {
2016-04-20 02:45:59 +03:00
reqid := strconv.FormatUint(atomic.AddUint64(&reqidCounter, 1), 10)
log.Debugf("Starting request %s: %s %s %s %s %s", reqid, r.Method, r.URL.RequestURI(), r.Proto, r.Referer(), r.UserAgent())
r.Header[loggerReqidHeader] = []string{reqid}
defer deleteReqid(r, reqid)
frontendBackendLoggingHandler{reqid, l.file, next}.ServeHTTP(rw, r)
2015-09-12 16:10:03 +03:00
}
2015-09-12 16:20:56 +03:00
}
2016-04-20 02:45:59 +03:00
// Delete a reqid from the map and the request's headers
func deleteReqid(r *http.Request, reqid string) {
log.Debugf("Ending request %s", reqid)
infoRwMap.Remove(reqid)
delete(r.Header, loggerReqidHeader)
}
// Save the backend name for the Logger
func saveBackendNameForLogger(r *http.Request, backendName string) {
if reqidHdr := r.Header[loggerReqidHeader]; len(reqidHdr) == 1 {
reqid := reqidHdr[0]
if infoRw, ok := infoRwMap.get(reqid); ok {
infoRw.SetBackend(backendName)
infoRw.SetFrontend(backend2FrontendMap[backendName])
}
}
}
// Close closes the Logger (i.e. the file).
2015-09-12 16:20:56 +03:00
func (l *Logger) Close() {
if l.file != nil {
l.file.Close()
}
}
2016-04-20 02:45:59 +03:00
// Logging handler to log frontend name, backend name, and elapsed time
func (fblh frontendBackendLoggingHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
startTime := time.Now()
infoRw := &logInfoResponseWriter{rw: rw}
infoRwMap.Set(fblh.reqid, infoRw)
fblh.handlerFunc(infoRw, req)
username := "-"
url := *req.URL
if url.User != nil {
if name := url.User.Username(); name != "" {
username = name
}
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
host = req.RemoteAddr
}
ts := startTime.Format("02/Jan/2006:15:04:05 -0700")
method := req.Method
uri := url.RequestURI()
if qmIndex := strings.Index(uri, "?"); qmIndex > 0 {
uri = uri[0:qmIndex]
}
proto := req.Proto
referer := req.Referer()
agent := req.UserAgent()
frontend := strings.TrimPrefix(infoRw.GetFrontend(), "frontend-")
backend := infoRw.GetBackend()
status := infoRw.GetStatus()
size := infoRw.GetSize()
elapsed := time.Now().UTC().Sub(startTime.UTC())
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %s%s`,
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsed, "\n")
}
func (lirw *logInfoResponseWriter) Header() http.Header {
return lirw.rw.Header()
}
func (lirw *logInfoResponseWriter) Write(b []byte) (int, error) {
if lirw.status == 0 {
lirw.status = http.StatusOK
}
size, err := lirw.rw.Write(b)
lirw.size += size
return size, err
}
func (lirw *logInfoResponseWriter) WriteHeader(s int) {
lirw.rw.WriteHeader(s)
lirw.status = s
}
func (lirw *logInfoResponseWriter) Flush() {
f, ok := lirw.rw.(http.Flusher)
if ok {
f.Flush()
}
}
func (lirw *logInfoResponseWriter) GetStatus() int {
return lirw.status
}
func (lirw *logInfoResponseWriter) GetSize() int {
return lirw.size
}
func (lirw *logInfoResponseWriter) GetBackend() string {
return lirw.backend
}
func (lirw *logInfoResponseWriter) GetFrontend() string {
return lirw.frontend
}
func (lirw *logInfoResponseWriter) SetBackend(backend string) {
lirw.backend = backend
}
func (lirw *logInfoResponseWriter) SetFrontend(frontend string) {
lirw.frontend = frontend
}