amuz.es/src/infra/goutils@v0.1.3/http/middleware/accesslog.go (about) 1 package middleware 2 3 import ( 4 "net" 5 "net/http" 6 "time" 7 "sync" 8 myhttp "amuz.es/src/infra/goutils/http" 9 "amuz.es/src/infra/goutils/buf" 10 "amuz.es/src/infra/goutils/misc" 11 "io" 12 ) 13 14 const ( 15 dateFormat = "02/Jan/2006:15:04:05 -0700" 16 forwardedForIPHeader = "X-Forwarded-For" 17 ) 18 19 type bufferedWriter interface { 20 WriteTo(w io.Writer) (n int64, err error) 21 Write(p []byte) (n int, err error) 22 } 23 24 type accessLoggerMiddleware struct { 25 serverNameValue []string 26 writer io.Writer 27 waiter *sync.WaitGroup 28 dumpQ bufferedWriter 29 writerFlushChan chan bool 30 } 31 32 func (m *accessLoggerMiddleware) Handle(next http.Handler) http.Handler { 33 accessLoggerBufPool := buf.ByteBufferPool{} 34 fn := func(w http.ResponseWriter, r *http.Request) { 35 m.waiter.Add(1) 36 respWrapper := myhttp.NewWrapResponseWriter(w, r.ProtoMajor) 37 defer func(now time.Time) { 38 dur := time.Since(now) 39 buf := accessLoggerBufPool.Acquire() 40 defer accessLoggerBufPool.Release(buf) 41 buf.WriteString(m.remoteHost(r)) 42 buf.WriteString(` - - [`) 43 buf.WriteString(now.Format(dateFormat)) 44 buf.WriteString(`] "`) 45 buf.WriteString(r.Method) 46 buf.WriteByte(' ') 47 buf.WriteString(r.RequestURI) 48 buf.WriteByte(' ') 49 buf.WriteString(r.Proto) 50 buf.WriteString(`" `) 51 buf.WriteString(misc.FormatInt(respWrapper.Status())) 52 buf.WriteByte(' ') 53 buf.WriteString(misc.FormatInt(respWrapper.BytesWritten())) 54 buf.WriteString(` "`) 55 buf.WriteString(r.Referer()) 56 buf.WriteString(`" "`) 57 buf.WriteString(r.UserAgent()) 58 buf.WriteString(`" `) 59 buf.WriteString(misc.FormatInt64(dur.Nanoseconds() / time.Millisecond.Nanoseconds())) 60 buf.WriteByte(' ') 61 buf.WriteString(r.Host) 62 buf.WriteByte('\n') 63 m.dumpQ.Write(buf.B) 64 m.waiter.Done() 65 }(time.Now().Local()) 66 67 respWrapper.Header()["Server"] = m.serverNameValue 68 69 next.ServeHTTP(respWrapper, r) 70 } 71 return http.HandlerFunc(fn) 72 } 73 func (m *accessLoggerMiddleware) Close() { 74 m.waiter.Wait() 75 m.writerFlushChan <- true 76 <-m.writerFlushChan 77 } 78 79 func (m *accessLoggerMiddleware) lineByLineWriter() { 80 ticker := time.NewTicker(250 * time.Millisecond) 81 defer func() { 82 ticker.Stop() 83 m.dumpQ.WriteTo(m.writer) 84 close(m.writerFlushChan) 85 }() 86 for { 87 <-ticker.C 88 m.dumpQ.WriteTo(m.writer) 89 select { 90 case <-m.writerFlushChan: 91 return 92 default: 93 } 94 } 95 } 96 97 // strip port from addresses with hostname, ipv4 or ipv6 98 func (m *accessLoggerMiddleware) stripPort(address string) string { 99 if h, _, err := net.SplitHostPort(address); err == nil { 100 return h 101 } 102 103 return address 104 } 105 106 // The remote address of the client. When the 'X-Forwarded-For' 107 // header is set, then it is used instead. 108 func (m *accessLoggerMiddleware) remoteAddr(r *http.Request) string { 109 ff := r.Header.Get(forwardedForIPHeader) 110 if ff != "" { 111 return ff 112 } 113 114 return r.RemoteAddr 115 } 116 117 func (m *accessLoggerMiddleware) remoteHost(r *http.Request) string { 118 a := m.remoteAddr(r) 119 h := m.stripPort(a) 120 if h != "" { 121 return h 122 } 123 124 return "-" 125 } 126 127 func AccessLog(serverName string, bufferingWriter bufferedWriter, accessLogWriter io.Writer) (func(next http.Handler) http.Handler, func()) { 128 impl := &accessLoggerMiddleware{} 129 impl.serverNameValue = append(impl.serverNameValue, serverName) 130 impl.writer = accessLogWriter 131 impl.waiter = &sync.WaitGroup{} 132 impl.writerFlushChan = make(chan bool, 1) 133 impl.dumpQ = bufferingWriter 134 go impl.lineByLineWriter() 135 return impl.Handle, impl.Close 136 }