Lambda Go Agent
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:userpass@docker.for.mac.localhost: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
- 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
- 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
- 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>
- Configure your sls
sls
- 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
- Trigger the lambda endpoint and monitor your sample app in apm.thundra.io !