github.com/epsagon/epsagon-go@v1.39.0/README.md (about)

     1  <p align="center">
     2    <a href="https://epsagon.com" target="_blank" align="center">
     3      <img src="https://cdn2.hubspot.net/hubfs/4636301/Positive%20RGB_Logo%20Horizontal%20-01.svg" width="300">
     4    </a>
     5    <br />
     6  </p>
     7  
     8  [![Build Status](https://travis-ci.com/epsagon/epsagon-go.svg?token=wsveVqcNtBtmq6jpZfSf&branch=master)](https://travis-ci.com/epsagon/epsagon-go)
     9  [![GoDoc](https://godoc.org/github.com/epsagon/epsagon-go?status.svg)](https://godoc.org/github.com/epsagon/epsagon-go)
    10  
    11  # Epsagon Tracing for Go
    12  
    13  ![Trace](trace.png)
    14  
    15  
    16  This package provides tracing to Go applications for the collection of distributed tracing and performance metrics in [Epsagon](https://app.epsagon.com/?utm_source=github).
    17  
    18  
    19  ## Contents
    20  
    21  - [Installation](#installation)
    22  - [Usage](#usage)
    23    - [Tagging Traces](#tagging-traces)
    24    - [Custom Errors](#custom-errors)
    25    - [Ignored Keys](#ignored-keys)
    26  - [Frameworks](#frameworks)
    27  - [Integrations](#integrations)
    28  - [Configuration](#configuration)
    29  - [Getting Help](#getting-help)
    30  - [Opening Issues](#opening-issues)
    31  - [License](#license)
    32  
    33  
    34  ## Installation
    35  
    36  To install Epsagon, simply run:
    37  ```sh
    38  go get github.com/epsagon/epsagon-go
    39  ```
    40  
    41  Or using `dep`:
    42  ```sh
    43  dep ensure -add github.com/epsagon/epsagon-go
    44  ```
    45  
    46  ## Usage
    47  
    48  ### Tagging Traces
    49  
    50  You can add custom tags to your traces, for easier filtering and aggregations.
    51  
    52  Add the following call inside your code:
    53  ```go
    54  epsagon.Label("key", "value")
    55  epsagon.Label("user_id", user_id)
    56  ```
    57  
    58  You can also use it to ship custom metrics:
    59  ```go
    60  epsagon.Label("key", "metric")
    61  epsagon.Label("items_in_cart", items_in_cart)
    62  ```
    63  
    64  Valid types are `string`, `bool`, `int` and `float`.
    65  Custom labels are not trimmed with the trace events in case the trace is too big
    66  
    67  ### Custom Errors
    68  
    69  You can set a trace as an error (although handled correctly) to get an alert or just follow it on the dashboard.
    70  Add the following call inside your code:
    71  ```go
    72  epsagon.TypeError("My custom error", "Custom Error Type")
    73  # Or manually add an error
    74  epsagon.TypeError(errors.New("My custom error"), "Custom Error Type")
    75  ```
    76  
    77  You can also set a tracer as an error with a default error type:
    78  ```go
    79  epsagon.Error("My custom error")
    80  # Or manually add an error
    81  epsagon.Error(errors.New("My custom error"))
    82  ```
    83  
    84  Valid types are `string` and `error`.
    85  
    86  ### Ignored Keys
    87  
    88  You can set keys that will be masked in the sent trace from the events metadata to hide selected information:
    89  ```go
    90  	config.IgnoredKeys = []string{"password"}
    91  	client := http.Client{Transport: epsagonhttp.NewTracingTransport(ctx)}
    92  	// This password will be masked in the sent trace:
    93  	decodedJSON, err := json.Marshal(map[string]string{"password": "abcde", "animal": "lion"})
    94  	resp, err := client.Post("http://example.com/upload", "application/json", bytes.NewReader(decodedJSON))
    95  ```
    96  
    97  ## Frameworks
    98  
    99  The following frameworks are supported by Epsagon:
   100  
   101  |Framework                               |Supported Version          |
   102  |----------------------------------------|---------------------------|
   103  |[AWS Lambda](#aws-lambda)               |All                        |
   104  |[Generic Function](#generic)            |All                        |
   105  |[HTTP](#http)                           |All                        |
   106  |[Gin](#gin)                             |All                        |
   107  |[Fiber](#fiber)                         | >= 2.11.0                 |
   108  |[go-redis](#go-redis)                   | >= 8.0.0                  |
   109  |[mongo](#mongo)                         |>=1.0                      |
   110  
   111  
   112  ### AWS Lambda
   113  
   114  Tracing Lambda functions can be done in the following method:
   115  
   116  ```go
   117  package main
   118  
   119  import (
   120  	"github.com/aws/aws-lambda-go/events"
   121  	"github.com/aws/aws-lambda-go/lambda"
   122  	"github.com/epsagon/epsagon-go/epsagon"
   123  	"log"
   124  )
   125  
   126  func myHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
   127  	log.Println("In myHandler, received body: ", request.Body)
   128  	return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
   129  }
   130  
   131  func main() {
   132  	log.Println("enter main")
   133  	lambda.Start(epsagon.WrapLambdaHandler(
   134          epsagon.NewTracerConfig("app-name-stage","epsagon-token"),
   135          myHandler))
   136  }
   137  ```
   138  
   139  ### Generic
   140  
   141  You can instrument a single function, this function can use go routines inside and their operations will still be traced.
   142  
   143  ```go
   144  func doTask(a int, b string) (int, error) {
   145  	log.Printf("inside doTask: b = %s", b)
   146  	return a + 1, fmt.Errorf("boom")
   147  }
   148  func main() {
   149  	// With Epsagon instrumentation
   150  	config := epsagon.NewTracerConfig("generic-go-wrapper", "")
   151  	config.Debug = true
   152  	response := epsagon
   153  	
   154  	.GoWrapper(config, doTask)(5, "hello")
   155  	res2 := response[0].Int()
   156  	errInterface := response[1].Interface()
   157  }
   158  ```
   159  Optionally, you can pass a custom name for your wrapped function. In the epsagon dashboard,
   160  your wrapped function will be displayed with your configured name in all the relevant screens:
   161  traces search, service map and more.
   162  ```
   163  	response := epsagon.GoWrapper(config, doTask, "<MyInstrumentedFuncName>")(5, "hello")
   164  ```
   165  
   166  ### Concurrent Generic
   167  In order to support more than one function being traced in the same environment (using different goroutines), use this wrapper as shown in the example below. The wrapped function has to receive a context as its first parameter, and pass it to the relevant wrapped operations.
   168  
   169  
   170  ```go
   171  func doTask(ctx context.Context, a int, b string, wg *sync.WaitGroup) (int, error) {
   172  	defer wg.Done()
   173  	log.Printf("inside doTask: b = %s", b)
   174  	client := epsagonhttp.Wrap(http.Client{}, ctx)
   175  	client.Get("https://epsagon.com/")
   176  	return a + 1, fmt.Errorf("boom")
   177  }
   178  
   179  func main() {
   180  	config := epsagon.NewTracerConfig("generic-go-wrapper", "")
   181  	config.Debug = true
   182  	var wg sync.WaitGroup
   183  	for i := 0; i < 5; i++ {
   184  		go epsagon.ConcurrentGoWrapper(config, doTask)(i, "hello", &wg)
   185  	}
   186  	wg.Wait()
   187  	time.Sleep(2 * time.Second)
   188  }
   189  ```
   190  Optionally, you can pass a custom name for your wrapped function. In the epsagon dashboard,
   191  your wrapped function will be displayed with your configured name in all the relevant screens:
   192  traces search, service map and more.
   193  ```
   194  		go epsagon.ConcurrentGoWrapper(config, doTask, "<MyInstrumentedFuncName>")(i, "hello", &wg)
   195  ```
   196  
   197  ### http
   198  
   199  Wrapping http handlers with Epsagon:
   200  ```go
   201  import (
   202  	"github.com/epsagon/epsagon-go/epsagon"
   203  	epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
   204  )
   205  
   206  func main() {
   207  	mux := http.NewServeMux()
   208  	mux.HandleFunc("/ping", epsagonhttp.WrapHandleFunc(
   209  		epsagon.NewTracerConfig("test-http-mux", ""),
   210  		func(rw http.ResponseWriter, req *http.Request) {
   211  			epsagon.Error("Ping Endpoint Called", req.Context())
   212  			rw.Write([]byte("Pong.\n"))
   213  		}),
   214          "my-handler-name",
   215  	)
   216  
   217  	http.ListenAndServe(":8080", mux)
   218  }
   219  ```
   220  
   221  The third and fourth arguments to `epsagonhttp.WrapHandleFunc` are optional and set the resource name and the hostname. If the resource name is not set then the wrapped funcdtion name is used and the hostname is taken from the request URL if omitted.
   222  ```go
   223  	mux.HandleFunc("/ping", epsagonhttp.WrapHandleFunc(
   224  		epsagon.NewTracerConfig("test-http-mux", ""),
   225  		func(rw http.ResponseWriter, req *http.Request) {
   226  			epsagon.Error("Ping Endpoint Called", req.Context())
   227  			rw.Write([]byte("Pong.\n"))
   228  		}),
   229          "my-handler-name",
   230  		"test.hostname.com",
   231  	)
   232  ```
   233  
   234  To wrap nested libraries you can get the epsagon context from the request context:
   235  ```go
   236  client := http.Client{
   237      Transport: epsagonhttp.NewTracingTransport(req.Context())}
   238  resp, err := client.Get("http://example.com")
   239  ```
   240  
   241  ### gin
   242  
   243  You can easily instrument gin applications with Epsagon:
   244  
   245  ```go
   246  import (
   247  	"github.com/epsagon/epsagon-go/epsagon"
   248  	epsagongin "github.com/epsagon/epsagon-go/wrappers/gin"
   249  	"github.com/gin-gonic/gin"
   250  )
   251  
   252  func main() {
   253  	r := epsagongin.GinRouterWrapper{
   254  		IRouter:  gin.Default(),
   255  		Hostname: "my_site",
   256          Config:   epsagon.NewTracerConfig(
   257          "test-gin-application", "",
   258          ),
   259  	}
   260  
   261  	r.GET("/ping", func(c *gin.Context) {
   262  		c.JSON(200, gin.H{
   263  			"message": "pong",
   264  		})
   265  	})
   266  	r.IRouter.(*gin.Engine).Run()
   267  ```
   268  
   269  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:
   270  
   271  ```go
   272  client := http.Client{
   273      Transport: epsagonhttp.NewTracingTransport(epsagongin.EpsagonContext(c))}
   274  resp, err := client.Get("http://example.com")
   275  ```
   276  
   277  ### fiber
   278  
   279  You can easily instrument fiber applications with Epsagon middleware:
   280  
   281  ```go
   282  import (
   283  	"github.com/epsagon/epsagon-go/epsagon"
   284  	epsagonfiber "github.com/epsagon/epsagon-go/wrappers/fiber"
   285  	"github.com/gofiber/fiber/v2"
   286  )
   287  
   288  func main() {
   289  	config := epsagon.NewTracerConfig(
   290  		"fiber-example", "",
   291  	)
   292  	config.MetadataOnly = false
   293  	app := fiber.New()
   294  	// Match all routes
   295  	epsagonMiddleware := &epsagonfiber.FiberEpsagonMiddleware{
   296  		Config: config,
   297  	}
   298  	app.Use(epsagonMiddleware.HandlerFunc())
   299  	app.Post("/", func(c *fiber.Ctx) error {
   300  		return c.SendString("Hello, World 👋!")
   301  	})
   302  
   303  	app.Listen("0.0.0.0:3000")
   304  }
   305  app.Post("/", func(c *fiber.Ctx) error {
   306  		return c.SendString("Hello, World 👋!")
   307  	})
   308  ```
   309  
   310  If you want to instument other integrated libraries inside the fiber handler you can get the Epsagon context from the fiber.Ctx UserContext function to do that:
   311  
   312  ```go
   313  client := http.Client{
   314  	Transport: epsagonhttp.NewTracingTransport(c.UserContext()
   315  	)}
   316  resp, err := client.Get("http://example.com")
   317  ```
   318  
   319  ### go-redis
   320  
   321  The go-redis instrumentation supports both single and pipeline operations ([full example](https://github.com/epsagon/epsagon-go/tree/master/example)):
   322  
   323  ```go
   324  func main() {
   325  	config := epsagon.NewTracerConfig("redis-wrapper-test", "")
   326  	config.MetadataOnly = false
   327  
   328  	mux := http.NewServeMux()
   329  	mux.HandleFunc("/ping", epsagonhttp.WrapHandleFunc(
   330  		config,
   331  		func(w http.ResponseWriter, req *http.Request) {
   332  			// initialize the redis client as usual
   333  			// make sure to pass in the epsagon tracer context
   334  			rdb := epsagonredis.NewClient(&redis.Options{
   335  				Addr:     "localhost:6379",
   336  				Password: "",
   337  				DB:       0,
   338  			}, req.Context())
   339  
   340  			value, _ := rdb.Get(context.Background(), "somekey").Result()
   341  			io.WriteString(w, value)
   342  		}),
   343  	)
   344  	http.ListenAndServe(":8080", mux)
   345  }
   346  ```
   347  
   348  ### mongo
   349  
   350  Trace through all Mongo collection operations by wrapping the Collection:
   351  
   352  ```go
   353  
   354  package main
   355  
   356  import (
   357  	"context"
   358  	"github.com/epsagon/epsagon-go/epsagon"
   359  	epsagonmongo "github.com/epsagon/epsagon-go/wrappers/mongo"
   360  	"time"
   361  
   362  )
   363  
   364  
   365  func main() {
   366  
   367  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   368  	defer cancel()
   369  	client, err := mongo.Connect(
   370  		ctx,
   371  		options.Client().ApplyURI("mongodb://..."),
   372  	)
   373  	defer func() {
   374  		if err = client.Disconnect(ctx); err != nil {
   375  			panic(err)
   376  		}
   377  	}()
   378  
   379  	db := client.Database("DB")
   380  	coll := epsagonmongo.WrapMongoCollection(
   381  		db.Collection("COLL"),
   382  	)
   383  }
   384  
   385  ```
   386  
   387  ## Integrations
   388  
   389  Epsagon provides out-of-the-box instrumentation (tracing) for many popular frameworks and libraries.
   390  
   391  |Library              |Supported Version          |
   392  |---------------------|---------------------------|
   393  |[net/http](#net/http)|Fully supported            |
   394  |aws-sdk-go           |`>=1.10.0`                 |
   395  |aws-sdk-go-v2        |`>=0.23.0`                 |
   396  
   397  
   398  ### net/http
   399  
   400  Any http request can be traced using a custom RoundTripper implementation and using that in an http.Client:
   401  
   402  ```go
   403  import (
   404  	"github.com/epsagon/epsagon-go/wrappers/net/http"
   405  ...
   406  	client := http.Client{Transport: epsagonhttp.NewTracingTransport()}
   407  	resp, err := client.Get(anyurl)
   408  ```
   409  
   410  If you are already using a custom RoundTripper implementation, such as for AWS V4 request signing, you can wrap it:
   411  
   412  ```go
   413  import (
   414  	"github.com/epsagon/epsagon-go/wrappers/net/http"
   415  ...
   416  	rt := &custom.Roundtripper{}
   417  	client := http.Client{Transport: epsagonhttp.NewWrappedTracingTransport(rt)}
   418  	resp, err := client.Get(anyurl)
   419  ```
   420  
   421  The wrapped http.Client functionality exists for backwards compatibility:
   422  
   423  ```go
   424  import (
   425  	"github.com/epsagon/epsagon-go/wrappers/net/http"
   426  ...
   427  	client := epsagonhttp.Wrap(http.Client{})
   428  	resp, err := client.Get(anyurl)
   429  ```
   430  
   431  If you want to disable data collection only for the calls made by this client set  `client.MetadataOnly = true`
   432  
   433  ### aws-sdk-go
   434  
   435  Wrapping of aws-sdk-go is done through the Session object that has to be created to communicate with AWS:
   436  
   437  ```go
   438  import (
   439  ...
   440  	"github.com/epsagon/epsagon-go/wrappers/aws/aws-sdk-go/aws"
   441  )
   442      ...
   443  	sess := epsagonawswrapper.WrapSession(session.Must(session.NewSession()))
   444  	svcSQS := sqs.New(sess)
   445  ```
   446  
   447  
   448  ### aws-sdk-go-v2
   449  
   450  Wrapping of aws-sdk-go-v2 is done through the service object:
   451  ```go
   452  import (
   453  ...
   454  	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
   455  	"github.com/epsagon/epsagon-go/epsagon"
   456  )
   457  
   458  	svc := epsagon.WrapAwsV2Service(dynamodb.New(cfg)).(*dynamodb.Client)
   459      ...
   460  ```
   461  
   462  
   463  
   464  
   465  
   466  ## Configuration
   467  
   468  Advanced options can be configured as a parameter to the `Config` struct to the `WrapLambdaHandler` or as environment variables.
   469  
   470  |Parameter             |Environment Variable                |Type   |Default      |Description                                                                        |
   471  |----------------------|------------------------------------|-------|-------------|-----------------------------------------------------------------------------------|
   472  |Token                 |EPSAGON_TOKEN                       |String |-            |Epsagon account token                                                              |
   473  |ApplicationName       |-                                   |String |-            |Application name that will be set for traces                                       |
   474  |MetadataOnly          |EPSAGON_METADATA                    |Boolean|`true`       |Whether to send only the metadata (`True`) or also the payloads (`False`)          |
   475  |CollectorURL          |EPSAGON_COLLECTOR_URL               |String |-            |The address of the trace collector to send trace to                                |
   476  |Debug                 |EPSAGON_DEBUG                       |Boolean|`False`      |Enable debug prints for troubleshooting                                            |
   477  |SendTimeout           |EPSAGON_SEND_TIMEOUT_SEC            |String |`1s`         |The timeout duration to send the traces to the trace collector                     |
   478  |MaxTraceSize          |EPSAGON_MAX_TRACE_SIZE              |Integer|`1287936`    |The max allowed trace size (in bytes). Defaults to 64KB, max allowed size - 512KB  |
   479  |_                     |EPSAGON_LAMBDA_TIMEOUT_THRESHOLD_MS |Integer|`200`        |The threshold in milliseconds to send the trace before a Lambda timeout occurs     |
   480  
   481  
   482  ## Getting Help
   483  
   484  If you have any issue around using the library or the product, please don't hesitate to:
   485  
   486  * Use the [documentation](https://docs.epsagon.com).
   487  * Use the help widget inside the product.
   488  * Open an issue in GitHub.
   489  
   490  
   491  ## Opening Issues
   492  
   493  If you encounter a bug with the Epsagon library for Go, we want to hear about it.
   494  
   495  When opening a new issue, please provide as much information about the environment:
   496  * Library version, Go runtime version, dependencies, etc.
   497  * Snippet of the usage.
   498  * A reproducible example can really help.
   499  
   500  The GitHub issues are intended for bug reports and feature requests.
   501  For help and questions about Epsagon, use the help widget inside the product.
   502  
   503  ## License
   504  
   505  Provided under the MIT license. See LICENSE for details.
   506  
   507  Copyright 2020, Epsagon