11package middleware
22
33import (
4- "bytes"
54 "io"
65 "os"
76 "strconv"
87 "strings"
9- "sync"
108 "time"
119
10+ "encoding/json"
11+
1212 "github.com/labstack/echo"
13- "github.com/labstack/gommon/color"
14- isatty "github.com/mattn/go-isatty"
15- "github.com/valyala/fasttemplate"
13+ "github.com/labstack/echo/db"
1614)
1715
1816type (
2119 // Skipper defines a function to skip middleware.
2220 Skipper Skipper
2321
24- // Log format which can be constructed using the following tags :
22+ // Availabe logger fields :
2523 //
24+ // - time_unix
2625 // - time_rfc3339
2726 // - id (Request ID - Not implemented)
2827 // - remote_ip
@@ -33,39 +32,51 @@ type (
3332 // - referer
3433 // - user_agent
3534 // - status
36- // - latency (In microseconds )
35+ // - latency (In nanosecond )
3736 // - latency_human (Human readable)
3837 // - bytes_in (Bytes received)
3938 // - bytes_out (Bytes sent)
4039 // - header:<name>
4140 // - query:<name>
4241 // - form:<name>
43- //
44- // Example "${remote_ip} ${status}"
45- //
46- // Optional. Default value DefaultLoggerConfig.Format.
47- Format string `json:"format"`
4842
49- // Output is a writer where logs are written.
50- // Optional. Default value os.Stdout.
51- Output io.Writer
43+ // Optional. Default value DefaultLoggerConfig.Fields.
44+ Fields []string `json:"fields"`
45+
46+ // Output is where logs are written.
47+ // Optional. Default value &Stream{os.Stdout}.
48+ Output db.Logger
49+ }
5250
53- template * fasttemplate. Template
54- color * color. Color
55- pool sync. Pool
51+ // Stream implements `db.Logger`.
52+ Stream struct {
53+ io. Writer
5654 }
5755)
5856
57+ // LogRequest encodes `db.Request` into a stream.
58+ func (s * Stream ) Log (r * db.Request ) error {
59+ enc := json .NewEncoder (s )
60+ return enc .Encode (r )
61+ }
62+
5963var (
6064 // DefaultLoggerConfig is the default Logger middleware config.
6165 DefaultLoggerConfig = LoggerConfig {
6266 Skipper : defaultSkipper ,
63- Format : `{"time":"${time_rfc3339}","remote_ip":"${remote_ip}","host":"${host}",` +
64- `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
65- `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
66- `"bytes_out":${bytes_out}}` + "\n " ,
67- Output : os .Stdout ,
68- color : color .New (),
67+ Fields : []string {
68+ "time" ,
69+ "remote_ip" ,
70+ "host" ,
71+ "method" ,
72+ "uri" ,
73+ "status" ,
74+ "latency" ,
75+ "latency_human" ,
76+ "bytes_in" ,
77+ "bytes_out" ,
78+ },
79+ Output : & Stream {os .Stdout },
6980 }
7081)
7182
@@ -81,24 +92,13 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
8192 if config .Skipper == nil {
8293 config .Skipper = DefaultLoggerConfig .Skipper
8394 }
84- if config .Format == "" {
85- config .Format = DefaultLoggerConfig .Format
95+ if len ( config .Fields ) == 0 {
96+ config .Fields = DefaultLoggerConfig .Fields
8697 }
8798 if config .Output == nil {
8899 config .Output = DefaultLoggerConfig .Output
89100 }
90101
91- config .template = fasttemplate .New (config .Format , "${" , "}" )
92- config .color = color .New ()
93- if w , ok := config .Output .(* os.File ); ! ok || ! isatty .IsTerminal (w .Fd ()) {
94- config .color .Disable ()
95- }
96- config .pool = sync.Pool {
97- New : func () interface {} {
98- return bytes .NewBuffer (make ([]byte , 256 ))
99- },
100- }
101-
102102 return func (next echo.HandlerFunc ) echo.HandlerFunc {
103103 return func (c echo.Context ) (err error ) {
104104 if config .Skipper (c ) {
@@ -112,74 +112,66 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
112112 c .Error (err )
113113 }
114114 stop := time .Now ()
115- buf := config .pool .Get ().(* bytes.Buffer )
116- buf .Reset ()
117- defer config .pool .Put (buf )
118-
119- _ , err = config .template .ExecuteFunc (buf , func (w io.Writer , tag string ) (int , error ) {
120- switch tag {
121- case "time_rfc3339" :
122- return w .Write ([]byte (time .Now ().Format (time .RFC3339 )))
115+ request := & db.Request {
116+ Header : make (map [string ]string ),
117+ Query : make (map [string ]string ),
118+ Form : make (map [string ]string ),
119+ }
120+
121+ for _ , f := range config .Fields {
122+ switch f {
123+ case "time" :
124+ request .Time = time .Now ()
123125 case "remote_ip" :
124- ra := c .RealIP ()
125- return w .Write ([]byte (ra ))
126+ request .RemoteIP = c .RealIP ()
126127 case "host" :
127- return w . Write ([] byte ( req .Host ))
128+ request . Host = req .Host
128129 case "uri" :
129- return w . Write ([] byte ( req .RequestURI ))
130+ request . URI = req .RequestURI
130131 case "method" :
131- return w . Write ([] byte ( req .Method ))
132+ request . Method = req .Method
132133 case "path" :
133134 p := req .URL .Path
134135 if p == "" {
135136 p = "/"
136137 }
137- return w . Write ([] byte ( p ))
138+ request . Path = p
138139 case "referer" :
139- return w . Write ([] byte ( req .Referer ()) )
140+ request . Referer = req .Referer ()
140141 case "user_agent" :
141- return w . Write ([] byte ( req .UserAgent ()) )
142+ request . UserAgent = req .UserAgent ()
142143 case "status" :
143- n := res .Status
144- s := config .color .Green (n )
145- switch {
146- case n >= 500 :
147- s = config .color .Red (n )
148- case n >= 400 :
149- s = config .color .Yellow (n )
150- case n >= 300 :
151- s = config .color .Cyan (n )
152- }
153- return w .Write ([]byte (s ))
144+ request .Status = res .Status
154145 case "latency" :
155- l := stop .Sub (start ).Nanoseconds () / 1000
156- return w .Write ([]byte (strconv .FormatInt (l , 10 )))
146+ request .Latency = stop .Sub (start )
157147 case "latency_human" :
158- return w . Write ([] byte ( stop .Sub (start ).String ()) )
148+ request . LatencyHuman = stop .Sub (start ).String ()
159149 case "bytes_in" :
160- b := req .Header .Get (echo .HeaderContentLength )
161- if b == "" {
162- b = "0"
150+ cl := req .Header .Get (echo .HeaderContentLength )
151+ if cl == "" {
152+ cl = "0"
163153 }
164- return w .Write ([]byte (b ))
154+ l , _ := strconv .ParseInt (cl , 10 , 64 )
155+ request .BytesIn = l
165156 case "bytes_out" :
166- return w . Write ([] byte ( strconv . FormatInt ( res .Size , 10 )))
157+ request . BytesOut = res .Size
167158 default :
168159 switch {
169- case strings .HasPrefix (tag , "header:" ):
170- return buf .Write ([]byte (c .Request ().Header .Get (tag [7 :])))
171- case strings .HasPrefix (tag , "query:" ):
172- return buf .Write ([]byte (c .QueryParam (tag [6 :])))
173- case strings .HasPrefix (tag , "form:" ):
174- return buf .Write ([]byte (c .FormValue (tag [5 :])))
160+ case strings .HasPrefix (f , "header:" ):
161+ k := f [7 :]
162+ request .Header [k ] = c .Request ().Header .Get (k )
163+ case strings .HasPrefix (f , "query:" ):
164+ k := f [6 :]
165+ request .Query [k ] = c .QueryParam (k )
166+ case strings .HasPrefix (f , "form:" ):
167+ k := f [5 :]
168+ request .Form [k ] = c .FormValue (k )
175169 }
176170 }
177- return 0 , nil
178- })
179- if err == nil {
180- config .Output .Write (buf .Bytes ())
181171 }
182- return
172+
173+ // Write
174+ return config .Output .Log (request )
183175 }
184176 }
185177}
0 commit comments