Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New AWS SDK V2 integration #309

Merged
merged 1 commit into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ jobs:
extratesting: go get -u github.com/sirupsen/logrus@master
- go-version: 1.15.x
dirs: v3/integrations/nrawssdk-v1
extratesting: go get -u github.com/aws/aws-sdk-go@master
extratesting: go get -u github.com/aws/aws-sdk-go@main
- go-version: 1.15.x
dirs: v3/integrations/nrawssdk-v2
extratesting: go get -u github.com/aws/aws-sdk-go-v2@master
extratesting: go get -u github.com/aws/aws-sdk-go-v2@main
- go-version: 1.15.x
dirs: v3/integrations/nrecho-v3
# Test against the latest v3 Echo:
extratesting: go get -u github.com/labstack/echo@v3
- go-version: 1.15.x
dirs: v3/integrations/nrecho-v4
extratesting: go get -u github.com/labstack/echo/v4@master
extratesting: go get -u github.com/labstack/echo@master
- go-version: 1.15.x
dirs: v3/integrations/nrelasticsearch-v7
extratesting: go get -u github.com/elastic/go-elasticsearch/v7@7.x
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Fixed
* Replaced the NR AWS SDK V2 integration for the v3 agent with a new version that works. See the v3/integrations/nrawssdk-v2/example/main.go file for an example of how to use it.

## 3.12.0

### Changes
Expand Down
1 change: 1 addition & 0 deletions v3/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.7

require (
github.com/golang/protobuf v1.3.3
golang.org/x/tools v0.1.0 // indirect
google.golang.org/grpc v1.27.0
)
67 changes: 67 additions & 0 deletions v3/integrations/nrawssdk-v2/example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"context"
"fmt"
"log"
"os"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
nraws "github.com/newrelic/go-agent/v3/integrations/nrawssdk-v2"
"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {

// Create a New Relic application. This will look for your license key in an
// environment varluable called NEW_RELIC_LICENSE_KEY. This example turns on
// Distributed Tracing, but that's not required.
app, err := newrelic.NewApplication(
newrelic.ConfigFromEnvironment(),
newrelic.ConfigAppName("Example App"),
newrelic.ConfigInfoLogger(os.Stdout),
newrelic.ConfigDistributedTracerEnabled(true),
)
if nil != err {
fmt.Println(err)
os.Exit(1)
}

// For demo purposes only. Don't use the app.WaitForConnection call in
// production unless this is a very short-lived process and the caller
// doesn't block or exit if there's an error.
app.WaitForConnection(5 * time.Second)

// Start recording a New Relic transaction
txn := app.StartTransaction("My sample transaction")

ctx := context.Background()
awsConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}

// Instrument all new AWS clients with New Relic
nraws.AppendMiddlewares(&awsConfig.APIOptions, txn)

s3Client := s3.NewFromConfig(awsConfig)
output, err := s3Client.ListBuckets(ctx, nil)
if err != nil {
log.Fatal(err)
}

for _, object := range output.Buckets {
log.Printf("Bucket name is %s\n", aws.ToString(object.Name))
}

// End the New Relic transaction
txn.End()

// Force all the harvests and shutdown. Like the app.WaitForConnection call
// above, this is for the purposes of this demo only and can be safely
// removed for longer-running processes.
app.Shutdown(10 * time.Second)
}
16 changes: 11 additions & 5 deletions v3/integrations/nrawssdk-v2/go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
module github.com/newrelic/go-agent/v3/integrations/nrawssdk-v2

// As of Dec 2019, the aws-sdk-go-v2 go.mod file uses 1.12:
// As of May 2021, the aws-sdk-go-v2 go.mod file uses 1.15:
// https://github.com/aws/aws-sdk-go-v2/blob/master/go.mod
go 1.12
go 1.15

replace github.com/newrelic/go-agent/v3 => ../../

