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 71 rw := responsePool.Get().(*wrapRW) 72 73 *rw = wrapRW{ 74 w, 75 &d.Status, 76 &d.ResponseLength, 77 } 78 d.StartTime = time.Now() 79 80 l.Handler.ServeHTTP( 81 httpwrap.Wrap(w, httpwrap.OverrideWriter(rw), httpwrap.OverrideHeaderWriter(rw)), 82 r, 83 ) 84 85 d.EndTime = time.Now() 86 87 *rw = wrapRW{} 88 89 responsePool.Put(rw) 90 91 go l.Logger.Log(d) 92 } 93 94 // WriteLogger is a Logger which formats log data to a given template and 95 // writes it to a given io.Writer. 96 type WriteLogger struct { 97 mu sync.Mutex 98 w io.Writer 99 template *template.Template 100 } 101 102 // NewWriteLogger uses the given format as a template to write log data to the 103 // given io.Writer. 104 func NewWriteLogger(w io.Writer, format string) (Logger, error) { 105 if format == "" { 106 return nil, errors.New("invalid format") 107 } 108 109 if format[len(format)-1] != '\n' { 110 format += "\n" 111 } 112 113 t, err := template.New("").Parse(format) 114 if err != nil { 115 return nil, err 116 } 117 118 return &WriteLogger{ 119 w: w, 120 template: t, 121 }, nil 122 } 123 124 // Log satisfies the Logger interface. 125 func (w *WriteLogger) Log(d Details) { 126 w.mu.Lock() 127 w.template.Execute(w.w, d) 128 w.mu.Unlock() 129 } 130