78 lines
1.9 KiB
Go
78 lines
1.9 KiB
Go
package server
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AccessLog wraps h so every response writes one line to w:
|
|
//
|
|
// <timestamp> <method> <path> <status> <latency>
|
|
//
|
|
// For the attach websocket the line appears when the connection ends, with
|
|
// status 101 and the connection's lifetime as latency.
|
|
func AccessLog(h http.Handler, w io.Writer) http.Handler {
|
|
var mu sync.Mutex
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
sw := &statusWriter{ResponseWriter: rw}
|
|
start := time.Now()
|
|
h.ServeHTTP(sw, r)
|
|
status := sw.status
|
|
if status == 0 {
|
|
status = http.StatusOK
|
|
}
|
|
line := fmt.Sprintf("%s %s %s %d %s\n",
|
|
start.Format("2006-01-02T15:04:05.000Z07:00"),
|
|
r.Method, r.URL.RequestURI(), status,
|
|
time.Since(start).Round(time.Microsecond))
|
|
mu.Lock()
|
|
_, _ = io.WriteString(w, line)
|
|
mu.Unlock()
|
|
})
|
|
}
|
|
|
|
// statusWriter records the status code. It passes hijacking through so the
|
|
// attach endpoint's websocket upgrade keeps working.
|
|
type statusWriter struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (s *statusWriter) WriteHeader(code int) {
|
|
if s.status == 0 {
|
|
s.status = code
|
|
}
|
|
s.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (s *statusWriter) Write(b []byte) (int, error) {
|
|
if s.status == 0 {
|
|
s.status = http.StatusOK
|
|
}
|
|
return s.ResponseWriter.Write(b)
|
|
}
|
|
|
|
// Unwrap lets http.ResponseController reach the underlying writer.
|
|
func (s *statusWriter) Unwrap() http.ResponseWriter { return s.ResponseWriter }
|
|
|
|
func (s *statusWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
h, ok := s.ResponseWriter.(http.Hijacker)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("underlying ResponseWriter does not support hijacking")
|
|
}
|
|
if s.status == 0 {
|
|
s.status = http.StatusSwitchingProtocols
|
|
}
|
|
return h.Hijack()
|
|
}
|
|
|
|
func (s *statusWriter) Flush() {
|
|
if f, ok := s.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|