@@ -197,6 +197,7 @@ type LoggerConfig struct {
197197 template * fasttemplate.Template
198198 colorer * color.Color
199199 pool * sync.Pool
200+ timeNow func () time.Time
200201}
201202
202203// DefaultLoggerConfig is the default Logger middleware config.
@@ -208,6 +209,7 @@ var DefaultLoggerConfig = LoggerConfig{
208209 `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n " ,
209210 CustomTimeFormat : "2006-01-02 15:04:05.00000" ,
210211 colorer : color .New (),
212+ timeNow : time .Now ,
211213}
212214
213215// Logger returns a middleware that logs HTTP requests using the default configuration.
@@ -267,9 +269,18 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
267269 if config .Format == "" {
268270 config .Format = DefaultLoggerConfig .Format
269271 }
272+ writeString := func (buf * bytes.Buffer , in string ) (int , error ) { return buf .WriteString (in ) }
273+ if config .Format [0 ] == '{' { // format looks like JSON, so we need to escape invalid characters
274+ writeString = writeJSONSafeString
275+ }
276+
270277 if config .Output == nil {
271278 config .Output = DefaultLoggerConfig .Output
272279 }
280+ timeNow := DefaultLoggerConfig .timeNow
281+ if config .timeNow != nil {
282+ timeNow = config .timeNow
283+ }
273284
274285 config .template = fasttemplate .New (config .Format , "${" , "}" )
275286 config .colorer = color .New ()
@@ -305,49 +316,47 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
305316 }
306317 return config .CustomTagFunc (c , buf )
307318 case "time_unix" :
308- return buf .WriteString (strconv .FormatInt (time . Now ().Unix (), 10 ))
319+ return buf .WriteString (strconv .FormatInt (timeNow ().Unix (), 10 ))
309320 case "time_unix_milli" :
310- // go 1.17 or later, it supports time#UnixMilli()
311- return buf .WriteString (strconv .FormatInt (time .Now ().UnixNano ()/ 1000000 , 10 ))
321+ return buf .WriteString (strconv .FormatInt (timeNow ().UnixMilli (), 10 ))
312322 case "time_unix_micro" :
313- // go 1.17 or later, it supports time#UnixMicro()
314- return buf .WriteString (strconv .FormatInt (time .Now ().UnixNano ()/ 1000 , 10 ))
323+ return buf .WriteString (strconv .FormatInt (timeNow ().UnixMicro (), 10 ))
315324 case "time_unix_nano" :
316- return buf .WriteString (strconv .FormatInt (time . Now ().UnixNano (), 10 ))
325+ return buf .WriteString (strconv .FormatInt (timeNow ().UnixNano (), 10 ))
317326 case "time_rfc3339" :
318- return buf .WriteString (time . Now ().Format (time .RFC3339 ))
327+ return buf .WriteString (timeNow ().Format (time .RFC3339 ))
319328 case "time_rfc3339_nano" :
320- return buf .WriteString (time . Now ().Format (time .RFC3339Nano ))
329+ return buf .WriteString (timeNow ().Format (time .RFC3339Nano ))
321330 case "time_custom" :
322- return buf .WriteString (time . Now ().Format (config .CustomTimeFormat ))
331+ return buf .WriteString (timeNow ().Format (config .CustomTimeFormat ))
323332 case "id" :
324333 id := req .Header .Get (echo .HeaderXRequestID )
325334 if id == "" {
326335 id = res .Header ().Get (echo .HeaderXRequestID )
327336 }
328- return buf . WriteString ( id )
337+ return writeString ( buf , id )
329338 case "remote_ip" :
330- return buf . WriteString ( c .RealIP ())
339+ return writeString ( buf , c .RealIP ())
331340 case "host" :
332- return buf . WriteString ( req .Host )
341+ return writeString ( buf , req .Host )
333342 case "uri" :
334- return buf . WriteString ( req .RequestURI )
343+ return writeString ( buf , req .RequestURI )
335344 case "method" :
336- return buf . WriteString ( req .Method )
345+ return writeString ( buf , req .Method )
337346 case "path" :
338347 p := req .URL .Path
339348 if p == "" {
340349 p = "/"
341350 }
342- return buf . WriteString ( p )
351+ return writeString ( buf , p )
343352 case "route" :
344- return buf . WriteString ( c .Path ())
353+ return writeString ( buf , c .Path ())
345354 case "protocol" :
346- return buf . WriteString ( req .Proto )
355+ return writeString ( buf , req .Proto )
347356 case "referer" :
348- return buf . WriteString ( req .Referer ())
357+ return writeString ( buf , req .Referer ())
349358 case "user_agent" :
350- return buf . WriteString ( req .UserAgent ())
359+ return writeString ( buf , req .UserAgent ())
351360 case "status" :
352361 n := res .Status
353362 s := config .colorer .Green (n )
@@ -377,17 +386,17 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
377386 if cl == "" {
378387 cl = "0"
379388 }
380- return buf . WriteString ( cl )
389+ return writeString ( buf , cl )
381390 case "bytes_out" :
382391 return buf .WriteString (strconv .FormatInt (res .Size , 10 ))
383392 default :
384393 switch {
385394 case strings .HasPrefix (tag , "header:" ):
386- return buf . Write ([] byte ( c .Request ().Header .Get (tag [7 :]) ))
395+ return writeString ( buf , c .Request ().Header .Get (tag [7 :]))
387396 case strings .HasPrefix (tag , "query:" ):
388- return buf . Write ([] byte ( c .QueryParam (tag [6 :]) ))
397+ return writeString ( buf , c .QueryParam (tag [6 :]))
389398 case strings .HasPrefix (tag , "form:" ):
390- return buf . Write ([] byte ( c .FormValue (tag [5 :]) ))
399+ return writeString ( buf , c .FormValue (tag [5 :]))
391400 case strings .HasPrefix (tag , "cookie:" ):
392401 cookie , err := c .Cookie (tag [7 :])
393402 if err == nil {
0 commit comments