@@ -18,7 +18,9 @@ import (
1818 "time"
1919
2020 "github.com/go-resty/resty/v2"
21+ "github.com/google/uuid"
2122 "github.com/rs/zerolog"
23+ "github.com/rs/zerolog/hlog"
2224)
2325
2426// Route holds information about the mock route configuration
@@ -173,7 +175,7 @@ func Wake(options ...ServerOption) (*Server, error) {
173175 if p .jsonLogs {
174176 writers = append (writers , os .Stderr )
175177 } else {
176- consoleOut := zerolog.ConsoleWriter {Out : os .Stderr , TimeFormat : time . RFC3339Nano }
178+ consoleOut := zerolog.ConsoleWriter {Out : os .Stderr , TimeFormat : "2006-01-02T15:04:05.000" }
177179 writers = append (writers , consoleOut )
178180 }
179181
@@ -208,7 +210,7 @@ func Wake(options ...ServerOption) (*Server, error) {
208210 p .server = & http.Server {
209211 ReadHeaderTimeout : 5 * time .Second ,
210212 Addr : listener .Addr ().String (),
211- Handler : mux ,
213+ Handler : p . loggingMiddleware ( mux ) ,
212214 }
213215
214216 if err = p .load (); err != nil {
@@ -290,69 +292,71 @@ func (p *Server) Register(route *Route) error {
290292 p .routesMu .Lock ()
291293 defer p .routesMu .Unlock ()
292294 p .routes [route .ID ()] = route
295+ p .log .Info ().
296+ Str ("Route ID" , route .ID ()).
297+ Str ("Path" , route .Path ).
298+ Str ("Method" , route .Method ).
299+ Msg ("Route registered" )
293300
294301 return nil
295302}
296303
297304// registerRouteHandler handles the dynamic route registration.
298305func (p * Server ) registerRouteHandler (w http.ResponseWriter , r * http.Request ) {
299- const parrotPath = "/register"
306+ registerLogger := zerolog . Ctx ( r . Context ())
300307 if r .Method == http .MethodDelete {
301308 var routeRequest * RouteRequest
302309 if err := json .NewDecoder (r .Body ).Decode (& routeRequest ); err != nil {
303310 http .Error (w , "Invalid request body" , http .StatusBadRequest )
311+ registerLogger .Debug ().Err (err ).Msg ("Failed to decode request body" )
304312 return
305313 }
306314 defer r .Body .Close ()
307315
308316 if routeRequest .ID == "" {
309- err := errors . New ( " ID required" )
310- http . Error ( w , err . Error (), http . StatusBadRequest )
317+ http . Error ( w , "Route ID required", http . StatusBadRequest )
318+ registerLogger . Debug (). Msg ( "No Route ID provided" )
311319 return
312320 }
313321
314322 err := p .Unregister (routeRequest .ID )
315323 if err != nil {
316324 http .Error (w , err .Error (), http .StatusBadRequest )
317- p . log . Trace ().Err (err ). Str ( "Path" , parrotPath ).Msg ("Failed to unregister route" )
325+ registerLogger . Debug ().Err (err ).Msg ("Failed to unregister route" )
318326 return
319327 }
320328
321329 w .WriteHeader (http .StatusNoContent )
322- p . log .Info ().
330+ registerLogger .Info ().
323331 Str ("Route ID" , routeRequest .ID ).
324- Str ("Parrot Path" , parrotPath ).
325332 Msg ("Route unregistered" )
326333 } else if r .Method == http .MethodPost {
327334 var route * Route
328335 if err := json .NewDecoder (r .Body ).Decode (& route ); err != nil {
329336 http .Error (w , "Invalid request body" , http .StatusBadRequest )
337+ registerLogger .Debug ().Err (err ).Msg ("Failed to decode request body" )
330338 return
331339 }
332340 defer r .Body .Close ()
333341
334342 if route .Method == "" || route .Path == "" {
335343 err := errors .New ("Method and path are required" )
336344 http .Error (w , err .Error (), http .StatusBadRequest )
345+ registerLogger .Debug ().Err (err ).Msg ("Method and path are required" )
337346 return
338347 }
339348
340349 err := p .Register (route )
341350 if err != nil {
342351 http .Error (w , err .Error (), http .StatusBadRequest )
343- p . log . Trace ().Err (err ).Msg ("Failed to register route" )
352+ registerLogger . Debug ().Err (err ).Msg ("Failed to register route" )
344353 return
345354 }
346355
347356 w .WriteHeader (http .StatusCreated )
348- p .log .Info ().
349- Str ("Parrot Path" , parrotPath ).
350- Str ("Route Path" , route .Path ).
351- Str ("Method" , route .Method ).
352- Msg ("Route registered" )
353357 } else {
354358 http .Error (w , "Invalid method, only use POST or DELETE" , http .StatusMethodNotAllowed )
355- p . log . Trace (). Str ( "Method" , r . Method ).Msg ("Invalid method" )
359+ registerLogger . Debug ( ).Msg ("Invalid method" )
356360 return
357361 }
358362}
@@ -361,34 +365,44 @@ func (p *Server) registerRouteHandler(w http.ResponseWriter, r *http.Request) {
361365func (p * Server ) Record (recorder * Recorder ) error {
362366 p .recordersMu .Lock ()
363367 defer p .recordersMu .Unlock ()
368+ if recorder == nil {
369+ return ErrNilRecorder
370+ }
371+ if recorder .URL == "" {
372+ return ErrNoRecorderURL
373+ }
374+ _ , err := url .Parse (recorder .URL )
375+ if err != nil {
376+ return fmt .Errorf ("failed to parse recorder URL: %w" , err )
377+ }
364378 p .recorderHooks = append (p .recorderHooks , recorder .URL )
365379 return nil
366380}
367381
368382func (p * Server ) recordHandler (w http.ResponseWriter , r * http.Request ) {
369- const parrotPath = "/record"
383+ recordLogger := zerolog . Ctx ( r . Context ())
370384 if r .Method != http .MethodPost {
371385 http .Error (w , "Invalid method, only use POST or DELETE" , http .StatusMethodNotAllowed )
372- p . log . Trace (). Str ( "Method" , r . Method ).Msg ("Invalid method" )
386+ recordLogger . Debug ( ).Msg ("Invalid method" )
373387 return
374388 }
375389
376390 var recorder * Recorder
377391 if err := json .NewDecoder (r .Body ).Decode (& recorder ); err != nil {
378392 http .Error (w , "Invalid request body" , http .StatusBadRequest )
379- p . log . Trace (). Err (err ). Str ( "Parrot Path" , parrotPath ).Msg ("Failed to decode request body" )
393+ recordLogger . Err (err ).Msg ("Failed to decode request body" )
380394 return
381395 }
382396
383397 err := p .Record (recorder )
384398 if err != nil {
385399 http .Error (w , err .Error (), http .StatusBadRequest )
386- p . log . Trace ().Err (err ). Str ( "Parrot Path" , parrotPath ).Msg ("Failed to add recorder" )
400+ recordLogger . Debug ().Err (err ).Msg ("Failed to add recorder" )
387401 return
388402 }
389403
390404 w .WriteHeader (http .StatusCreated )
391- p . log . Info ().Str ("Recorder URL" , recorder .URL ). Str ( "Parrot Path" , parrotPath ).Msg ("Recorder added" )
405+ recordLogger . Info ().Str ("Recorder URL" , recorder .URL ).Msg ("Recorder added" )
392406}
393407
394408// Unregister removes a route from the parrot
@@ -423,78 +437,98 @@ func (p *Server) dynamicHandler(w http.ResponseWriter, r *http.Request) {
423437 route , exists := p .routes [r .Method + ":" + r .URL .Path ]
424438 p .routesMu .RUnlock ()
425439
440+ dynamicLogger := zerolog .Ctx (r .Context ())
441+ if ! exists {
442+ http .NotFound (w , r )
443+ dynamicLogger .Debug ().Msg ("Route not found" )
444+ return
445+ }
446+
447+ requestID := uuid .New ().String ()[0 :8 ]
448+ dynamicLogger .UpdateContext (func (c zerolog.Context ) zerolog.Context {
449+ return c .Str ("Request ID" , requestID ).Str ("Route ID" , route .ID ())
450+ })
451+
452+ requestBody , err := io .ReadAll (r .Body )
453+ if err != nil {
454+ dynamicLogger .Debug ().
455+ Err (err ).
456+ Msg ("Failed to read request body" )
457+ http .Error (w , "Failed to read request body" , http .StatusInternalServerError )
458+ return
459+ }
460+
426461 routeCall := & RouteCall {
427462 RouteID : r .Method + ":" + r .URL .Path ,
428- Request : r ,
463+ Request : & RouteCallRequest {
464+ Method : r .Method ,
465+ URL : r .URL ,
466+ Header : r .Header ,
467+ Body : requestBody ,
468+ },
429469 }
430470 recordingWriter := newResponseWriterRecorder (w )
431471
432472 defer func () {
433- routeCall .Response = recordingWriter .Result ()
473+ res := recordingWriter .Result ()
474+ resBody , err := io .ReadAll (res .Body )
475+ if err != nil {
476+ dynamicLogger .Debug ().Err (err ).Msg ("Failed to read response body" )
477+ http .Error (w , "Failed to read response body" , http .StatusInternalServerError )
478+ return
479+ }
480+
481+ routeCall .Response = & RouteCallResponse {
482+ StatusCode : res .StatusCode ,
483+ Header : res .Header ,
484+ Body : resBody ,
485+ }
434486 p .sendToRecorders (routeCall )
435487 }()
436488
437- if ! exists { // Route not found
438- http .NotFound (recordingWriter , r )
439- p .log .Trace ().Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Route not found" )
440- return
441- }
442-
443489 // Let the custom handler take over if it exists
444490 if route .Handler != nil {
445- p . log . Trace (). Str ( "Remote Addr" , r . RemoteAddr ). Str ( "Path" , r . URL . Path ). Str ( "Method" , r . Method ).Msg ("Calling route handler" )
491+ dynamicLogger . Debug ( ).Msg ("Calling route handler" )
446492 route .Handler (recordingWriter , r )
447493 return
448494 }
449495
450- recordingWriter .WriteHeader (route .ResponseStatusCode )
451-
452496 if route .RawResponseBody != "" {
453497 if _ , err := w .Write ([]byte (route .RawResponseBody )); err != nil {
454- p . log . Trace ().Err (err ). Str ( "Remote Addr" , r . RemoteAddr ). Str ( "Path" , r . URL . Path ). Str ( "Method" , r . Method ).Msg ("Failed to write response" )
498+ dynamicLogger . Debug ().Err (err ).Msg ("Failed to write response" )
455499 http .Error (recordingWriter , "Failed to write response" , http .StatusInternalServerError )
456500 return
457501 }
458- p .log .Trace ().
459- Str ("Remote Addr" , r .RemoteAddr ).
502+ dynamicLogger .Debug ().
460503 Str ("Response" , route .RawResponseBody ).
461- Str ("Path" , r .URL .Path ).
462- Str ("Method" , r .Method ).
463504 Msg ("Returned raw response" )
505+ recordingWriter .WriteHeader (route .ResponseStatusCode )
464506 return
465507 }
466508
467509 if route .ResponseBody != nil {
468510 rawJSON , err := json .Marshal (route .ResponseBody )
469511 if err != nil {
470- p .log .Trace ().Err (err ).
471- Str ("Remote Addr" , r .RemoteAddr ).
472- Str ("Path" , r .URL .Path ).
473- Str ("Method" , r .Method ).
474- Msg ("Failed to marshal JSON response" )
512+ dynamicLogger .Debug ().Err (err ).Msg ("Failed to marshal JSON response" )
475513 http .Error (recordingWriter , "Failed to marshal response into json" , http .StatusInternalServerError )
476514 return
477515 }
478516 if _ , err = w .Write (rawJSON ); err != nil {
479- p . log . Trace ().Err (err ).
517+ dynamicLogger . Debug ().Err (err ).
480518 RawJSON ("Response" , rawJSON ).
481- Str ("Remote Addr" , r .RemoteAddr ).
482- Str ("Path" , r .URL .Path ).
483- Str ("Method" , r .Method ).
484519 Msg ("Failed to write response" )
485520 http .Error (recordingWriter , "Failed to write JSON response" , http .StatusInternalServerError )
486521 return
487522 }
488- p .log .Trace ().
489- Str ("Remote Addr" , r .RemoteAddr ).
523+ dynamicLogger .Debug ().
490524 RawJSON ("Response" , rawJSON ).
491- Str ("Path" , r .URL .Path ).
492- Str ("Method" , r .Method ).
493525 Msg ("Returned JSON response" )
526+ recordingWriter .WriteHeader (route .ResponseStatusCode )
494527 return
495528 }
496529
497- p .log .Error ().Str ("Remote Addr" , r .RemoteAddr ).Str ("Path" , r .URL .Path ).Str ("Method" , r .Method ).Msg ("Route has no response" )
530+ dynamicLogger .Error ().Msg ("Route has no response" )
531+ http .Error (recordingWriter , "Route has no response" , http .StatusInternalServerError )
498532}
499533
500534// load loads all registered routes from a file.
@@ -554,12 +588,15 @@ func (p *Server) save() error {
554588func (p * Server ) sendToRecorders (routeCall * RouteCall ) {
555589 p .recordersMu .RLock ()
556590 defer p .recordersMu .RUnlock ()
591+ if len (p .recorderHooks ) == 0 {
592+ return
593+ }
557594
558595 client := resty .New ()
596+ p .log .Trace ().Strs ("Recorders" , p .recorderHooks ).Str ("Route ID" , routeCall .RouteID ).Msg ("Sending route call to recorders" )
559597
560598 for _ , hook := range p .recorderHooks {
561599 go func (hook string ) {
562- p .log .Trace ().Str ("Recorder Hook" , hook ).Msg ("Sending route call to recorder" )
563600 resp , err := client .R ().SetBody (routeCall ).Post (hook )
564601 if err != nil {
565602 p .log .Error ().Err (err ).Str ("Recorder Hook" , hook ).Msg ("Failed to send route call to recorder" )
@@ -573,11 +610,30 @@ func (p *Server) sendToRecorders(routeCall *RouteCall) {
573610 Msg ("Failed to send route call to recorder" )
574611 return
575612 }
576- p .log .Debug ( ).Str ("Recorder Hook" , hook ).Msg ("Route call sent to recorder" )
613+ p .log .Trace (). Str ( "Route ID" , routeCall . RouteID ).Str ("Recorder Hook" , hook ).Msg ("Route call sent to recorder" )
577614 }(hook )
578615 }
579616}
580617
618+ func (p * Server ) loggingMiddleware (next http.Handler ) http.Handler {
619+ h := hlog .NewHandler (p .log )
620+
621+ accessHandler := hlog .AccessHandler (
622+ func (r * http.Request , status , size int , duration time.Duration ) {
623+ hlog .FromRequest (r ).Trace ().
624+ Str ("Method" , r .Method ).
625+ Stringer ("URL" , r .URL ).
626+ Int ("Status Code" , status ).
627+ Int ("Response Size Bytes" , size ).
628+ Str ("Duration" , duration .String ()).
629+ Str ("Remote Addr" , r .RemoteAddr ).
630+ Msg ("Handled request" )
631+ },
632+ )
633+
634+ return h (accessHandler (next ))
635+ }
636+
581637var pathRegex = regexp .MustCompile (`^\/[a-zA-Z0-9\-._~%!$&'()*+,;=:@\/]*$` )
582638
583639func isValidPath (path string ) bool {
0 commit comments