Giter Site home page Giter Site logo

thundra-lambda-agent-go's Introduction

Lambda Go Agent OpenTracing Badge Thundra CI Check Go Report Card

Trace your AWS lambda functions with async monitoring by Thundra!

Check out Thundra docs for more information.

Usage

In order to trace your lambda usages with Thundra all you need to do is wrap your function.

import "github.com/thundra-io/thundra-lambda-agent-go/v2/thundra"	// with go modules enabled (GO111MODULE=on or outside GOPATH) for version >= v2.3.1
import "github.com/thundra-io/thundra-lambda-agent-go/thundra"         // with go modules disabled
package main

import (
	"github.com/aws/aws-lambda-go/lambda"
	// thundra go agent import here
)

// Your lambda handler
func handler() (string, error) {
	return "Hello, Thundra!", nil
}

func main() {
	// Wrap your lambda handler with Thundra
	lambda.Start(thundra.Wrap(handler))
}

Later just build and deploy your executable to AWS as regular. Test your function on lambda console and visit Thundra to observe your function metrics.

Environment variables

Name Type Default Value
thundra_applicationProfile string default
thundra_agent_lambda_disable bool false
thundra_agent_lambda_timeout_margin number 200
thundra_agent_lambda_report_rest_baseUrl string https://api.thundra.io/v1
thundra_agent_lambda_trace_disable bool false
thundra_agent_lambda_metric_disable bool false
thundra_agent_lambda_log_disable bool false
thundra_log_logLevel string TRACE
thundra_agent_lambda_trace_request_skip bool false
thundra_agent_lambda_trace_response_skip bool false
thundra_agent_lambda_report_rest_trustAllCertificates bool false
thundra_agent_lambda_debug_enable bool false
thundra_agent_lambda_warmup_warmupAware bool false

Async Monitoring

Check out our docs to see how to configure Thundra and async monitoring to visualize your functions in Thundra.

Warmup Support

You can cut down cold starts easily by deploying our lambda function thundra-lambda-warmup.

Our agent handles warmup requests automatically so you don't need to make any code changes.

You just need to deploy thundra-lambda-warmup once, then you can enable warming up for your lambda by

  • setting its environment variable thundra_agent_lambda_warmup_warmupAware true OR
  • adding its name to thundra-lambda-warmup's environment variable thundra_lambda_warmup_function.

Check out this part in our docs for more information.

Thundra Go Agent Integrations

AWS SDK

Thundra's Go agent provides you with the capability to trace AWS SDK by wrapping the session object that AWS SDK provides. You can easily start using it by simply wrapping your session objects as shown in the following code:

package main

import (
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundraaws "github.com/thundra-io/thundra-lambda-agent-go/wrappers/aws"
)

// Your lambda handler
func handler() (string, error) {
	// Create a new session object
	sess, _ := session.NewSession(&aws.Config{
		Region: aws.String("us-west-2")},
	)

	// Wrap it using the thundraaws.Wrap method
	sess = thundraaws.Wrap(sess)

	// Create a new client using the wrapped session
	svc := dynamodb.New(sess)

	// Use the client as normal, Thundra will automatically
	// create spans for the AWS SDK calls
	svc.PutItem(&dynamodb.PutItemInput{
		Item: map[string]*dynamodb.AttributeValue{
			"AlbumTitle": {
				S: aws.String("Somewhat Famous"),
			},
			"Artist": {
				S: aws.String("No One You Know"),
			},
			"SongTitle": {
				S: aws.String("Call Me Today"),
			},
		},
		ReturnConsumedCapacity: aws.String("TOTAL"),
		TableName:              aws.String("Music"),
	})

	return "Hello, Thundra!", nil
}

func main() {
	// Wrap your lambda handler with Thundra
	lambda.Start(thundra.Wrap(handler))
}

Mongo SDK

package main

