Skip to content

Commit

Permalink
Gin trigger event (epsagon#74)
Browse files Browse the repository at this point in the history
* moving ExtractTracer to epsagon and using noncolliding key

* Adding gin trigger event

* improving gin example with nested instrumentation

* gin: adding trigger event tests

* gin: updating README

* gin: save status code to trigger

* defer runner type set, return query string on error
  • Loading branch information
enoodle authored Nov 2, 2020
1 parent bbc378f commit cb5c69e
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 175 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ The following frameworks are supported by Epsagon:
|----------------------------------------|---------------------------|
|[AWS Lambda](#aws-lambda) |All |
|[Generic Function](#generic) |All |
|[Gin](#gin) |All |


### AWS Lambda
Expand Down Expand Up @@ -171,6 +172,42 @@ traces search, service map and more.
go epsagon.ConcurrentGoWrapper(config, doTask, "<MyInstrumentedFuncName>")(i, "hello", &wg)
```

### gin

You can easily instrument gin applications with Epsagon:

```go
import (
"github.com/epsagon/epsagon-go/epsagon"
epsagongin "github.com/epsagon/epsagon-go/wrappers/gin"
"github.com/gin-gonic/gin"
)

func main() {
r := epsagongin.GinRouterWrapper{
IRouter: gin.Default(),
Hostname: "my_site",
Config: epsagon.NewTracerConfig(
"test-gin-application", "",
),
}

r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.IRouter.(*gin.Engine).Run()
```
If you want to instument other integrated libraries inside the gin handler you can get the Epsagon context from the gin.Context to do that:
```go
client := http.Client{
Transport: epsagonhttp.NewTracingTransport(epsagongin.EpsagonContext(c))}
resp, err := client.Get("http://example.com")
```
## Integrations
Epsagon provides out-of-the-box instrumentation (tracing) for many popular frameworks and libraries.
Expand Down
3 changes: 1 addition & 2 deletions epsagon/aws_v2_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/epsagon/epsagon-go/epsagon/aws_sdk_v2_factories"
"github.com/epsagon/epsagon-go/internal"
"github.com/epsagon/epsagon-go/protocol"
"github.com/epsagon/epsagon-go/tracer"
"log"
Expand All @@ -22,7 +21,7 @@ func WrapAwsV2Service(svcClient interface{}, args ...context.Context) interface{
aws.NamedHandler{
Name: "epsagon-aws-sdk-v2",
Fn: func(r *aws.Request) {
currentTracer := internal.ExtractTracer(args)
currentTracer := ExtractTracer(args)
completeEventData(r, currentTracer)
},
},
Expand Down
111 changes: 106 additions & 5 deletions epsagon/common_utils.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package epsagon

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"

"github.com/epsagon/epsagon-go/internal"
"github.com/epsagon/epsagon-go/tracer"
"github.com/onsi/gomega/types"
)

// DefaultErrorType Default custom error type
const DefaultErrorType = "Error"

// MaxMetadataSize Maximum size of event metadata
const MaxMetadataSize = 10 * 1024

// Config is the configuration for Epsagon's tracer
type Config struct {
tracer.Config
Expand Down Expand Up @@ -39,24 +48,116 @@ func NewTracerConfig(applicationName, token string) *Config {

// Label adds a label to the sent trace
func Label(key string, value interface{}, args ...context.Context) {
currentTracer := internal.ExtractTracer(args)
currentTracer := ExtractTracer(args)
if currentTracer != nil {
currentTracer.AddLabel(key, value)
}
}

// Error adds an error to the sent trace
func Error(value interface{}, args ...context.Context) {
currentTracer := internal.ExtractTracer(args)
currentTracer := ExtractTracer(args)
if currentTracer != nil {
currentTracer.AddError(DefaultErrorType, value)
}
}

// Error adds an error to the sent trace with specific error type
// TypeError adds an error to the sent trace with specific error type
func TypeError(value interface{}, errorType string, args ...context.Context) {
currentTracer := internal.ExtractTracer(args)
currentTracer := ExtractTracer(args)
if currentTracer != nil {
currentTracer.AddError(errorType, value)
}
}

// FormatHeaders format HTTP headers to string - using first header value, ignoring the rest
func FormatHeaders(headers http.Header) (string, error) {
headersToFormat := make(map[string]string)
for headerKey, headerValues := range headers {
if len(headerValues) > 0 {
headersToFormat[headerKey] = headerValues[0]
}
}
headersJSON, err := json.Marshal(headersToFormat)
if err != nil {
return "", err
}
return string(headersJSON), nil
}

// ExtractRequestData extracts headers and body from http.Request
func ExtractRequestData(req *http.Request) (headers string, body string) {
headers, err := FormatHeaders(req.Header)
if err != nil {
headers = ""
}

if req.Body == nil {
return
}

buf, err := ioutil.ReadAll(req.Body)
req.Body = NewReadCloser(buf, err)
if err != nil {
return
}
// truncates request body to the first 64KB
trimmed := buf
if len(buf) > MaxMetadataSize {
trimmed = buf[0:MaxMetadataSize]
}
body = string(trimmed)
return
}

// NewReadCloser returns an io.ReadCloser
// will mimick read from body depending on given error
func NewReadCloser(body []byte, err error) io.ReadCloser {
if err != nil {
return &errorReader{err: err}
}
return ioutil.NopCloser(bytes.NewReader(body))
}

type errorReader struct {
err error
}

func (er *errorReader) Read([]byte) (int, error) {
return 0, er.err
}
func (er *errorReader) Close() error {
return er.err
}

type matchUserError struct {
exception interface{}
}

func (matcher *matchUserError) Match(actual interface{}) (bool, error) {
uErr, ok := actual.(userError)
if !ok {
return false, fmt.Errorf("excpects userError, got %v", actual)
}

if !reflect.DeepEqual(uErr.exception, matcher.exception) {
return false, fmt.Errorf("expected\n\t%v\nexception, got\n\t%v", matcher.exception, uErr.exception)
}

return true, nil
}

func (matcher *matchUserError) FailureMessage(actual interface{}) string {
return fmt.Sprintf("Expected\n\t%#v\nto be userError with exception\n\t%#v", actual, matcher.exception)
}

func (matcher *matchUserError) NegatedFailureMessage(actual interface{}) string {
return fmt.Sprintf("NegatedFailureMessage")
}

// MatchUserError matches epsagon exceptions
func MatchUserError(exception interface{}) types.GomegaMatcher {
return &matchUserError{
exception: exception,
}
}
4 changes: 1 addition & 3 deletions epsagon/generic_wrapper.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package epsagon

import (
"context"
"fmt"
"reflect"
"runtime"
Expand Down Expand Up @@ -104,8 +103,7 @@ func (wrapper *GenericWrapper) transformArguments(args ...interface{}) []reflect
inputs := make([]reflect.Value, actualLength)
argsInputs := inputs
if wrapper.injectContext {
inputs[0] = reflect.ValueOf(
context.WithValue(context.Background(), "tracer", wrapper.tracer))
inputs[0] = reflect.ValueOf(ContextWithTracer(wrapper.tracer))
argsInputs = argsInputs[1:]
}
for k, in := range args {
Expand Down
33 changes: 0 additions & 33 deletions epsagon/generic_wrapper_test.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,20 @@
package epsagon

import (
"fmt"
"reflect"
"testing"

"github.com/epsagon/epsagon-go/protocol"
"github.com/epsagon/epsagon-go/tracer"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
)

func TestGenericWrapper(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Generic Wrapper")
}

type matchUserError struct {
exception interface{}
}

func (matcher *matchUserError) Match(actual interface{}) (bool, error) {
uErr, ok := actual.(userError)
if !ok {
return false, fmt.Errorf("excpects userError, got %v", actual)
}

if !reflect.DeepEqual(uErr.exception, matcher.exception) {
return false, fmt.Errorf("expected\n\t%v\nexception, got\n\t%v", matcher.exception, uErr.exception)
}

return true, nil
}

func (matcher *matchUserError) FailureMessage(actual interface{}) string {
return fmt.Sprintf("Expected\n\t%#v\nto be userError with exception\n\t%#v", actual, matcher.exception)
}

func (matcher *matchUserError) NegatedFailureMessage(actual interface{}) string {
return fmt.Sprintf("NegatedFailureMessage")
}

func MatchUserError(exception interface{}) types.GomegaMatcher {
return &matchUserError{
exception: exception,
}
}

var _ = Describe("generic_wrapper", func() {
Describe("GoWrapper", func() {
Context("called with nil config", func() {
Expand Down
15 changes: 12 additions & 3 deletions internal/tracer_helpers.go → epsagon/tracer_helpers.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package internal
package epsagon

import (
"context"

"github.com/epsagon/epsagon-go/tracer"
)

// Extracts the tracer from given contexts (using first context),
type tracerKey string

const tracerKeyValue tracerKey = "tracer"

// ContextWithTracer creates a context with given tracer
func ContextWithTracer(t tracer.Tracer) context.Context {
return context.WithValue(context.Background(), tracerKeyValue, t)
}

// ExtractTracer Extracts the tracer from given contexts (using first context),
// returns Global tracer if no context is given and GlobalTracer is valid (= non nil, not stopped)
func ExtractTracer(ctx []context.Context) tracer.Tracer {
if len(ctx) == 0 {
Expand All @@ -15,7 +24,7 @@ func ExtractTracer(ctx []context.Context) tracer.Tracer {
}
return tracer.GlobalTracer
}
rawValue := ctx[0].Value("tracer")
rawValue := ctx[0].Value(tracerKeyValue)
if rawValue == nil {
panic("Invalid context, see Epsagon Concurrent Generic GO function example")
}
Expand Down
37 changes: 33 additions & 4 deletions example/gin_wrapper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ package main

import (
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/epsagon/epsagon-go/epsagon"
"github.com/epsagon/epsagon-go/wrappers/gin"
epsagongin "github.com/epsagon/epsagon-go/wrappers/gin"
epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
"github.com/gin-gonic/gin"
)

func main() {
// r := gin.Default()
config := epsagon.NewTracerConfig(
"erez-test-gin", "",
)
config.MetadataOnly = false
r := epsagongin.GinRouterWrapper{
IRouter: gin.Default(),
Hostname: "my_site",
Config: epsagon.NewTracerConfig(
"erez-test-gin", "",
),
Config: config,
}

r.GET("/ping", func(c *gin.Context) {
Expand All @@ -26,6 +31,30 @@ func main() {
"message": "pong",
})
})
r.POST("/ping", func(c *gin.Context) {
time.Sleep(time.Second * 1)
body, err := ioutil.ReadAll(c.Request.Body)
if err == nil {
fmt.Println("Recieved body: ", string(body))
} else {
fmt.Println("Error reading body: ", err)
}

client := http.Client{
Transport: epsagonhttp.NewTracingTransport(epsagongin.EpsagonContext(c))}
resp, err := client.Get("http://example.com")

if err == nil {
respBody, err := ioutil.ReadAll(resp.Body)
if err == nil {
fmt.Println("First 1000 bytes recieved: ", string(respBody[:1000]))
}
}

c.JSON(200, gin.H{
"message": "pong",
})
})
// r.Run()
r.IRouter.(*gin.Engine).Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
10 changes: 10 additions & 0 deletions tracer/mocked_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,13 @@ func (t *MockedEpsagonTracer) AddError(errorType string, value interface{}) {
Message: "test",
}
}

// GetRunnerEvent implements AddError
func (t *MockedEpsagonTracer) GetRunnerEvent() *protocol.Event {
for _, event := range *t.Events {
if event.Origin == "runner" {
return event
}
}
return nil
}
Loading

0 comments on commit cb5c69e

Please sign in to comment.