infra
/
goutils
Archived
1
0
Fork 0
This repository has been archived on 2022-04-06. You can view files and clone it, but cannot push or open issues or pull requests.
goutils/http/middleware/accesslog.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
}