1 // Package httplog is used to create wrappers around http.Handler's to gather 2 // information about a request and its response. 3 package httplog // import "vimagination.zapto.org/httplog" 4 5 import ( 6 "errors" 7 "io" 8 "net/http" 9 "sync" 10 "text/template" 11 "time" 12 13 "vimagination.zapto.org/httpwrap" 14 ) 15 16 // DefaultFormat is a simple template to output log data in something reminiscent 17 // on the Apache default format. 18 const DefaultFormat = "{{.RemoteAddr}} - {{.URL.User.Username}} - [{{.StartTime.Format \"02/01/2006:15:04:05 +0700\"}}] \"{{.Method}} {{.URL.RequestURI}} {{.Proto}}\" {{.Status}} {{.RequestLength}} {{.StartTime.Sub .EndTime}}" 19 20 // Details is a collection of data about the request and response. 21 type Details struct { 22 *http.Request 23 Status, ResponseLength int 24 StartTime, EndTime time.Time 25 } 26 27 type wrapRW struct { 28 http.ResponseWriter 29 status, contentLength *int 30 } 31 32 func (w *wrapRW) WriteHeader(n int) { 33 *w.status = n 34 35 w.ResponseWriter.WriteHeader(n) 36 } 37 38 type logMux struct { 39 http.Handler 40 Logger 41 } 42 43 // Logger allows clients to specify how collected data is handled. 44 type Logger interface { 45 Log(d Details) 46 } 47 48 // Wrap wraps an existing http.Handler and collects data about the request 49 // and response and passes it to a logger. 50 func Wrap(m http.Handler, l Logger) http.Handler { 51 if m == nil { 52 m = http.DefaultServeMux 53 } 54 55 return &logMux{Handler: m, Logger: l} 56 } 57 58 var responsePool = sync.Pool{ 59 New: func() interface{} { 60 return new(wrapRW) 61 }, 62 } 63 64 // ServeHTTP satisfies the http.Handler interface. 65 func (l *logMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 66 d := Details{ 67 Request: r, 68 Status: 200, 69 } 70 rw := responsePool.Get().(*wrapRW) 71 *rw = wrapRW{ 72 w, 73 &d.Status, 74 &d.ResponseLength, 75 } 76 d.StartTime = time.Now() 77 78 l.Handler.ServeHTTP( 79 httpwrap.Wrap(w, httpwrap.OverrideWriter(rw), httpwrap.OverrideHeaderWriter(rw)), 80 r, 81 ) 82 83 d.EndTime = time.Now() 84 *rw = wrapRW{} 85 86 responsePool.Put(rw) 87 88 go l.Logger.Log(d) 89 } 90 91 // WriteLogger is a Logger which formats log data to a given template and 92 // writes it to a given io.Writer. 93 type WriteLogger struct { 94 mu sync.Mutex 95 w io.Writer 96 template *template.Template 97 } 98 99 // NewWriteLogger uses the given format as a template to write log data to the 100 // given io.Writer. 101 func NewWriteLogger(w io.Writer, format string) (Logger, error) { 102 if format == "" { 103 return nil, errors.New("invalid format") 104 } 105 106 if format[len(format)-1] != '\n' { 107 format += "\n" 108 } 109 110 t, err := template.New("").Parse(format) 111 if err != nil { 112 return nil, err 113 } 114 115 return &WriteLogger{ 116 w: w, 117 template: t, 118 }, nil 119 } 120 121 // Log satisfies the Logger interface. 122 func (w *WriteLogger) Log(d Details) { 123 w.mu.Lock() 124 w.template.Execute(w.w, d) 125 w.mu.Unlock() 126 } 127