github.com/epsagon/epsagon-go@v1.39.0/wrappers/fiber/fiber.go (about) 1 package epsagonfiber 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "runtime/debug" 7 "strconv" 8 9 "github.com/epsagon/epsagon-go/epsagon" 10 "github.com/epsagon/epsagon-go/protocol" 11 "github.com/epsagon/epsagon-go/tracer" 12 "github.com/gofiber/fiber/v2" 13 "github.com/valyala/fasthttp" 14 ) 15 16 // FiberEpsagonMiddleware is an epsagon instumentation middleware for fiber apps 17 type FiberEpsagonMiddleware struct { 18 Config *epsagon.Config 19 } 20 21 func parseQueryArgs(args *fasthttp.Args) map[string]string { 22 result := make(map[string]string) 23 args.VisitAll(func(key, val []byte) { 24 result[string(key)] = string(val) 25 }) 26 return result 27 } 28 29 // convert map values to string. On error, add exception to tracer with the 30 // given error message 31 func convertMapValuesToString( 32 values map[string]string, 33 wrapperTracer tracer.Tracer, 34 errorMessage string) string { 35 processed, err := json.Marshal(values) 36 if err != nil { 37 wrapperTracer.AddException(&protocol.Exception{ 38 Type: "trigger-creation", 39 Message: errorMessage, 40 Traceback: string(debug.Stack()), 41 Time: tracer.GetTimestamp(), 42 }) 43 return "" 44 } 45 return string(processed) 46 } 47 48 func processQueryFromURI(uriObj *fasthttp.URI, wrapperTracer tracer.Tracer) string { 49 if uriObj == nil { 50 return "" 51 } 52 args := parseQueryArgs(uriObj.QueryArgs()) 53 return convertMapValuesToString( 54 args, 55 wrapperTracer, 56 fmt.Sprintf("Failed to serialize query params %s", uriObj.QueryString())) 57 58 } 59 60 func processRequestHeaders(requestHeaders *fasthttp.RequestHeader, wrapperTracer tracer.Tracer) string { 61 if requestHeaders == nil { 62 return "" 63 } 64 headers := make(map[string]string) 65 requestHeaders.VisitAll(func(key, val []byte) { 66 headers[string(key)] = string(val) 67 }) 68 return convertMapValuesToString( 69 headers, 70 wrapperTracer, 71 fmt.Sprintf("Failed to serialize request headers")) 72 } 73 74 func processResponseHeaders(responseHeaders *fasthttp.ResponseHeader, wrapperTracer tracer.Tracer) string { 75 if responseHeaders == nil { 76 return "" 77 } 78 headers := make(map[string]string) 79 responseHeaders.VisitAll(func(key, val []byte) { 80 headers[string(key)] = string(val) 81 }) 82 return convertMapValuesToString( 83 headers, 84 wrapperTracer, 85 fmt.Sprintf("Failed to serialize response headers")) 86 } 87 88 // Gets the request content type header, empty string if not found 89 func getRequestContentType(fiberCtx *fiber.Ctx) string { 90 return string(fiberCtx.Request().Header.ContentType()) 91 } 92 93 // CreateHTTPTriggerEvent creates an HTTP trigger event 94 func CreateHTTPTriggerEvent(wrapperTracer tracer.Tracer, fiberCtx *fiber.Ctx, resourceName string) *protocol.Event { 95 request := fiberCtx.Request() 96 97 name := resourceName 98 if len(name) == 0 { 99 name = fiberCtx.Hostname() 100 } 101 102 event := &protocol.Event{ 103 Id: "", 104 Origin: "trigger", 105 StartTime: tracer.GetTimestamp(), 106 Resource: &protocol.Resource{ 107 Name: name, 108 Type: "http", 109 Operation: fiberCtx.Method(), 110 Metadata: map[string]string{ 111 "path": fiberCtx.Path(), 112 }, 113 }, 114 } 115 if !wrapperTracer.GetConfig().MetadataOnly { 116 event.Resource.Metadata["query_string_params"] = processQueryFromURI(request.URI(), wrapperTracer) 117 event.Resource.Metadata["request_headers"] = processRequestHeaders(&request.Header, wrapperTracer) 118 event.Resource.Metadata["request_body"] = string(fiberCtx.Body()) 119 } 120 return event 121 } 122 123 func fiberHandler(c *fiber.Ctx) (err error) { 124 err = c.Next() 125 return err 126 } 127 128 func (middleware *FiberEpsagonMiddleware) HandlerFunc() fiber.Handler { 129 config := middleware.Config 130 if config == nil { 131 config = &epsagon.Config{} 132 } 133 return func(c *fiber.Ctx) (err error) { 134 if epsagon.ShouldIgnoreRequest(getRequestContentType(c), c.Path()) { 135 return c.Next() 136 } 137 138 callingOriginalHandler := false 139 called := false 140 var triggerEvent *protocol.Event = nil 141 defer func() { 142 userError := recover() 143 if userError == nil { 144 return 145 } 146 if !callingOriginalHandler { 147 err = c.Next() 148 return 149 } 150 if !called { // panic only if error happened in original handler 151 triggerEvent.Resource.Metadata["status_code"] = "500" 152 panic(userError) 153 } 154 }() 155 156 wrapperTracer := tracer.CreateTracer(&config.Config) 157 wrapperTracer.Start() 158 defer wrapperTracer.SendStopSignal() 159 userContext := c.UserContext() 160 c.SetUserContext(epsagon.ContextWithTracer(wrapperTracer, userContext)) 161 triggerEvent = CreateHTTPTriggerEvent(wrapperTracer, c, c.Hostname()) 162 wrapperTracer.AddEvent(triggerEvent) 163 wrapper := epsagon.WrapGenericFunction( 164 func(c *fiber.Ctx) error { 165 err = c.Next() 166 return err 167 }, config, wrapperTracer, false, c.Path()) 168 defer postExecutionUpdates(wrapperTracer, triggerEvent, c, wrapper) 169 callingOriginalHandler = true 170 wrapper.Call(c) 171 called = true 172 return err 173 } 174 } 175 176 func postExecutionUpdates( 177 wrapperTracer tracer.Tracer, triggerEvent *protocol.Event, 178 c *fiber.Ctx, handlerWrapper *epsagon.GenericWrapper) { 179 runner := handlerWrapper.GetRunnerEvent() 180 if runner != nil { 181 runner.Resource.Type = "fiber" 182 } 183 response := c.Response() 184 triggerEvent.Resource.Metadata["status_code"] = strconv.Itoa(response.StatusCode()) 185 if !wrapperTracer.GetConfig().MetadataOnly { 186 triggerEvent.Resource.Metadata["response_headers"] = processResponseHeaders(&response.Header, wrapperTracer) 187 triggerEvent.Resource.Metadata["response_body"] = string(response.Body()) 188 } 189 }