require (
// v0.8.0 is the earliest aws-sdk-go-v2 version where
// dynamodb.DescribeTableRequest.Send takes a context.Context parameter.
github.com/aws/aws-sdk-go-v2 v0.8.0
github.com/aws/aws-sdk-go-v2 v1.4.0
github.com/aws/aws-sdk-go-v2/config v1.1.7
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.2.3
github.com/aws/aws-sdk-go-v2/service/lambda v1.2.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.6.0
github.com/aws/smithy-go v1.4.0
github.com/newrelic/go-agent/v3 v3.0.0
golang.org/x/tools v0.1.0 // indirect
)
184 changes: 110 additions & 74 deletions v3/integrations/nrawssdk-v2/nrawssdk.go
Original file line number Diff line number Diff line change
@@ -1,88 +1,124 @@
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package nrawssdk instruments https://github.com/aws/aws-sdk-go-v2 requests.
// Package nrawssdk instruments requests made by the
// https://github.com/aws/aws-sdk-go-v2 library.
//
// For most operations, external segments and spans are automatically created
// for display in the New Relic UI on the External services section. For
// DynamoDB operations, datastore segements and spans are created and will be
// displayed on the Databases page. All operations will also be displayed on
// transaction traces and distributed traces.
//
// To use this integration, simply apply the AppendMiddlewares fuction to the apiOptions in
// your AWS Config object before performing any AWS operations. See
// example/main.go for a working sample.
package nrawssdk

import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/newrelic/go-agent/v3/internal"
"github.com/newrelic/go-agent/v3/internal/awssupport"
)
"context"
"strconv"

func init() { internal.TrackUsage("integration", "library", "aws-sdk-go-v2") }
awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware"
smithymiddle "github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
"github.com/newrelic/go-agent/v3/internal/integrationsupport"
"github.com/newrelic/go-agent/v3/newrelic"
)

