diff --git a/README.md b/README.md index adcadb7..0793d68 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,41 @@ func main() { } ``` +For tracing AWS SDK v2.0 calls check the following example: + +```go + client := &http.Client{ + Transport: lumigotracer.NewTransport(http.DefaultTransport), + } + + // for AWS SDK v1.x + sess := session.Must(session.NewSession(&aws.Config{ + HTTPClient: client, + })) + + svc := s3.New(sess) + + // for AWS SDK v2.x + cfg, _ := config.LoadDefaultConfig(context.Background(), config.WithHTTPClient(client)) + svc := s3.NewFromConfig(cfg) + +``` + +For tracing HTTP calls check the following example: + +```go + client := &http.Client{ + Transport: lumigotracer.NewTransport(http.DefaultTransport), + } + req, _ := http.NewRequest("GET", "https://", nil) + + // for net/http + res, err := client.Do(req) + + // for golang.org/x/net/context/ctxhttp + res, err := ctxhttp.Do(context.Background(), client, req) +``` + In your lambda environment variables you need to set `LUMIGO_USE_TRACER_EXTENSION: true` and use the following layer for `us-east-1`: `arn:aws:lambda:us-east-1:114300393969:layer:lumigo-tracer-extension:36`. The layer will be available in more regions soon. ## Contributing diff --git a/_example/lambda.go b/_example/lambda.go index 6d49836..250a146 100644 --- a/_example/lambda.go +++ b/_example/lambda.go @@ -4,11 +4,16 @@ import ( "context" "errors" "fmt" + "net/http" "os" "strconv" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/aws/aws-sdk-go/aws" lumigotracer "github.com/lumigo-io/go-tracer-beta" ) @@ -17,7 +22,33 @@ type MyEvent struct { } func HandleRequest(ctx context.Context, name MyEvent) (events.APIGatewayProxyResponse, error) { + client := &http.Client{ + Transport: lumigotracer.NewTransport(http.DefaultTransport), + } + cfg, _ := config.LoadDefaultConfig(context.Background(), config.WithHTTPClient(client)) + cfg.Region = "us-east-1" + // testing S3 + svc := s3.NewFromConfig(cfg) + _, err := svc.ListBuckets(context.Background(), &s3.ListBucketsInput{}) + if err != nil { + return events.APIGatewayProxyResponse{Body: "", StatusCode: 500}, err + } + + svc.CreateBucket(context.Background(), &s3.CreateBucketInput{ + Bucket: aws.String("test-bucket-go-tracer-2"), + }) + + // testing SSM + ssmClient := ssm.NewFromConfig(cfg) + input := &ssm.GetParameterInput{ + Name: aws.String("parameter-name"), + } + _, err = ssmClient.GetParameter(context.Background(), input) + if err != nil { + return events.APIGatewayProxyResponse{Body: "ssm error", StatusCode: 500}, err + } response := fmt.Sprintf("Hello %s!", name.Name) + returnErr, ok := os.LookupEnv("RETURN_ERROR") if !ok { return events.APIGatewayProxyResponse{Body: response, StatusCode: 200}, nil @@ -26,9 +57,11 @@ func HandleRequest(ctx context.Context, name MyEvent) (events.APIGatewayProxyRes if err != nil { return events.APIGatewayProxyResponse{Body: response, StatusCode: 500}, err } + // testing return error if isReturnErr { return events.APIGatewayProxyResponse{Body: response, StatusCode: 500}, errors.New("failed error") } + // testing return return events.APIGatewayProxyResponse{Body: response, StatusCode: 200}, nil } diff --git a/_example/terraform/lambda.tf b/_example/terraform/lambda.tf index 936770f..ba44171 100644 --- a/_example/terraform/lambda.tf +++ b/_example/terraform/lambda.tf @@ -11,7 +11,7 @@ resource "aws_lambda_function" "otel" { role = aws_iam_role.lambda_exec.arn - layers = ["arn:aws:lambda:us-east-1:114300393969:layer:lumigo-tracer-extension:33"] + layers = ["arn:aws:lambda:us-east-1:114300393969:layer:lumigo-tracer-extension:37"] environment { variables = { @@ -59,7 +59,14 @@ resource "aws_iam_role_policy" "lambda_s3_read" { "Statement": [ { "Action": [ - "s3:ListAllMyBuckets" + "s3:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ssm:*" ], "Effect": "Allow", "Resource": "*" diff --git a/extensions.go b/extensions.go deleted file mode 100644 index 0eae190..0000000 --- a/extensions.go +++ /dev/null @@ -1,37 +0,0 @@ -package lumigotracer - -import ( - "context" - "net/http" - - "github.com/aws/aws-sdk-go-v2/aws" - awsConfig "github.com/aws/aws-sdk-go-v2/config" - "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel" -) - -// NewTransport creates an HTTP transport -func NewTransport(transport http.RoundTripper) *otelhttp.Transport { - return otelhttp.NewTransport( - transport, - otelhttp.WithTracerProvider(otel.GetTracerProvider()), - ) -} - -// LoadAWSConfig returns an AWS config and we wrapped alredy the AWS -// clients with OpenTelemetry -func LoadAWSConfig(ctx context.Context, optFns ...func(*awsConfig.LoadOptions) error) (cfg aws.Config, err error) { - cfg, err = awsConfig.LoadDefaultConfig(ctx, optFns...) - if err != nil { - return aws.Config{}, err - } - otelaws.AppendMiddlewares(&cfg.APIOptions, otelaws.WithTracerProvider(otel.GetTracerProvider())) - return cfg, err -} - -// TraceAWSClients adds the middlewares for AWS Client -// with OpenTelemetry -func TraceAWSClients(cfg *aws.Config) { - otelaws.AppendMiddlewares(&cfg.APIOptions, otelaws.WithTracerProvider(otel.GetTracerProvider())) -} diff --git a/go.mod b/go.mod index 874cedb..3b95ebe 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,7 @@ go 1.17 require ( github.com/aws/aws-lambda-go v1.27.0 - github.com/aws/aws-sdk-go v1.42.23 github.com/aws/aws-sdk-go-v2 v1.16.1 - github.com/aws/aws-sdk-go-v2/config v1.10.2 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 github.com/segmentio/ksuid v1.0.4 @@ -16,37 +14,20 @@ require ( github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 go.opentelemetry.io/contrib/detectors/aws/lambda v0.27.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.27.0 - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.27.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.27.0 go.opentelemetry.io/otel v1.3.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0 go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/trace v1.3.0 + golang.org/x/net v0.0.0-20220325170049-de3da57026de ) require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.6.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.6.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.10.1 // indirect github.com/aws/smithy-go v1.11.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.2 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-logr/logr v1.2.1 // indirect github.com/go-logr/stdr v1.2.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/pelletier/go-toml v1.9.4 // indirect @@ -56,9 +37,6 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect - go.opentelemetry.io/otel/internal/metric v0.25.0 // indirect - go.opentelemetry.io/otel/metric v0.25.0 // indirect - golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/ini.v1 v1.63.2 // indirect diff --git a/go.sum b/go.sum index 081eac1..4b65152 100644 --- a/go.sum +++ b/go.sum @@ -52,51 +52,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-lambda-go v1.27.0 h1:aLzrJwdyHoF1A18YeVdJjX8Ixkd+bpogdxVInvHcWjM= github.com/aws/aws-lambda-go v1.27.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/aws/aws-sdk-go v1.42.23 h1:V0V5hqMEyVelgpu1e4gMPVCJ+KhmscdNxP/NWP1iCOA= -github.com/aws/aws-sdk-go v1.42.23/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs= -github.com/aws/aws-sdk-go-v2 v1.11.0/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= -github.com/aws/aws-sdk-go-v2 v1.11.1 h1:GzvOVAdTbWxhEMRK4FfiblkGverOkAT0UodDxC1jHQM= -github.com/aws/aws-sdk-go-v2 v1.11.1/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= github.com/aws/aws-sdk-go-v2 v1.16.1 h1:udzee98w8H6ikRgtFdVN9JzzYEbi/quFfSvduZETJIU= github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= -github.com/aws/aws-sdk-go-v2/config v1.10.2 h1:lrNnqRpPDgrozyKMnt5/Bhcv01kel7JO6KFx4VdroCY= -github.com/aws/aws-sdk-go-v2/config v1.10.2/go.mod h1:OY1jfuHozx6GDg+NITKNukVQi4fLlnenu1PAbDJg5fk= -github.com/aws/aws-sdk-go-v2/credentials v1.6.2 h1:2faRNX8JgZVy7dDxERkaGBqb/xo5Rgmc8JMPL5j1o58= -github.com/aws/aws-sdk-go-v2/credentials v1.6.2/go.mod h1:8kRH9fthlxHEeNJ3g1N3NTSUMBba+KtTM8hp6SvUWn8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.1 h1:pXwGBINU30CsjYztV/IyCgA7QKp99Q8wM4Gb0Ls3rB0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.1/go.mod h1:MYiG3oeEcmrdBOV7JOIWhionzyRZJWCnByS5FmvhAoU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1 h1:LZwqhOyqQ2w64PZk04V0Om9AEExtW8WMkCRoE1h9/94= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1/go.mod h1:22SEiBSQm5AyKEjoPcG1hzpeTI+m9CXfE6yt1h49wBE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 h1:CDaO90VZVBAL1sK87S5oSPIrp7yZqORv1hPIi2UsTMk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 h1:ObMfGNk0xjOWduPxsrRWVwZZia3e9fOcO6zlKCkt38s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1/go.mod h1:1xvCD+I5BcDuQUc+psZr7LI1a9pclAWZs3S3Gce5+lg= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 h1:XXR3cdOcKRCTZf6ctcqpMf+go1BdzTm6+T9Ul5zxcMI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.1 h1:fdQSN/ieDwbxdj7ptvFKjS2cS2a91l/WdjacCt5GgTE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.1/go.mod h1:5eEM4wZ6I2GaeOaVXsiJexIH4P1sFnK5Yp2Tlw9Ah3c= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 h1:VoMBHtQZygRs8mcQNDrfmn09vFH2ccjf79nGJ0xuUfo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2/go.mod h1:2Fzbfwkx7z4yue1Lz6KDSKG84UpOcUKFl3VAtSF/gcg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.1 h1:ZFSfgetO5kf4WXy+a2B8zug6DXGUYjsWacyvwx5cgXU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.1/go.mod h1:fEaHB2bi+wVZw4uKMHEXTL9LwtT4EL//DOhTeflqIVo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 h1:RrN7V0r8+lUUKZM4OAoCOIZqjPLZPOl6wuwMd2QIryI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2/go.mod h1:7hwSi01X5Yj9H0qLQljrn8OSdLwwSym1aQCfGn1tDQQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 h1:yxr9h06slG9fdVmO3CpBVuFVD73AeUHLmBxhCr3T3+E= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2/go.mod h1:rvV/Jr4T8H3kMMw/9fFQw9kxqb70YKihA0oWuUFd3K8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 h1:Op/A+5+D1K0bmwH3BStYbp/7iod9Rdfm9898A0qYxLc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2/go.mod h1:Ao1W746VIMdV1WhEkjeVa5JzlaE1JkxJ46facHX9kzs= -github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0 h1:p22U2yL/AeRToERGcZv1R26Yci5VQnWIrpzcZdG54cg= -github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0/go.mod h1:chcyLYBEVRac/7rWJsD6cUHUR2osROwavvNqCplfwog= -github.com/aws/aws-sdk-go-v2/service/sso v1.6.1 h1:NF/qN6e8hdHO/Pt5jN+S65dxFom3b8+ciVdyv8Jr00U= -github.com/aws/aws-sdk-go-v2/service/sso v1.6.1/go.mod h1:/73aFBwUl60wKBKhdth2pEOkut5ZNjVHGF9hjXz0bM0= -github.com/aws/aws-sdk-go-v2/service/sts v1.10.1 h1:2DKYFOmC7d3WOzdBTFJxfkcMXVVIgcitrpEoJDUKlN4= -github.com/aws/aws-sdk-go-v2/service/sts v1.10.1/go.mod h1:+BmlPeQ1Y+PuIho93MMKDby12PoUnt1SZXQdEHCzSlw= -github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= -github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -127,8 +84,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= -github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -186,8 +141,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -240,7 +195,6 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -346,19 +300,11 @@ go.opentelemetry.io/contrib/detectors/aws/lambda v0.27.0 h1:Ffihu66TCqspMDIUqrKl go.opentelemetry.io/contrib/detectors/aws/lambda v0.27.0/go.mod h1:im9CylM2nOrbRstzBA2rOUembz2cKRuvlXQeQlaah4k= go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.27.0 h1:ZvjcV95tBi3/fisx4wb7nGWONQxX44Fg7sczEjU84Q4= go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.27.0/go.mod h1:B6tus/5ASA1N/D+0AIxvLkQMH3LpfZYjpKgtBWzxf1Q= -go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.27.0 h1:BVDJ2kZQqnq6m9XAqVo/CEaKLQR/RG0mloQXVfMvJLU= -go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.27.0/go.mod h1:fyocvbTP3XhTebpOJqVfL3L4Vfhhn1T9rUodZuXH1hk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.27.0 h1:0BgiNWjN7rUWO9HdjF4L12r8OW86QkVQcYmCjnayJLo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.27.0/go.mod h1:bdvm3YpMxWAgEfQhtTBaVR8ceXPRuRBSQrvOBnIlHxc= go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0 h1:OiYdrCq1Ctwnovp6EofSPwlp5aGy4LgKNbkg7PtEUw8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0/go.mod h1:DUFCmFkXr0VtAHl5Zq2JRx24G6ze5CAq8YfdD36RdX8= -go.opentelemetry.io/otel/internal/metric v0.25.0 h1:w/7RXe16WdPylaIXDgcYM6t/q0K5lXgSdZOEbIEyliE= -go.opentelemetry.io/otel/internal/metric v0.25.0/go.mod h1:Nhuw26QSX7d6n4duoqAFi5KOQR4AuzyMcl5eXOgwxtc= -go.opentelemetry.io/otel/metric v0.25.0 h1:7cXOnCADUsR3+EOqxPaSKwhEuNu0gz/56dRN1hpIdKw= -go.opentelemetry.io/otel/metric v0.25.0/go.mod h1:E884FSpQfnJOMMUaq+05IWlJ4rjZpk2s/F1Ju+TEEm8= go.opentelemetry.io/otel/sdk v1.2.0/go.mod h1:jNN8QtpvbsKhgaC6V5lHiejMoKD+V8uadoSafgHPx1U= go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= @@ -450,7 +396,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -535,11 +480,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -547,7 +492,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -611,7 +555,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/internal/telemetry/span.go b/internal/telemetry/span.go index cfa1170..282a82d 100644 --- a/internal/telemetry/span.go +++ b/internal/telemetry/span.go @@ -23,6 +23,24 @@ type SpanInfo struct { LogGroupName string `json:"logGroupName"` TraceID SpanTraceRoot `json:"traceId"` TracerVersion TracerVersion `json:"tracer"` + HttpInfo *SpanHttpInfo `json:"httpInfo,omitempty"` +} + +// SpanHttpInfo extra info for HTTP reuquests +type SpanHttpInfo struct { + Host string `json:"host"` + Request SpanHttpCommon `json:"request"` + Response SpanHttpCommon `json:"response"` +} + +// SpanHttpRequest the span for the HTTP request +type SpanHttpCommon struct { + URI *string `json:"uri,omitempty"` + Method *string `json:"method,omitempty"` + StatusCode *int64 `json:"statusCode,omitempty"` + InstanceID *string `json:"instance_id,omitempty"` + Body string `json:"body,omitempty"` + Headers string `json:"headers,omitempty"` } // SpanError the extra info if lambda returned @@ -108,5 +126,5 @@ type Span struct { } func IsStartSpan(span sdktrace.ReadOnlySpan) bool { - return span.Name() == os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + return span.Name() == os.Getenv("AWS_LAMBDA_FUNCTION_NAME") || span.Name() == "HttpSpan" } diff --git a/internal/transform/span.go b/internal/transform/span.go index c90c104..133cf18 100644 --- a/internal/transform/span.go +++ b/internal/transform/span.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-lambda-go/lambdacontext" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/google/uuid" lumigoctx "github.com/lumigo-io/go-tracer-beta/internal/context" "github.com/lumigo-io/go-tracer-beta/internal/telemetry" @@ -50,6 +50,7 @@ func (m *mapper) Transform() telemetry.Span { } for _, kv := range m.span.Attributes() { attrs[string(kv.Key)] = kv.Value.AsInterface() + m.logger.WithField(string(kv.Key), kv.Value.AsInterface()).Info() } m.logger.WithFields(attrs).Info("span attributes") @@ -64,11 +65,41 @@ func (m *mapper) Transform() telemetry.Span { } isStartSpan := telemetry.IsStartSpan(m.span) + lumigoSpan.Region = os.Getenv("AWS_REGION") + lumigoSpan.MemoryAllocated = os.Getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") + lumigoSpan.Runtime = os.Getenv("AWS_EXECUTION_ENV") + lumigoSpan.LambdaName = os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + + awsRoot := getAmazonTraceID() + if awsRoot == "" { + m.logger.Error("unable to fetch Amazon Trace ID") + } + lumigoSpan.SpanInfo = telemetry.SpanInfo{ + LogStreamName: os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME"), + LogGroupName: os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME"), + TraceID: telemetry.SpanTraceRoot{ + Root: awsRoot, + }, + } + + lambdaType := "function" + if m.span.Name() != lumigoSpan.LambdaName && m.span.Name() != "LumigoParentSpan" { + lambdaType = "http" + lumigoSpan.SpanInfo.HttpInfo = m.getHTTPInfo(attrs) + } + lumigoSpan.LambdaType = lambdaType + lambdaCtx, lambdaOk := lambdacontext.FromContext(m.ctx) if lambdaOk { - uuid, _ := uuid.NewUUID() - lumigoSpan.LambdaContainerID = uuid.String() - lumigoSpan.ID = lambdaCtx.AwsRequestID + containerID, _ := uuid.NewUUID() + lumigoSpan.LambdaContainerID = containerID.String() + + if lambdaType == "http" { + spanID, _ := uuid.NewUUID() + lumigoSpan.ID = spanID.String() + } else { + lumigoSpan.ID = lambdaCtx.AwsRequestID + } if isStartSpan { lumigoSpan.ID = fmt.Sprintf("%s_started", lumigoSpan.ID) @@ -104,23 +135,6 @@ func (m *mapper) Transform() telemetry.Span { m.logger.Error("unable to fetch lambda response from span") } - lumigoSpan.Region = os.Getenv("AWS_REGION") - lumigoSpan.MemoryAllocated = os.Getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") - lumigoSpan.Runtime = os.Getenv("AWS_EXECUTION_ENV") - lumigoSpan.LambdaName = os.Getenv("AWS_LAMBDA_FUNCTION_NAME") - - awsRoot := getAmazonTraceID() - if awsRoot == "" { - m.logger.Error("unable to fetch Amazon Trace ID") - } - lumigoSpan.SpanInfo = telemetry.SpanInfo{ - LogStreamName: os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME"), - LogGroupName: os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME"), - TraceID: telemetry.SpanTraceRoot{ - Root: awsRoot, - }, - } - if transactionID := getTransactionID(awsRoot); transactionID != "" { lumigoSpan.TransactionID = transactionID } else { @@ -143,12 +157,6 @@ func (m *mapper) Transform() telemetry.Span { lumigoSpan.LambdaReadiness = "warm" } - lambdaType := "function" - if m.span.Name() != lumigoSpan.LambdaName && m.span.Name() != "LumigoParentSpan" { - lambdaType = "http" - } - lumigoSpan.LambdaType = lambdaType - if !isStartSpan { lumigoSpan.SpanError = m.getSpanError(attrs) } @@ -156,35 +164,6 @@ func (m *mapper) Transform() telemetry.Span { return lumigoSpan } -func isProvisionConcurrencyInitialization() bool { - return os.Getenv("AWS_LAMBDA_INITIALIZATION_TYPE") == "provisioned-concurrency" -} - -func getAccountID(ctx *lambdacontext.LambdaContext) (string, error) { - functionARN, err := arn.Parse(ctx.InvokedFunctionArn) - if err != nil { - return "", errors.Wrap(err, "failed to parse ARN") - } - return functionARN.AccountID, nil -} - -func getAmazonTraceID() string { - awsTraceItems := strings.SplitN(os.Getenv("_X_AMZN_TRACE_ID"), ";", 2) - if len(awsTraceItems) > 1 { - root := strings.SplitN(awsTraceItems[0], "=", 2) - return root[1] - } - return "" -} - -func getTransactionID(root string) string { - items := strings.SplitN(root, "-", 3) - if len(items) > 1 { - return items[2] - } - return "" -} - func (m *mapper) getSpanError(attrs map[string]interface{}) *telemetry.SpanError { if _, ok := attrs["has_error"]; !ok { return nil @@ -225,3 +204,87 @@ func (m *mapper) getEnvVars() string { } return string(envsString) } + +func (m *mapper) getHTTPInfo(attrs map[string]interface{}) *telemetry.SpanHttpInfo { + var spanHttpInfo telemetry.SpanHttpInfo + if host, ok := attrs["http.host"]; ok { + spanHttpInfo.Host = fmt.Sprint(host) + } else { + m.logger.Error("unable to fetch HTTP host") + } + + if method, ok := attrs["http.method"]; ok { + spanHttpInfo.Request.Method = aws.String(fmt.Sprint(method)) + } else { + m.logger.Error("unable to fetch HTTP method") + } + + if target, ok := attrs["http.target"]; ok { + uri := fmt.Sprintf("%s%s", spanHttpInfo.Host, target) + spanHttpInfo.Request.URI = aws.String(uri) + } else { + m.logger.Error("unable to fetch HTTP target") + } + + if headers, ok := attrs["http.request_headers"]; ok { + spanHttpInfo.Request.Headers = fmt.Sprint(headers) + } else { + m.logger.Error("unable to fetch HTTP request headers") + } + + if reqBody, ok := attrs["http.request_body"]; ok { + spanHttpInfo.Request.Body = fmt.Sprint(reqBody) + } else { + m.logger.Error("unable to fetch HTTP request body") + } + + if headers, ok := attrs["http.response_headers"]; ok { + spanHttpInfo.Response.Headers = fmt.Sprint(headers) + } else { + m.logger.Error("unable to fetch HTTP response headers") + } + + // response + if respBody, ok := attrs["http.response_body"]; ok { + spanHttpInfo.Response.Body = fmt.Sprint(respBody) + } else { + m.logger.Error("unable to fetch HTTP response body") + } + + if code, ok := attrs["http.status_code"]; ok { + spanHttpInfo.Response.StatusCode = aws.Int64(code.(int64)) + } else { + m.logger.Error("unable to fetch HTTP status code") + } + + return &spanHttpInfo +} + +func isProvisionConcurrencyInitialization() bool { + return os.Getenv("AWS_LAMBDA_INITIALIZATION_TYPE") == "provisioned-concurrency" +} + +func getAccountID(ctx *lambdacontext.LambdaContext) (string, error) { + functionARN, err := arn.Parse(ctx.InvokedFunctionArn) + if err != nil { + return "", errors.Wrap(err, "failed to parse ARN") + } + return functionARN.AccountID, nil +} + +func getAmazonTraceID() string { + awsTraceItems := strings.SplitN(os.Getenv("_X_AMZN_TRACE_ID"), ";", 2) + if len(awsTraceItems) > 1 { + root := strings.SplitN(awsTraceItems[0], "=", 2) + return root[1] + } + return "" +} + +func getTransactionID(root string) string { + items := strings.SplitN(root, "-", 3) + if len(items) > 1 { + return items[2] + } + return "" +} diff --git a/internal/transform/span_test.go b/internal/transform/span_test.go index 8c29b7c..c3df599 100644 --- a/internal/transform/span_test.go +++ b/internal/transform/span_test.go @@ -238,7 +238,7 @@ func TestTransform(t *testing.T) { }, }, { - testname: "span from S3 or HTTP is type http", + testname: "span with error", input: &tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, @@ -246,22 +246,30 @@ func TestTransform(t *testing.T) { }), StartTime: now, EndTime: now.Add(1 * time.Second), - Name: "S3 HTTP", + Name: "LumigoParentSpan", Attributes: []attribute.KeyValue{ attribute.String("event", "test"), - attribute.String("response", "test2"), + attribute.Bool("has_error", true), + attribute.String("error_type", "TestError"), + attribute.String("error_message", "failed error"), + attribute.String("error_stacktrace", "failed error"), }, }, expect: telemetry.Span{ LambdaName: "test", - LambdaType: "http", + LambdaType: "function", LambdaReadiness: "warm", - LambdaResponse: aws.String("test2"), + LambdaResponse: nil, Event: "test", Account: "account-id", ID: mockLambdaContext.AwsRequestID, StartedTimestamp: now.UnixMilli(), EndedTimestamp: now.Add(1 * time.Second).UnixMilli(), + SpanError: &telemetry.SpanError{ + Type: "TestError", + Message: "failed error", + Stacktrace: "failed error", + }, }, before: func() { os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "test") @@ -273,7 +281,7 @@ func TestTransform(t *testing.T) { }, }, { - testname: "span with error", + testname: "span http success", input: &tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, @@ -281,18 +289,22 @@ func TestTransform(t *testing.T) { }), StartTime: now, EndTime: now.Add(1 * time.Second), - Name: "LumigoParentSpan", + Name: "HttpSpan", Attributes: []attribute.KeyValue{ + attribute.String("http.host", "s3.aws.com"), + attribute.String("http.target", "/"), + attribute.String("http.method", "POST"), + attribute.Int64("http.status_code", 200), + attribute.String("http.request_headers", `{"Agent": "test"}`), + attribute.String("http.request_body", `{"name": "test"}`), + attribute.String("http.response_headers", `{"Agent": "test"}`), + attribute.String("http.response_body", `{"response": "test"}`), attribute.String("event", "test"), - attribute.Bool("has_error", true), - attribute.String("error_type", "TestError"), - attribute.String("error_message", "failed error"), - attribute.String("error_stacktrace", "failed error"), }, }, expect: telemetry.Span{ LambdaName: "test", - LambdaType: "function", + LambdaType: "http", LambdaReadiness: "warm", LambdaResponse: nil, Event: "test", @@ -300,10 +312,21 @@ func TestTransform(t *testing.T) { ID: mockLambdaContext.AwsRequestID, StartedTimestamp: now.UnixMilli(), EndedTimestamp: now.Add(1 * time.Second).UnixMilli(), - SpanError: &telemetry.SpanError{ - Type: "TestError", - Message: "failed error", - Stacktrace: "failed error", + SpanInfo: telemetry.SpanInfo{ + HttpInfo: &telemetry.SpanHttpInfo{ + Host: "s3.aws.com", + Request: telemetry.SpanHttpCommon{ + URI: aws.String("s3.aws.com/"), + Method: aws.String("POST"), + Headers: `{"Agent": "test"}`, + Body: `{"name": "test"}`, + }, + Response: telemetry.SpanHttpCommon{ + StatusCode: aws.Int64(200), + Headers: `{"Agent": "test"}`, + Body: `{"response": "test"}`, + }, + }, }, }, before: func() { @@ -327,6 +350,9 @@ func TestTransform(t *testing.T) { lumigoSpan.LambdaContainerID = "" // intentionally ignore MaxFinishTime, cannot be matched lumigoSpan.MaxFinishTime = 0 + if lumigoSpan.LambdaType == "http" { + lumigoSpan.ID = mockLambdaContext.AwsRequestID + } if !reflect.DeepEqual(lumigoSpan, tc.expect) { t.Errorf("%s: %#v != %#v", tc.testname, lumigoSpan, tc.expect) } diff --git a/tracer.go b/tracer.go index 302a578..6d7ced9 100644 --- a/tracer.go +++ b/tracer.go @@ -10,6 +10,7 @@ import ( "github.com/sirupsen/logrus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) @@ -49,6 +50,8 @@ func NewTracer(ctx context.Context, cfg Config, payload json.RawMessage) (retTra ) retTracer.provider = tracerProvider otel.SetTracerProvider(tracerProvider) + otel.SetTextMapPropagator(propagation.TraceContext{}) + return retTracer, nil } diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..618aef5 --- /dev/null +++ b/transport.go @@ -0,0 +1,120 @@ +package lumigotracer + +import ( + "bytes" + "context" + "encoding/json" + "io" + "io/ioutil" + "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "go.opentelemetry.io/otel/trace" +) + +type Transport struct { + rt http.RoundTripper + provider trace.TracerProvider + propagator propagation.TextMapPropagator +} + +func NewTransport(transport http.RoundTripper) *Transport { + return &Transport{ + rt: transport, + provider: otel.GetTracerProvider(), + propagator: otel.GetTextMapPropagator(), + } +} + +func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { + traceCtx, span := t.provider.Tracer("lumigo").Start(req.Context(), "HttpSpan") + + req = req.WithContext(traceCtx) + span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(req)...) + span.SetAttributes(semconv.HTTPTargetKey.String(req.URL.Path)) + span.SetAttributes(semconv.HTTPHostKey.String(req.URL.Host)) + t.propagator.Inject(traceCtx, propagation.HeaderCarrier(req.Header)) + + if req.Body != nil { + bodyBytes, bodyErr := io.ReadAll(req.Body) + if bodyErr != nil { + logger.WithError(bodyErr).Error("failed to parse request body") + } + span.SetAttributes(attribute.String("http.request_body", string(bodyBytes))) + // restore body + req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + } + + reqHeaders := make(map[string]string) + for k, values := range req.Header { + for _, value := range values { + reqHeaders[k] = value + } + } + headersJson, err := json.Marshal(reqHeaders) + if err != nil { + logger.WithError(err).Error("failed to fetch request headers") + } + span.SetAttributes(attribute.String("http.request_headers", string(headersJson))) + + resp, err = t.rt.RoundTrip(req) + + // response + span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...) + span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode)) + + responseHeaders := make(map[string]string) + for k, values := range resp.Header { + for _, value := range values { + responseHeaders[k] = value + } + } + headersJson, jsonErr := json.Marshal(responseHeaders) + if jsonErr != nil { + logger.WithError(err).Error("failed to fetch response headers") + } + span.SetAttributes(attribute.String("http.response_headers", string(headersJson))) + + if resp.Body != nil { + bodyBytes, bodyErr := io.ReadAll(resp.Body) + if bodyErr != nil { + logger.WithError(bodyErr).Error("failed to parse response body") + } + span.SetAttributes(attribute.String("http.response_body", string(bodyBytes))) + resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + } + resp.Body = &wrappedBody{ctx: traceCtx, span: span, body: resp.Body} + return resp, err +} + +type wrappedBody struct { + ctx context.Context + span trace.Span + body io.ReadCloser +} + +var _ io.ReadCloser = &wrappedBody{} + +func (wb *wrappedBody) Read(b []byte) (int, error) { + n, err := wb.body.Read(b) + + switch err { + case nil: + // nothing to do here but fall through to the return + case io.EOF: + wb.span.End() + default: + wb.span.RecordError(err) + wb.span.SetStatus(codes.Error, err.Error()) + } + return n, err +} + +func (wb *wrappedBody) Close() error { + wb.span.End() + return wb.body.Close() +} diff --git a/transport_test.go b/transport_test.go new file mode 100644 index 0000000..a711852 --- /dev/null +++ b/transport_test.go @@ -0,0 +1,141 @@ +package lumigotracer + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +func TestTransport(t *testing.T) { + content := []byte("Hello, world!") + + ctx := context.Background() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if _, err := w.Write(content); err != nil { + t.Fatal(err) + } + })) + defer ts.Close() + + r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) + if err != nil { + t.Fatal(err) + } + + testcases := []struct { + testname string + transport http.RoundTripper + }{ + { + testname: "http default transport", + transport: http.DefaultTransport, + }, + { + testname: "http default transport", + transport: nil, + }, + } + + for _, tc := range testcases { + t.Run(tc.testname, func(t *testing.T) { + tr := NewTransport(http.DefaultTransport) + + c := http.Client{Transport: tr} + res, err := c.Do(r) + if err != nil { + t.Fatal(err) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(body, content) { + t.Fatalf("unexpected content: got %s, expected %s", body, content) + } + }) + } +} + +const readSize = 42 + +type readCloser struct { + readErr, closeErr error +} + +func (rc readCloser) Read(p []byte) (n int, err error) { + return readSize, rc.readErr +} +func (rc readCloser) Close() error { + return rc.closeErr +} + +type span struct { + trace.Span + + ended bool + recordedErr error + + statusCode codes.Code + statusDesc string +} + +func (s *span) End(...trace.SpanEndOption) { + s.ended = true +} + +func (s *span) RecordError(err error, _ ...trace.EventOption) { + s.recordedErr = err +} + +func (s *span) SetStatus(c codes.Code, d string) { + s.statusCode, s.statusDesc = c, d +} + +func TestWrappedBodyRead(t *testing.T) { + s := new(span) + wb := &wrappedBody{span: trace.Span(s), body: readCloser{}} + n, err := wb.Read([]byte{}) + assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") + assert.NoError(t, err) +} + +func TestWrappedBodyReadEOFError(t *testing.T) { + s := new(span) + wb := &wrappedBody{span: trace.Span(s), body: readCloser{readErr: io.EOF}} + n, err := wb.Read([]byte{}) + assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") + assert.Equal(t, io.EOF, err) +} + +func TestWrappedBodyReadError(t *testing.T) { + s := new(span) + expectedErr := errors.New("test") + wb := &wrappedBody{span: trace.Span(s), body: readCloser{readErr: expectedErr}} + n, err := wb.Read([]byte{}) + assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") + assert.Equal(t, expectedErr, err) +} + +func TestWrappedBodyClose(t *testing.T) { + s := new(span) + wb := &wrappedBody{span: trace.Span(s), body: readCloser{}} + assert.NoError(t, wb.Close()) +} + +func TestWrappedBodyCloseError(t *testing.T) { + s := new(span) + expectedErr := errors.New("test") + wb := &wrappedBody{span: trace.Span(s), body: readCloser{closeErr: expectedErr}} + assert.Equal(t, expectedErr, wb.Close()) +} diff --git a/wrapper.go b/wrapper.go index a479a41..18a4dfa 100644 --- a/wrapper.go +++ b/wrapper.go @@ -7,7 +7,6 @@ import ( "os" "github.com/aws/aws-lambda-go/lambda" - "github.com/aws/aws-sdk-go-v2/aws" lumigoctx "github.com/lumigo-io/go-tracer-beta/internal/context" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -70,12 +69,6 @@ func WrapHandler(handler interface{}, conf *Config) interface{} { } } -// WrapHandlerWithAWSConfig wraps the lambda handler passing AWS Config -func WrapHandlerWithAWSConfig(handler interface{}, cfg *Config, awsConfig *aws.Config) interface{} { - TraceAWSClients(awsConfig) - return WrapHandler(handler, cfg) -} - // newResource returns a resource describing this application. func newResource(ctx context.Context, extraAttrs ...attribute.KeyValue) *resource.Resource { attrs := []attribute.KeyValue{ diff --git a/wrapper_test.go b/wrapper_test.go index 654b29b..5c1089c 100644 --- a/wrapper_test.go +++ b/wrapper_test.go @@ -1,10 +1,14 @@ package lumigotracer import ( + "bytes" "context" "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" "os" "reflect" "testing" @@ -13,6 +17,7 @@ import ( "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "golang.org/x/net/context/ctxhttp" ) var ( @@ -181,11 +186,19 @@ func (w *wrapperTestSuite) TestLambdaHandlerE2ELocal() { hello := func(s string) string { return fmt.Sprintf("Hello %s!", s) } + content := []byte("Hello, world!") + ts := httptest.NewServer(http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) { + if _, err := wr.Write(content); err != nil { + w.T().Fatal(err) + } + })) testCases := []struct { - name string - input interface{} - expected expected - handler interface{} + name string + lambdaType string + isHttp bool + input interface{} + expected expected + handler interface{} }{ { name: "input: string, with context", @@ -211,6 +224,36 @@ func (w *wrapperTestSuite) TestLambdaHandlerE2ELocal() { return nil, errors.New("failed error") }, }, + { + name: "ctxhttp transport", + input: "test", + isHttp: true, + expected: expected{`"Hello test!"`, nil}, + handler: func(ctx context.Context, name string) (string, error) { + postBody, _ := json.Marshal(map[string]string{ + "name": "test", + }) + r, err := http.NewRequestWithContext(ctx, http.MethodPost, ts.URL, bytes.NewBuffer(postBody)) + if err != nil { + w.T().Fatal(err) + } + r.Header.Set("Agent", "test") + c := &http.Client{Transport: NewTransport(http.DefaultTransport)} + ctxhttp.Do(context.Background(), c, r) // nolint + + res, err := ctxhttp.Do(ctx, c, r) + if err != nil { + w.T().Fatal(err) + } + + _, err = ioutil.ReadAll(res.Body) + if err != nil { + w.T().Fatal(err) + } + + return hello(name), nil + }, + }, } testContext := lambdacontext.NewContext(mockContext, &mockLambdaContext) for i, testCase := range testCases { @@ -229,7 +272,6 @@ func (w *wrapperTestSuite) TestLambdaHandlerE2ELocal() { assert.Equal(w.T(), "account-id", lumigoStart.Account) assert.Equal(w.T(), "token", lumigoStart.Token) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_FUNCTION_NAME"), lumigoStart.LambdaName) - assert.Equal(w.T(), "function", lumigoStart.LambdaType) assert.Equal(w.T(), "go", lumigoStart.Runtime) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME"), lumigoStart.SpanInfo.LogStreamName) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME"), lumigoStart.SpanInfo.LogGroupName) @@ -238,12 +280,19 @@ func (w *wrapperTestSuite) TestLambdaHandlerE2ELocal() { assert.Equal(w.T(), "bd862e3fe1be46a994272793", lumigoStart.TransactionID) assert.Equal(w.T(), string(inputPayload), lumigoStart.Event) assert.Equal(w.T(), version, lumigoStart.SpanInfo.TracerVersion.Version) + if lumigoStart.LambdaType == "http" { + assert.NotNil(w.T(), lumigoStart.SpanInfo.HttpInfo) + assert.Equal(w.T(), ts.URL, fmt.Sprintf("http://%s", lumigoStart.SpanInfo.HttpInfo.Host)) + assert.Equal(w.T(), fmt.Sprintf("%s/", ts.URL), lumigoStart.SpanInfo.HttpInfo.Request.URI) + assert.Equal(w.T(), "POST", lumigoStart.SpanInfo.HttpInfo.Request.Method) + assert.Equal(w.T(), `{\"name\": \"test\"}`, lumigoStart.SpanInfo.HttpInfo.Request.Body) + assert.Contains(w.T(), `"Agent": "test"`, lumigoStart.SpanInfo.HttpInfo.Request.Headers) + } lumigoEnd := spans.endSpan[0] assert.Equal(w.T(), "account-id", lumigoEnd.Account) assert.Equal(w.T(), "token", lumigoEnd.Token) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_FUNCTION_NAME"), lumigoEnd.LambdaName) - assert.Equal(w.T(), "function", lumigoEnd.LambdaType) assert.Equal(w.T(), "go", lumigoEnd.Runtime) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME"), lumigoEnd.SpanInfo.LogStreamName) assert.Equal(w.T(), os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME"), lumigoEnd.SpanInfo.LogGroupName) @@ -253,13 +302,19 @@ func (w *wrapperTestSuite) TestLambdaHandlerE2ELocal() { assert.Equal(w.T(), string(inputPayload), lumigoEnd.Event) assert.Equal(w.T(), version, lumigoStart.SpanInfo.TracerVersion.Version) + if lumigoStart.LambdaType == "http" { + assert.Equal(w.T(), 200, lumigoStart.SpanInfo.HttpInfo.Response.StatusCode) + assert.Equal(w.T(), `Hello, world!`, lumigoStart.SpanInfo.HttpInfo.Response.Body) + assert.Contains(w.T(), `"Content-Length": "13"`, lumigoStart.SpanInfo.HttpInfo.Response.Headers) + } + if testCase.expected.err != nil { assert.NotNil(w.T(), lumigoEnd.SpanError) assert.Equal(w.T(), testCase.expected.err.Error(), lumigoEnd.SpanError.Message) assert.Equal(w.T(), reflect.TypeOf(testCase.expected.err).String(), lumigoEnd.SpanError.Type) assert.Contains(t, lumigoEnd.SpanError.Stacktrace, "go-tracer-beta.WrapHandler.func1") - assert.Contains(t, lumigoEnd.SpanError.Stacktrace, "go-tracer-beta.(*wrapperTestSuite).TestLambdaHandlerE2ELocal.func5") + assert.Contains(t, lumigoEnd.SpanError.Stacktrace, "go-tracer-beta.(*wrapperTestSuite).TestLambdaHandlerE2ELocal.func7") } else { assert.NotNil(w.T(), lumigoEnd.LambdaResponse) assert.Equal(w.T(), testCase.expected.val, *lumigoEnd.LambdaResponse)