137 lines
3.3 KiB
Go
137 lines
3.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
"sync"
|
|
myhttp "amuz.es/src/infra/goutils/http"
|
|
"amuz.es/src/infra/goutils/buf"
|
|
"amuz.es/src/infra/goutils/misc"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
dateFormat = "02/Jan/2006:15:04:05 -0700"
|
|
forwardedForIPHeader = "X-Forwarded-For"
|
|
)
|
|
|
|
type bufferedWriter interface {
|
|
WriteTo(w io.Writer) (n int64, err error)
|
|
Write(p []byte) (n int, err error)
|
|
}
|
|
|
|
type accessLoggerMiddleware struct {
|
|
serverNameValue []string
|
|
writer io.Writer
|
|
waiter *sync.WaitGroup
|
|
dumpQ bufferedWriter
|
|
writerFlushChan chan bool
|
|
}
|
|
|
|
func (m *accessLoggerMiddleware) Handle(next http.Handler) http.Handler {
|
|
accessLoggerBufPool := buf.ByteBufferPool{}
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
m.waiter.Add(1)
|
|
respWrapper := myhttp.NewWrapResponseWriter(w, r.ProtoMajor)
|
|
defer func(now time.Time) {
|
|
dur := time.Since(now)
|
|
buf := accessLoggerBufPool.Acquire()
|
|
defer accessLoggerBufPool.Release(buf)
|
|
buf.WriteString(m.remoteHost(r))
|
|
buf.WriteString(` - - [`)
|
|
buf.WriteString(now.Format(dateFormat))
|
|
buf.WriteString(`] "`)
|
|
buf.WriteString(r.Method)
|
|
buf.WriteByte(' ')
|
|
buf.WriteString(r.RequestURI)
|
|
buf.WriteByte(' ')
|
|
buf.WriteString(r.Proto)
|
|
buf.WriteString(`" `)
|
|
buf.WriteString(misc.FormatInt(respWrapper.Status()))
|
|
buf.WriteByte(' ')
|
|
buf.WriteString(misc.FormatInt(respWrapper.BytesWritten()))
|
|
buf.WriteString(` "`)
|
|
buf.WriteString(r.Referer())
|
|
buf.WriteString(`" "`)
|
|
buf.WriteString(r.UserAgent())
|
|
buf.WriteString(`" `)
|
|
buf.WriteString(misc.FormatInt64(dur.Nanoseconds() / time.Millisecond.Nanoseconds()))
|
|
buf.WriteByte(' ')
|
|
buf.WriteString(r.Host)
|
|
buf.WriteByte('\n')
|
|
m.dumpQ.Write(buf.B)
|
|
m.waiter.Done()
|
|
}(time.Now().Local())
|
|
|
|
respWrapper.Header()["Server"] = m.serverNameValue
|
|
|
|
next.ServeHTTP(respWrapper, r)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
func (m *accessLoggerMiddleware) Close() {
|
|
m.waiter.Wait()
|
|
m.writerFlushChan <- true
|
|
<-m.writerFlushChan
|
|
}
|
|
|
|
func (m *accessLoggerMiddleware) lineByLineWriter() {
|
|
ticker := time.NewTicker(250 * time.Millisecond)
|
|
defer func() {
|
|
ticker.Stop()
|
|
m.dumpQ.WriteTo(m.writer)
|
|
close(m.writerFlushChan)
|
|
}()
|
|
for {
|
|
<-ticker.C
|
|
m.dumpQ.WriteTo(m.writer)
|
|
select {
|
|
case <-m.writerFlushChan:
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
// strip port from addresses with hostname, ipv4 or ipv6
|
|
func (m *accessLoggerMiddleware) stripPort(address string) string {
|
|
if h, _, err := net.SplitHostPort(address); err == nil {
|
|
return h
|
|
}
|
|
|
|
return address
|
|
}
|
|
|
|
// The remote address of the client. When the 'X-Forwarded-For'
|
|
// header is set, then it is used instead.
|
|
func (m *accessLoggerMiddleware) remoteAddr(r *http.Request) string {
|
|
ff := r.Header.Get(forwardedForIPHeader)
|
|
if ff != "" {
|
|
return ff
|
|
}
|
|
|
|
return r.RemoteAddr
|
|
}
|
|
|
|
func (m *accessLoggerMiddleware) remoteHost(r *http.Request) string {
|
|
a := m.remoteAddr(r)
|
|
h := m.stripPort(a)
|
|
if h != "" {
|
|
return h
|
|
}
|
|
|
|
return "-"
|
|
}
|
|
|
|
func AccessLog(serverName string, bufferingWriter bufferedWriter, accessLogWriter io.Writer) (func(next http.Handler) http.Handler, func()) {
|
|
impl := &accessLoggerMiddleware{}
|
|
impl.serverNameValue = append(impl.serverNameValue, serverName)
|
|
impl.writer = accessLogWriter
|
|
impl.waiter = &sync.WaitGroup{}
|
|
impl.writerFlushChan = make(chan bool, 1)
|
|
impl.dumpQ = bufferingWriter
|
|
go impl.lineByLineWriter()
|
|
return impl.Handle, impl.Close
|
|
}
|