Skip to content

Commit

Permalink
new AWS SDK V2 integration
Browse files Browse the repository at this point in the history
  • Loading branch information
RichVanderwal committed May 7, 2021
1 parent 55d67d0 commit 8940036
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 408 deletions.
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

0 comments on commit 8940036

Please sign in to comment.