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  }