import (
	"context"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundramongo "github.com/thundra-io/thundra-lambda-agent-go/wrappers/mongodb"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type Post struct {
	Author string
	Text   string
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

	client, _ := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017").SetMonitor(thundramongo.NewCommandMonitor()))

	collection := client.Database("test").Collection("posts")
	post := Post{"Mike", "My first blog!"}

	// Insert post to mongodb
	collection.InsertOne(context.TODO(), post)

	return events.APIGatewayProxyResponse{
		Body:       "res",
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(thundra.Wrap(handler))
}

Mysql SDK

package main

import (
	"context"
	"database/sql"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/go-sql-driver/mysql"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundrardb "github.com/thundra-io/thundra-lambda-agent-go/wrappers/rdb"
)

func handler(ctx context.Context) {

	// Register wrapped driver for tracing
	// Note that driver name registered should be different than "mysql" as it is already registered on init method of
	// this package otherwise it panics.
	sql.Register("thundra-mysql", thundrardb.Wrap(&mysql.MySQLDriver{}))

	// Get the database handle with registered driver name
	db, err := sql.Open("thundra-mysql", "user:userpass@tcp(docker.for.mac.localhost:3306)/db")

	if err != nil {
		// Just for example purpose. You should use proper error handling instead of panic
		panic(err.Error())
	}
	defer db.Close()

	rows, err := db.Query("SELECT * FROM test")
	if err != nil {
		thundra.Logger.Error(err)
	}
	defer rows.Close()
}

func main() {
	lambda.Start(thundra.Wrap(handler))
}

PostgreSQL SDK

package main

import (
	"context"
	"database/sql"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/lib/pq"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundrardb "github.com/thundra-io/thundra-lambda-agent-go/wrappers/rdb"
)

func handler(ctx context.Context) {
	// Register wrapped driver for tracing
	// Note that driver name registered should be different than "postgres"
	// as it is already registered on init method of this package
	// otherwise it panics.
	sql.Register("thundra-postgres", thundrardb.Wrap(&pq.Driver{}))

	// Get the database handle with registered driver name
	db, err := sql.Open("thundra-postgres", "postgres://user:[email protected]:5432/db?sslmode=disable")

	_, err = db.Exec("CREATE table IF NOT EXISTS test(id int, type text)")

	if err != nil {
		// Just for example purpose. You should use proper error handling instead of panic
		panic(err.Error())
	}
	defer db.Close()

	// Perform a query
	rows, err := db.Query("SELECT * FROM test")
	if err != nil {
		thundra.Logger.Error(err)
	}
	defer rows.Close()
}

func main() {
	lambda.Start(thundra.Wrap(handler))
}

Redis SDK

package main

import (
	"fmt"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundraredigo "github.com/thundra-io/thundra-lambda-agent-go/wrappers/redis/redigo"
)

func test() {
	conn, err := thundraredigo.Dial("tcp", "docker.for.mac.localhost:6379")
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	ret, _ := conn.Do("SET", "mykey", "hello")
	fmt.Printf("%s\n", ret)

	ret, _ = conn.Do("GET", "mykey")
	fmt.Printf("%s\n", ret)

}

func main() {
	lambda.Start(thundra.Wrap(test))
}

Http SDK

package main

import (
	"fmt"
	"net/http"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundrahttp "github.com/thundra-io/thundra-lambda-agent-go/wrappers/http"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

	thundraHttpClient := thundrahttp.Wrap(http.Client{})

	resp, err := thundraHttpClient.Get("URL")
	if err == nil {
		fmt.Println(resp)
	}
	fmt.Println(err)

	return events.APIGatewayProxyResponse{
		Body:       "res",
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(thundra.Wrap(handler))
}

Elasticsearch SDK

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
	thundraelastic "github.com/thundra-io/thundra-lambda-agent-go/wrappers/elastic/olivere"
	"gopkg.in/olivere/elastic.v6"
)

func getClient() *elastic.Client {

	var httpClient = thundraelastic.Wrap(&http.Client{})
	var client, _ = elastic.NewClient(
		elastic.SetURL("http://localhost:9200"),
		elastic.SetHttpClient(httpClient),
		elastic.SetSniff(false),
		elastic.SetHealthcheck(false),
	)
	return client
}

// Tweet is a structure used for serializing/deserializing data in Elasticsearch.
type Tweet struct {
	User     string   `json:"user"`
	Message  string   `json:"message"`
	Retweets int      `json:"retweets"`
	Image    string   `json:"image,omitempty"`
	Tags     []string `json:"tags,omitempty"`
	Location string   `json:"location,omitempty"`
}

func handler() {

	// Starting with elastic.v5, you must pass a context to execute each service
	ctx := context.Background()

	// Obtain a client and connect to the default Elasticsearch installation
	// on 127.0.0.1:9200. Of course you can configure your client to connect
	// to other hosts and configure it in various other ways.
	client := getClient()

	// Index a tweet (using JSON serialization)
	tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
	put1, err := client.Index().
		Index("thundra").
		Type("users").
		Id("id").
		BodyJson(tweet1).
		Do(ctx)
	if err != nil {
		// Handle error
		panic(err)
	}
	fmt.Printf("Indexed tweet %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)

	// Get tweet with specified ID
	get1, err := client.Get().
		Index("twitter").
		Type("tweet").
		Id("1").
		Do(ctx)
	if err != nil {
		// Handle error
		panic(err)
	}
	if get1.Found {
		fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
	}

	// Flush to make sure the documents got written.
	_, err = client.Flush().Index("twitter").Do(ctx)
	if err != nil {
		panic(err)
	}

	// Search with a term query
	termQuery := elastic.NewTermQuery("user", "olivere")
	searchResult, err := client.Search().
		Index("twitter").   // search in index "twitter"
		Query(termQuery).   // specify the query
		Sort("user", true). // sort by "user" field, ascending
		From(0).Size(10).   // take documents 0-9
		Pretty(true).       // pretty print request and response JSON
		Do(ctx)             // execute
	if err != nil {
		// Handle error
		panic(err)
	}

}

func main() {
	lambda.Start(thundra.Wrap(handler))
}

Setting up local development environment

  1. Clone Go sample lambda app to your local
git clone https://github.com/thundra-io/thundra-go-lambda-sample-app.git

# cd into sample app

cd thundra-go-lambda-sample-app
  1. Inside of sample app root directory, clone Thundra Go agent. The sample app has configured the way that it will ignore local Thundra Go agent for git and local version changes of Thundra agent will be effective.
git clone https://github.com/thundra-io/thundra-lambda-agent-go.git
  1. In serverless.yml in project root directory, uncomment thundra_apiKey and put your Thundra api key value, which you can get from apm.thundra.io

Current serverless.yml

#thundra_apiKey: <your_thundra_ApiKey>

Should look something like this

thundra_apiKey: <your_project_key_from_thundra>
  1. Configure your sls
sls
  1. Deploy your lambda to AWS. Thundra Go lambda agent's codebase, which is going to used for sending instrumentation data to apm.thundra.io, will be your own copy at your local.
sls deploy
  1. Trigger the lambda endpoint and monitor your sample app in apm.thundra.io !

thundra-lambda-agent-go's People

Contributors

bunyaminsg avatar default-usr avatar fatihaydilek avatar faziletozer avatar hamitb avatar plazma-prizma avatar rwxdash avatar salihkardan avatar serkankorkut avatar yusuf-erdem avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

etsangsplk

thundra-lambda-agent-go's Issues

Improvement Ideas

  • Consider refactoring plugin system

    • Consider adding namespaces to plugins and split big plugins into smaller ones under same namespace. It is easier to initialize(compared to) and maintain smaller plugins. For ex.: metric plugin can be separated by each stat.

    • Consider registering plugins to global within init() funcs that should be defined in each plugin. Easier to init and load plugins this way(compared to). For ex.: Std database/sql pkg has the similar feature to load different SQL compatible database extensions.

    • Create a common plugins package to place all plugins inside.

    • I couldn't quite understand why given context to plugin API is mutated or replaced and returned back. It may cause to ambiguous behaviors during cancelation. most of the time, creating one context and passing everywhere should be enough. Once a cancellation made to context or its children contexts, <-Done() listeners of parent context will receive a close signal anyway.

    • [feature request] Open plugin system to community and allow community made plugins to be registered to agent and exposed to WebUI.

  • Switch to go mod for dependency management
    Go mod is the official package manager and works seamlessly.

    • Drop use of Gopkg dep manager.
    • Remove vendor/ folder from project, effectively use GOPROXY.
  • Setup GOPROXY

  • Add CI steps for PRs

    • Add linter check, run tests and more
    • Format existent source code with formatting tools and improve code to satisfy Go linter.
  • Refactor config/ package

    • There are open source config manager packages that parses configurations from multiple sources like env, flags, json files and more.
      Pick and use one of these packages to avoid manually parsing and dealing with configs
      .
    • All configs should be kept inside a 'struct' type. This is simpler compared to defining each config as a separate var.
  • Expose and implement package level interfaces when possible
    Most packages should expose an interface that describes its features and an implementation of that interface.
    This way it is easier to mock dependency packages while UNIT testing a dependent.
    For ex.: config/ pkg can expose and implement

type ConfigService interface { LoadConfigs() (Configs, error) }
  • Refactor constant/ package

    • Avoid manually defining values that used multiple times. Instead create a constant and reuse everywhere to avoid typos at runtime.
    • Consider grouping similar constants by structs instead of defining each of them separately.
  • Errors always should implement the built-in 'error' interface.
    For ex.: returned err in this func should have 'error' type instead of 'interface{}'

  • Separate utilities from the actual business logic
    For ex.: move StartTimeFromContext() to utility package.

    • Create an x package to place all utilities inside. Please see this sample x package for some insights.
    • Refactor and move existent utilities into x packages
    • Create an xaws pkg to move only AWS related logic inside, including any reflections, type discovery and inspections. There are lots of non-Thundra related AWS code spread to packages.
    • Thundra APIs should be abstracted away from AWS related APIs and tied together only inside Wrap().
  • Improve testing

    • Avoid unit testing private declarations(implementation details) and test public APIs (features) instead.
    • Move from legacy testify/assert to testify/require for assertion.
    • Calling AssertExpectations() on mocked tests seems to be forgotten all around the tests. Not having it skips all assertions about calls made to mocks.
    • Avoid creating mocks manually 1, 2 instead generate them with mockery cli. Only create fakers (for ex. a complete in memory dynamodb implementation) by hand. Creating mocks manually is not needed at most of the time.
  • Avoid keeping package level states.
    Keeping pkg level states is not sustainable, error prone, hard to test.
    Instead keep state in struct level, initialize structs with New() and GC them when their job is done.
    1, 2-a, 2-b, 3

  • Avoid using atomic pkg, use sync/mutex or channels instead.
    Atomic package is a low level synchronization primitive to implement high level ones. It is common to misuse atomic package and not friendly in OSS. It's easier for less experienced developers to use mutexes.

  • Follow some of these good practises.

    • Use early returns as possible instead having to use nested if-else. Easier for readability.
    • Prefer switches over else-if, it's very effective in Go.
    • Returning private types from funcs introduces some limitations. Consider making them public.
  • Add more docs

    • Add docs at least for every public declaration.
    • Add package wise docs to clearly describe each package' purpose.
  • Avoid/Reduce using reflection
    Reflection is required to create a lambda wrapper but using it should be avoided everywhere else if possible.

    • For ex.: using reflection to detect 1-error's type here is unnecessary and slow.
      Instead of using reflection, a Type field should be created and used in the implementation of this error. To access the underlying error implementation (struct), use type assertion.
      Using reflection in this case is also error prone since type's value will change if the underlying struct's name changes. Structs and their names should be related to only program itself and shouldn't have any side effects to data.

    • For ex.: 2(use type assertion instead)

  • Refactor more

    • Consider refactoring some package's public APIs
    • Consider refactoring folder/pkg structures
    • Rethink about use of contexts, only use them when necessary.
    • New() func should accept any dependencies instead of initializing them inside. This is a good practice since each instance can be configured by the caller easily. Adopt Option funcs within New() when needed.

Disabling lambda request/response tracing does not work

Looks like

const ThundraDisableTraceRequest = "thundra_agent_lambda_trace_request_disable"
const ThundraDisableTraceResponse = "thundra_agent_lambda_trace_response_disable"

variables are ignored and the lambda request and response are always added to the trace.

Go agent doesn't read the environment variable in Lambda

As suggested creating an issue here.

When I followed the instructions in the docs on Configuration and only supply the environment variable, I got an error like below. When I hardcoded the API key, it worked perfectly

response Body:
{
  "timestamp": 1521857820679,
  "status": 401,
  "error": "Unauthorized",
  "message": "No API key provided",
  "path": "/api/monitor-datas"
}

Wrapping for AWS v2 sessions

Hello, please let me know if the support for AWS v2 Go SDK support is on the roadmap, that would be quite useful.

not enough arguments in call

In my code I have:

func main() {
	// Instantiate Thundra Agent with Trace Support
	t := thundra.NewBuilder().AddPlugin(&trace.Trace{}).SetAPIKey("xxx").Build()

	// Wrap your lambda function with Thundra
	lambda.Start(thundra.Wrap(Handle, t))
}

When I try to build my app, it fails with the error:

vendor/github.com/thundra-io/thundra-lambda-agent-go/trace/trace.go:120:22: not enough arguments in call to uuid.Must
        have (uuid.UUID)
        want (uuid.UUID, error)

Changing line 122 in trace.go from uniqueId = uuid.Must(uuid.NewV4()) to uniqueId = uuid.Must(uuid.NewV4(), nil) fixes the issue and results in a successful build of my app (and also results in data being sent to Thundra). If you want I can contribute a patch 😄

github.com/satori/go.uuid is reported by Snyk as High vulnerable

 Insecure Randomness
Vulnerable module: github.com/satori/go.uuid
Introduced through: github.com/thundra-io/thundra-lambda-agent-go/[email protected]
Exploit maturity: No known exploit
Detailed paths
Introduced through: bitbucket.org/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/satori/[email protected]
Introduced through: bitbucket.org/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/satori/[email protected]
Introduced through: bitbucket.org/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/thundra-io/thundra-lambda-agent-go/[email protected] › github.com/satori/[email protected]
…and 26 more

Overview
github.com/satori/go.uuid provides pure Go implementation of Universally Unique Identifier (UUID).

Affected versions of this package are vulnerable to Insecure Randomness producing predictable UUID identifiers due to the limited number of bytes read when using the g.rand.Read function.

Not able to install package with dep

When trying to install this with dep I get the following error:

Solving failure: No versions of github.com/thundra-io/thundra-lambda-agent-go met constraints:
	v1.3.0: Could not introduce github.com/thundra-io/[email protected], as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)
	v1.2.1: Could not introduce github.com/thundra-io/[email protected], as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)
	v1.2.0: Could not introduce github.com/thundra-io/[email protected], as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)
	v1.1.0: Could not introduce github.com/thundra-io/[email protected], as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)
	master: Could not introduce github.com/thundra-io/thundra-lambda-agent-go@master, as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)
	feature/goveralls: Could not introduce github.com/thundra-io/thundra-lambda-agent-go@feature/goveralls, as its subpackage github.com/thundra-io/thundra-lambda-agent-go does not contain usable Go code (*build.NoGoError).. (Package is required by (root).)

Cannot use v2.3.0 with go mod

Hello, looks like the version needs to be fixed to follow go mod semantic versioning:

Using github.com/thundra-io/thundra-lambda-agent-go v2.3.0

go: errors parsing go.mod:
/Users/ds/work/go/serverless-backend/go.mod:49:2: require github.com/thundra-io/thundra-lambda-agent-go: version "v2.3.0" invalid: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

Using github.com/thundra-io/thundra-lambda-agent-go v2.3.0+incompatible

go: github.com/thundra-io/[email protected]+incompatible/go.mod: verifying module: github.com/thundra-io/[email protected]+incompatible/go.mod: reading https://sum.golang.org/lookup/github.com/thundra-io/[email protected]+incompatible: 410 Gone
	server response: not found: github.com/thundra-io/[email protected]+incompatible: invalid version: +incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.