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  }