func startSegment(req *aws.Request) {
input := awssupport.StartSegmentInputs{
HTTPRequest: req.HTTPRequest,
ServiceName: req.Metadata.ServiceName,
Operation: req.Operation.Name,
Region: req.Metadata.SigningRegion,
Params: req.Params,
}
req.HTTPRequest = awssupport.StartSegment(input)
type nrMiddleware struct {
txn *newrelic.Transaction
}

func endSegment(req *aws.Request) {
ctx := req.HTTPRequest.Context()
awssupport.EndSegment(ctx, req.HTTPResponse.Header)
type endable interface{ End() }

// See https://aws.github.io/aws-sdk-go-v2/docs/middleware/ for a description of
// AWS SDK V2 middleware.
func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error {
return stack.Deserialize.Add(smithymiddle.DeserializeMiddlewareFunc("NRDeserializeMiddleware", func(
ctx context.Context, in smithymiddle.DeserializeInput, next smithymiddle.DeserializeHandler) (
out smithymiddle.DeserializeOutput, metadata smithymiddle.Metadata, err error) {

smithyRequest := in.Request.(*smithyhttp.Request)

// The actual http.Request is inside the smithyhttp.Request
httpRequest := smithyRequest.Request
serviceName := awsmiddle.GetServiceID(ctx)
operation := awsmiddle.GetOperationName(ctx)
region := awsmiddle.GetRegion(ctx)

var segment endable
// Service name capitalization is different for v1 and v2.
if serviceName == "dynamodb" || serviceName == "DynamoDB" {
segment = &newrelic.DatastoreSegment{
Product: newrelic.DatastoreDynamoDB,
Collection: "", // AWS SDK V2 doesn't expose TableName
Operation: operation,
ParameterizedQuery: "",
QueryParameters: nil,
Host: httpRequest.URL.Host,
PortPathOrID: httpRequest.URL.Port(),
DatabaseName: "",
StartTime: m.txn.StartSegmentNow(),
}
} else {
segment = newrelic.StartExternalSegment(m.txn, httpRequest)
}

// Hand off execution to other middlewares and then perform the request
out, metadata, err = next.HandleDeserialize(ctx, in)

// After the request
response, ok := out.RawResponse.(*smithyhttp.Response)

if ok {
// Set additional span attributes
integrationsupport.AddAgentSpanAttribute(m.txn,
newrelic.AttributeResponseCode, strconv.Itoa(response.StatusCode))
integrationsupport.AddAgentSpanAttribute(m.txn,
newrelic.SpanAttributeAWSOperation, operation)
integrationsupport.AddAgentSpanAttribute(m.txn,
newrelic.SpanAttributeAWSRegion, region)
requestID, ok := awsmiddle.GetRequestIDMetadata(metadata)
if ok {
integrationsupport.AddAgentSpanAttribute(m.txn,
newrelic.AttributeAWSRequestID, requestID)
}
}
segment.End()
return out, metadata, err
}),
smithymiddle.Before)
}

// InstrumentHandlers will add instrumentation to the given *aws.Handlers.
//
// A Segment will be created for each out going request. The Transaction must
// be added to the `http.Request`'s Context in order for the segment to be
// recorded. For DynamoDB calls, these segments will be
// `newrelic.DatastoreSegment` type and for all others they will be
// `newrelic.ExternalSegment` type.
//
// Additional attributes will be added to Transaction Trace Segments and Span
// Events: aws.region, aws.requestId, and aws.operation.
//
// To add instrumentation to a Config and see segments created for each
// invocation that uses that Config, call InstrumentHandlers with the config's
// Handlers and add the current Transaction to the `http.Request`'s Context:
//
// cfg, _ := external.LoadDefaultAWSConfig()
// cfg.Region = "us-west-2"
// // Add instrumentation to handlers
// nrawssdk.InstrumentHandlers(&cfg.Handlers)
// lambdaClient = lambda.New(cfg)
// AppendMiddlewares inserts New Relic middleware in the given `apiOptions` for
// the AWS SDK V2 for Go. It must be called only once per AWS configuration.
//
// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{
// ClientContext: aws.String("MyApp"),
// FunctionName: aws.String("Function"),
// InvocationType: lambda.InvocationTypeEvent,
// LogType: lambda.LogTypeTail,
// Payload: []byte("{}"),
// }
// // Add txn to http.Request's context
// ctx := newrelic.NewContext(req.Context(), txn)
// resp, err := req.Send(ctx)
// Additional attributes will be added to transaction trace segments and span
// events: aws.region, aws.requestId, and aws.operation. In addition,
// http.statusCode will be added to span events.
//
// To add instrumentation to a Request and see a segment created just for the
// individual request, call InstrumentHandlers with the `aws.Request`'s
// Handlers and add the current Transaction to the `http.Request`'s Context:
// To see segments and spans for each AWS invocation, call AppendMiddlewares
// with the AWS Config `apiOptions` and pass in your current New Relic
// transaction. For example:
//
// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{
// ClientContext: aws.String("MyApp"),
// FunctionName: aws.String("Function"),
// InvocationType: lambda.InvocationTypeEvent,
// LogType: lambda.LogTypeTail,
// Payload: []byte("{}"),
// }
// // Add instrumentation to handlers
// nrawssdk.InstrumentHandlers(&req.Handlers)
// // Add txn to http.Request's context
// ctx := newrelic.NewContext(req.Context(), txn)
// resp, err := req.Send(ctx)
func InstrumentHandlers(handlers *aws.Handlers) {
handlers.Send.SetFrontNamed(aws.NamedHandler{
Name: "StartNewRelicSegment",
Fn: startSegment,
})
handlers.Send.SetBackNamed(aws.NamedHandler{
Name: "EndNewRelicSegment",
Fn: endSegment,
})
// awsConfig, err := config.LoadDefaultConfig(ctx)
// if err != nil {
// log.Fatal(err)
// }
// nraws.AppendMiddlewares(ctx, &awsConfig.APIOptions, txn)
func AppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, txn *newrelic.Transaction) {
m := nrMiddleware{txn: txn}
*apiOptions = append(*apiOptions, m.deserializeMiddleware)
}
Loading