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"
)
2015-09-24 18:16:13 +03:00
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
}
2015-11-01 21:34:54 +03:00
// NewLogger returns a new Logger instance.
2015-09-12 16:10:03 +03:00
func NewLogger ( file string ) * Logger {
2015-09-15 23:32:09 +03:00
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 }
}
2015-09-24 18:16:13 +03:00
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 ) {
2015-09-15 23:32:09 +03:00
if l . file == nil {
2015-09-12 16:10:03 +03:00
next ( rw , r )
2015-09-15 23:32:09 +03:00
} 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 ( ) {
2016-04-13 21:36:23 +03:00
if l . file != nil {
l . file . Close ( )
}
2015-09-15 23:32:09 +03:00
}
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
}