|
1 | | -// Copyright 2020 New Relic Corporation. All rights reserved. |
2 | | -// SPDX-License-Identifier: Apache-2.0 |
| 1 | +// Copyright The OpenTelemetry Authors |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
3 | 14 |
|
4 | | -// Package nrawssdk instruments https://github.com/aws/aws-sdk-go-v2 requests. |
| 15 | +// Package nrawssdk instruments requests made by the |
| 16 | +// https://github.com/aws/aws-sdk-go-v2 library. |
| 17 | +// |
| 18 | +// For most operations, external segments and spans are automatically created |
| 19 | +// for display in the New Relic UI on the External services section. For |
| 20 | +// DynamoDB operations, datastore segements and spans are created and will be |
| 21 | +// displayed on the Databases page. All operations will also be displayed on |
| 22 | +// transaction traces and distributed traces. |
| 23 | +// |
| 24 | +// To use this integration, simply apply the AppendMiddlewares fuction to the apiOptions in |
| 25 | +// your AWS Config object before performing any AWS operations. See |
| 26 | +// example/main.go for a working sample. |
5 | 27 | package nrawssdk |
6 | 28 |
|
7 | 29 | import ( |
8 | | - "github.com/aws/aws-sdk-go-v2/aws" |
9 | | - "github.com/newrelic/go-agent/v3/internal" |
10 | | - "github.com/newrelic/go-agent/v3/internal/awssupport" |
11 | | -) |
| 30 | + "context" |
| 31 | + "strconv" |
12 | 32 |
|
13 | | -func init() { internal.TrackUsage("integration", "library", "aws-sdk-go-v2") } |
| 33 | + awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware" |
| 34 | + smithymiddle "github.com/aws/smithy-go/middleware" |
| 35 | + smithyhttp "github.com/aws/smithy-go/transport/http" |
| 36 | + "github.com/newrelic/go-agent/v3/internal/integrationsupport" |
| 37 | + "github.com/newrelic/go-agent/v3/newrelic" |
| 38 | +) |
14 | 39 |
|
15 | | -func startSegment(req *aws.Request) { |
16 | | - input := awssupport.StartSegmentInputs{ |
17 | | - HTTPRequest: req.HTTPRequest, |
18 | | - ServiceName: req.Metadata.ServiceName, |
19 | | - Operation: req.Operation.Name, |
20 | | - Region: req.Metadata.SigningRegion, |
21 | | - Params: req.Params, |
22 | | - } |
23 | | - req.HTTPRequest = awssupport.StartSegment(input) |
| 40 | +type nrMiddleware struct { |
| 41 | + txn *newrelic.Transaction |
24 | 42 | } |
25 | 43 |
|
26 | | -func endSegment(req *aws.Request) { |
27 | | - ctx := req.HTTPRequest.Context() |
28 | | - awssupport.EndSegment(ctx, req.HTTPResponse.Header) |
| 44 | +type endable interface{ End() } |
| 45 | + |
| 46 | +// See https://aws.github.io/aws-sdk-go-v2/docs/middleware/ for a description of |
| 47 | +// AWS SDK V2 middleware. |
| 48 | +func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { |
| 49 | + return stack.Deserialize.Add(smithymiddle.DeserializeMiddlewareFunc("NRDeserializeMiddleware", func( |
| 50 | + ctx context.Context, in smithymiddle.DeserializeInput, next smithymiddle.DeserializeHandler) ( |
| 51 | + out smithymiddle.DeserializeOutput, metadata smithymiddle.Metadata, err error) { |
| 52 | + |
| 53 | + smithyRequest := in.Request.(*smithyhttp.Request) |
| 54 | + |
| 55 | + // The actual http.Request is inside the smithyhttp.Request |
| 56 | + httpRequest := smithyRequest.Request |
| 57 | + serviceName := awsmiddle.GetServiceID(ctx) |
| 58 | + operation := awsmiddle.GetOperationName(ctx) |
| 59 | + region := awsmiddle.GetRegion(ctx) |
| 60 | + |
| 61 | + var segment endable |
| 62 | + // Service name capitalization is different for v1 and v2. |
| 63 | + if serviceName == "dynamodb" || serviceName == "DynamoDB" { |
| 64 | + segment = &newrelic.DatastoreSegment{ |
| 65 | + Product: newrelic.DatastoreDynamoDB, |
| 66 | + Collection: "", // AWS SDK V2 doesn't expose TableName |
| 67 | + Operation: operation, |
| 68 | + ParameterizedQuery: "", |
| 69 | + QueryParameters: nil, |
| 70 | + Host: httpRequest.URL.Host, |
| 71 | + PortPathOrID: httpRequest.URL.Port(), |
| 72 | + DatabaseName: "", |
| 73 | + StartTime: m.txn.StartSegmentNow(), |
| 74 | + } |
| 75 | + } else { |
| 76 | + segment = newrelic.StartExternalSegment(m.txn, httpRequest) |
| 77 | + } |
| 78 | + |
| 79 | + // Hand off execution to other middlewares and then perform the request |
| 80 | + out, metadata, err = next.HandleDeserialize(ctx, in) |
| 81 | + |
| 82 | + // After the request |
| 83 | + response, ok := out.RawResponse.(*smithyhttp.Response) |
| 84 | + |
| 85 | + if ok { |
| 86 | + // Set additional span attributes |
| 87 | + integrationsupport.AddAgentSpanAttribute(m.txn, |
| 88 | + newrelic.AttributeResponseCode, strconv.Itoa(response.StatusCode)) |
| 89 | + integrationsupport.AddAgentSpanAttribute(m.txn, |
| 90 | + newrelic.SpanAttributeAWSOperation, operation) |
| 91 | + integrationsupport.AddAgentSpanAttribute(m.txn, |
| 92 | + newrelic.SpanAttributeAWSRegion, region) |
| 93 | + requestID, ok := awsmiddle.GetRequestIDMetadata(metadata) |
| 94 | + if ok { |
| 95 | + integrationsupport.AddAgentSpanAttribute(m.txn, |
| 96 | + newrelic.AttributeAWSRequestID, requestID) |
| 97 | + } |
| 98 | + } |
| 99 | + segment.End() |
| 100 | + return out, metadata, err |
| 101 | + }), |
| 102 | + smithymiddle.Before) |
29 | 103 | } |
30 | 104 |
|
31 | | -// InstrumentHandlers will add instrumentation to the given *aws.Handlers. |
32 | | -// |
33 | | -// A Segment will be created for each out going request. The Transaction must |
34 | | -// be added to the `http.Request`'s Context in order for the segment to be |
35 | | -// recorded. For DynamoDB calls, these segments will be |
36 | | -// `newrelic.DatastoreSegment` type and for all others they will be |
37 | | -// `newrelic.ExternalSegment` type. |
38 | | -// |
39 | | -// Additional attributes will be added to Transaction Trace Segments and Span |
40 | | -// Events: aws.region, aws.requestId, and aws.operation. |
41 | | -// |
42 | | -// To add instrumentation to a Config and see segments created for each |
43 | | -// invocation that uses that Config, call InstrumentHandlers with the config's |
44 | | -// Handlers and add the current Transaction to the `http.Request`'s Context: |
45 | | -// |
46 | | -// cfg, _ := external.LoadDefaultAWSConfig() |
47 | | -// cfg.Region = "us-west-2" |
48 | | -// // Add instrumentation to handlers |
49 | | -// nrawssdk.InstrumentHandlers(&cfg.Handlers) |
50 | | -// lambdaClient = lambda.New(cfg) |
| 105 | +// AppendMiddlewares inserts New Relic middleware in the given `apiOptions` for |
| 106 | +// the AWS SDK V2 for Go. It must be called only once per AWS configuration. |
51 | 107 | // |
52 | | -// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{ |
53 | | -// ClientContext: aws.String("MyApp"), |
54 | | -// FunctionName: aws.String("Function"), |
55 | | -// InvocationType: lambda.InvocationTypeEvent, |
56 | | -// LogType: lambda.LogTypeTail, |
57 | | -// Payload: []byte("{}"), |
58 | | -// } |
59 | | -// // Add txn to http.Request's context |
60 | | -// ctx := newrelic.NewContext(req.Context(), txn) |
61 | | -// resp, err := req.Send(ctx) |
| 108 | +// Additional attributes will be added to transaction trace segments and span |
| 109 | +// events: aws.region, aws.requestId, and aws.operation. In addition, |
| 110 | +// http.statusCode will be added to span events. |
62 | 111 | // |
63 | | -// To add instrumentation to a Request and see a segment created just for the |
64 | | -// individual request, call InstrumentHandlers with the `aws.Request`'s |
65 | | -// Handlers and add the current Transaction to the `http.Request`'s Context: |
| 112 | +// To see segments and spans for each AWS invocation, call AppendMiddlewares |
| 113 | +// with the AWS Config `apiOptions` and pass in your current New Relic |
| 114 | +// transaction. For example: |
66 | 115 | // |
67 | | -// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{ |
68 | | -// ClientContext: aws.String("MyApp"), |
69 | | -// FunctionName: aws.String("Function"), |
70 | | -// InvocationType: lambda.InvocationTypeEvent, |
71 | | -// LogType: lambda.LogTypeTail, |
72 | | -// Payload: []byte("{}"), |
73 | | -// } |
74 | | -// // Add instrumentation to handlers |
75 | | -// nrawssdk.InstrumentHandlers(&req.Handlers) |
76 | | -// // Add txn to http.Request's context |
77 | | -// ctx := newrelic.NewContext(req.Context(), txn) |
78 | | -// resp, err := req.Send(ctx) |
79 | | -func InstrumentHandlers(handlers *aws.Handlers) { |
80 | | - handlers.Send.SetFrontNamed(aws.NamedHandler{ |
81 | | - Name: "StartNewRelicSegment", |
82 | | - Fn: startSegment, |
83 | | - }) |
84 | | - handlers.Send.SetBackNamed(aws.NamedHandler{ |
85 | | - Name: "EndNewRelicSegment", |
86 | | - Fn: endSegment, |
87 | | - }) |
| 116 | +// awsConfig, err := config.LoadDefaultConfig(ctx) |
| 117 | +// if err != nil { |
| 118 | +// log.Fatal(err) |
| 119 | +// } |
| 120 | +// nraws.AppendMiddlewares(ctx, &awsConfig.APIOptions, txn) |
| 121 | +func AppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, txn *newrelic.Transaction) { |
| 122 | + m := nrMiddleware{txn: txn} |
| 123 | + *apiOptions = append(*apiOptions, m.deserializeMiddleware) |
88 | 124 | } |
0 commit comments