From 894003685bac9cb9bd68257d9af45dd66ad5de23 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Fri, 7 May 2021 14:20:36 -0700 Subject: [PATCH 01/15] new AWS SDK V2 integration --- .github/workflows/ci.yaml | 6 +- CHANGELOG.md | 3 + v3/go.mod | 1 + v3/integrations/nrawssdk-v2/example/main.go | 67 +++ v3/integrations/nrawssdk-v2/go.mod | 16 +- v3/integrations/nrawssdk-v2/nrawssdk.go | 184 +++++--- v3/integrations/nrawssdk-v2/nrawssdk_test.go | 458 ++++++------------- 7 files changed, 327 insertions(+), 408 deletions(-) create mode 100644 v3/integrations/nrawssdk-v2/example/main.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d73a5bbb6..64250a9e7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b61d61f6d..06a838811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/v3/go.mod b/v3/go.mod index 2ada3801e..6fd730773 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -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 ) diff --git a/v3/integrations/nrawssdk-v2/example/main.go b/v3/integrations/nrawssdk-v2/example/main.go new file mode 100644 index 000000000..d73f48708 --- /dev/null +++ b/v3/integrations/nrawssdk-v2/example/main.go @@ -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) +} diff --git a/v3/integrations/nrawssdk-v2/go.mod b/v3/integrations/nrawssdk-v2/go.mod index 18ee52cb0..209c4713e 100644 --- a/v3/integrations/nrawssdk-v2/go.mod +++ b/v3/integrations/nrawssdk-v2/go.mod @@ -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 ) diff --git a/v3/integrations/nrawssdk-v2/nrawssdk.go b/v3/integrations/nrawssdk-v2/nrawssdk.go index 9fb47f6cb..2a844bcf5 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk.go @@ -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) } diff --git a/v3/integrations/nrawssdk-v2/nrawssdk_test.go b/v3/integrations/nrawssdk-v2/nrawssdk_test.go index d8949fbaa..521d8c741 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk_test.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk_test.go @@ -9,18 +9,17 @@ import ( "errors" "io/ioutil" "net/http" - "strings" "testing" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/aws/external" - "github.com/aws/aws-sdk-go-v2/private/protocol/rest" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/lambda" + "github.com/aws/aws-sdk-go-v2/service/lambda/types" "github.com/newrelic/go-agent/v3/internal" - "github.com/newrelic/go-agent/v3/internal/awssupport" "github.com/newrelic/go-agent/v3/internal/integrationsupport" - newrelic "github.com/newrelic/go-agent/v3/newrelic" + "github.com/newrelic/go-agent/v3/newrelic" ) func testApp() integrationsupport.ExpectApp { @@ -60,23 +59,23 @@ var fakeCreds = func() interface{} { return fakeCredsWithContext{} }() -func newConfig(instrument bool) aws.Config { - cfg, _ := external.LoadDefaultAWSConfig() +func newConfig(ctx context.Context, txn *newrelic.Transaction) aws.Config { + cfg, _ := config.LoadDefaultConfig(ctx) cfg.Credentials = fakeCreds.(aws.CredentialsProvider) - cfg.Region = "us-west-2" + cfg.Region = awsRegion cfg.HTTPClient = &http.Client{ Transport: &fakeTransport{}, } - if instrument { - InstrumentHandlers(&cfg.Handlers) - } + AppendMiddlewares(&cfg.APIOptions, txn) + return cfg } const ( requestID = "testing request id" txnName = "aws-txn" + awsRegion = "us-west-2" ) var ( @@ -110,11 +109,12 @@ var ( }, UserAttributes: map[string]interface{}{}, AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "aws.operation": "Invoke", + "aws.region": awsRegion, + "aws.requestId": requestID, + "http.method": "POST", + "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "http.statusCode": "200", }, } externalSpanNoRequestID = internal.WantEvent{ @@ -132,15 +132,16 @@ var ( }, UserAttributes: map[string]interface{}{}, AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "aws.operation": "Invoke", + "aws.region": awsRegion, + "http.method": "POST", + "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "http.statusCode": "200", }, } datastoreSpan = internal.WantEvent{ Intrinsics: map[string]interface{}{ - "name": "Datastore/statement/DynamoDB/thebesttable/DescribeTable", + "name": "Datastore/operation/DynamoDB/DescribeTable", "sampled": true, "category": "datastore", "priority": internal.MatchAnything, @@ -153,16 +154,15 @@ var ( }, UserAttributes: map[string]interface{}{}, AgentAttributes: map[string]interface{}{ - "aws.operation": "DescribeTable", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "db.collection": "thebesttable", - "db.statement": "'DescribeTable' on 'thebesttable' using 'DynamoDB'", - "peer.address": "dynamodb.us-west-2.amazonaws.com:unknown", - "peer.hostname": "dynamodb.us-west-2.amazonaws.com", + "aws.operation": "DescribeTable", + "aws.region": awsRegion, + "aws.requestId": requestID, + "db.statement": "'DescribeTable' on 'unknown' using 'DynamoDB'", + "peer.address": "dynamodb.us-west-2.amazonaws.com:unknown", + "peer.hostname": "dynamodb.us-west-2.amazonaws.com", + "http.statusCode": "200", }, } - txnMetrics = []internal.WantMetric{ {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, @@ -184,119 +184,27 @@ var ( {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, {Name: "Datastore/instance/DynamoDB/dynamodb.us-west-2.amazonaws.com/unknown", Scope: "", Forced: false, Data: nil}, {Name: "Datastore/operation/DynamoDB/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: nil}, + {Name: "Datastore/operation/DynamoDB/DescribeTable", Scope: "OtherTransaction/Go/aws-txn", Forced: false, Data: nil}, }...) ) func TestInstrumentRequestExternal(t *testing.T) { app := testApp() txn := app.StartTransaction(txnName) + ctx := context.TODO() - client := lambda.New(newConfig(false)) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - externalSpan, genericSpan}) -} - -func TestInstrumentRequestDatastore(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName) - - client := dynamodb.New(newConfig(false)) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, datastoreMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - datastoreSpan, genericSpan}) -} - -func TestInstrumentRequestExternalNoTxn(t *testing.T) { - client := lambda.New(newConfig(false)) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentRequestDatastoreNoTxn(t *testing.T) { - client := dynamodb.New(newConfig(false)) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigExternal(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName) - - client := lambda.New(newConfig(true)) + client := lambda.NewFromConfig(newConfig(ctx, txn)) input := &lambda.InvokeInput{ ClientContext: aws.String("MyApp"), FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, + InvocationType: types.InvocationTypeRequestResponse, + LogType: types.LogTypeTail, Payload: []byte("{}"), } - req := client.InvokeRequest(input) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { + _, err := client.Invoke(ctx, input) + if err != nil { t.Error(err) } @@ -307,21 +215,19 @@ func TestInstrumentConfigExternal(t *testing.T) { externalSpan, genericSpan}) } -func TestInstrumentConfigDatastore(t *testing.T) { +func TestInstrumentRequestDatastore(t *testing.T) { app := testApp() txn := app.StartTransaction(txnName) + ctx := context.TODO() - client := dynamodb.New(newConfig(true)) + client := dynamodb.NewFromConfig(newConfig(ctx, txn)) input := &dynamodb.DescribeTableInput{ TableName: aws.String("thebesttable"), } - req := client.DescribeTableRequest(input) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { + _, err := client.DescribeTable(ctx, input) + if err != nil { t.Error(err) } @@ -332,109 +238,6 @@ func TestInstrumentConfigDatastore(t *testing.T) { datastoreSpan, genericSpan}) } -func TestInstrumentConfigExternalNoTxn(t *testing.T) { - client := lambda.New(newConfig(true)) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigDatastoreNoTxn(t *testing.T) { - client := dynamodb.New(newConfig(true)) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigExternalTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName) - - client := lambda.New(newConfig(true)) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestInstrumentConfigDatastoreTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName) - - client := dynamodb.New(newConfig(true)) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestDoublyInstrumented(t *testing.T) { - hs := &aws.Handlers{} - if found := hs.Send.Len(); 0 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } -} - type firstFailingTransport struct { failing bool } @@ -444,6 +247,7 @@ func (t *firstFailingTransport) RoundTrip(r *http.Request) (*http.Response, erro t.failing = false return nil, errors.New("Oops this failed") } + return &http.Response{ Status: "200 OK", StatusCode: 200, @@ -457,70 +261,121 @@ func (t *firstFailingTransport) RoundTrip(r *http.Request) (*http.Response, erro func TestRetrySend(t *testing.T) { app := testApp() txn := app.StartTransaction(txnName) + ctx := context.TODO() + + cfg := newConfig(ctx, txn) - cfg := newConfig(false) cfg.HTTPClient = &http.Client{ Transport: &firstFailingTransport{failing: true}, } - client := lambda.New(cfg) + customRetry := retry.NewStandard(func(o *retry.StandardOptions) { + o.MaxAttempts = 2 + }) + client := lambda.NewFromConfig(cfg, func(o *lambda.Options) { + o.Retryer = customRetry + }) + input := &lambda.InvokeInput{ ClientContext: aws.String("MyApp"), FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, + InvocationType: types.InvocationTypeRequestResponse, + LogType: types.LogTypeTail, Payload: []byte("{}"), } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - _, err := req.Send(ctx) - if nil != err { + _, err := client.Invoke(ctx, input) + if err != nil { t.Error(err) } txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/allOther", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: []float64{2}}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) + app.ExpectMetrics(t, externalMetrics) + app.ExpectSpanEvents(t, []internal.WantEvent{ - externalSpanNoRequestID, externalSpan, genericSpan}) + { + Intrinsics: map[string]interface{}{ + "name": "External/lambda.us-west-2.amazonaws.com/http/POST", + "sampled": true, + "category": "http", + "priority": internal.MatchAnything, + "guid": internal.MatchAnything, + "transactionId": internal.MatchAnything, + "traceId": internal.MatchAnything, + "parentId": internal.MatchAnything, + "component": "http", + "span.kind": "client", + }, + UserAttributes: map[string]interface{}{}, + AgentAttributes: map[string]interface{}{ + "aws.operation": "Invoke", + "aws.region": awsRegion, + "http.method": "POST", + "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "http.statusCode": "0", + }, + }, { + Intrinsics: map[string]interface{}{ + "name": "External/lambda.us-west-2.amazonaws.com/http/POST", + "sampled": true, + "category": "http", + "priority": internal.MatchAnything, + "guid": internal.MatchAnything, + "transactionId": internal.MatchAnything, + "traceId": internal.MatchAnything, + "parentId": internal.MatchAnything, + "component": "http", + "span.kind": "client", + }, + UserAttributes: map[string]interface{}{}, + AgentAttributes: map[string]interface{}{ + "aws.operation": "Invoke", + "aws.region": awsRegion, + "aws.requestId": requestID, + "http.method": "POST", + "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", + "http.statusCode": "200", + }, + }, { + Intrinsics: map[string]interface{}{ + "name": "OtherTransaction/Go/" + txnName, + "transaction.name": "OtherTransaction/Go/" + txnName, + "sampled": true, + "category": "generic", + "priority": internal.MatchAnything, + "guid": internal.MatchAnything, + "transactionId": internal.MatchAnything, + "nr.entryPoint": true, + "traceId": internal.MatchAnything, + }, + UserAttributes: map[string]interface{}{}, + AgentAttributes: map[string]interface{}{}, + }}) } func TestRequestSentTwice(t *testing.T) { app := testApp() txn := app.StartTransaction(txnName) + ctx := context.TODO() + + client := lambda.NewFromConfig(newConfig(ctx, txn)) - client := lambda.New(newConfig(false)) input := &lambda.InvokeInput{ ClientContext: aws.String("MyApp"), FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, + InvocationType: types.InvocationTypeRequestResponse, + LogType: types.LogTypeTail, Payload: []byte("{}"), } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - _, firstErr := req.Send(ctx) - if nil != firstErr { + _, firstErr := client.Invoke(ctx, input) + if firstErr != nil { t.Error(firstErr) } - _, secondErr := req.Send(ctx) - if nil != secondErr { + _, secondErr := client.Invoke(ctx, input) + if secondErr != nil { t.Error(secondErr) } @@ -555,26 +410,23 @@ func (t *noRequestIDTransport) RoundTrip(r *http.Request) (*http.Response, error func TestNoRequestIDFound(t *testing.T) { app := testApp() txn := app.StartTransaction(txnName) + ctx := context.TODO() - cfg := newConfig(false) + cfg := newConfig(ctx, txn) cfg.HTTPClient = &http.Client{ Transport: &noRequestIDTransport{}, } + client := lambda.NewFromConfig(cfg) - client := lambda.New(cfg) input := &lambda.InvokeInput{ ClientContext: aws.String("MyApp"), FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, + InvocationType: types.InvocationTypeRequestResponse, + LogType: types.LogTypeTail, Payload: []byte("{}"), } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { + _, err := client.Invoke(ctx, input) + if err != nil { t.Error(err) } @@ -584,49 +436,3 @@ func TestNoRequestIDFound(t *testing.T) { app.ExpectSpanEvents(t, []internal.WantEvent{ externalSpanNoRequestID, genericSpan}) } - -func TestGetRequestID(t *testing.T) { - primary := "X-Amzn-Requestid" - secondary := "X-Amz-Request-Id" - - testcases := []struct { - hdr http.Header - expected string - }{ - {hdr: http.Header{ - "hello": []string{"world"}, - }, expected: ""}, - - {hdr: http.Header{ - strings.ToUpper(primary): []string{"hello"}, - }, expected: ""}, - - {hdr: http.Header{ - primary: []string{"hello"}, - }, expected: "hello"}, - - {hdr: http.Header{ - secondary: []string{"hello"}, - }, expected: "hello"}, - - {hdr: http.Header{ - primary: []string{"hello"}, - secondary: []string{"world"}, - }, expected: "hello"}, - } - - // Make sure our assumptions still hold against aws-sdk-go-v2 - for _, test := range testcases { - req := &aws.Request{ - HTTPResponse: &http.Response{ - Header: test.hdr, - }, - Data: &lambda.InvokeOutput{}, - } - rest.UnmarshalMeta(req) - if out := awssupport.GetRequestID(test.hdr); req.RequestID != out { - t.Error("requestId assumptions incorrect", out, req.RequestID, - test.hdr, test.expected) - } - } -} From a9bac1ad7e0a0bdac34cdedbf67d30929c76cada Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Thu, 13 May 2021 20:43:39 -0700 Subject: [PATCH 02/15] Getting http2 and grpc to run on old Go --- build-script.sh | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/build-script.sh b/build-script.sh index 22b6b0c07..6f3f87077 100755 --- a/build-script.sh +++ b/build-script.sh @@ -38,12 +38,16 @@ for dir in $DIRS; do else # Only v3 code version 1.9+ needs GRPC dependencies VERSION=$(go version) - V17="1.7" - V18="1.8" - V19="1.9" - if [[ "$VERSION" =~ .*"$V17".* || "$VERSION" =~ .*"$V18".* ]]; then + V1_7="1.7" + V1_8="1.8" + V1_9="1.9" + V1_10="1.10" + V1_11="1.11" + V1_12="1.12" + if [[ "$VERSION" =~ .*"$V1_7".* || "$VERSION" =~ .*"$V1_8".* ]]; then echo "Not installing GRPC for old versions" - elif [[ "$VERSION" =~ .*"$V19" ]]; then + fi + if [[ "$VERSION" =~ .*"$V1_9" ]]; then # install v3 dependencies that support this go version set +e go get -u google.golang.org/grpc # this go get will fail to build @@ -52,6 +56,22 @@ for dir in $DIRS; do git checkout v1.31.0 cd - + set +e + go get -u golang.org/x/net/http2 # this go get will fail to build + set -e + cd $GOPATH/src/golang.org/x/net/http2 + git checkout 7fd8e65b642006927f6cec5cb4241df7f98a2210 + cd - + + go get -u github.com/golang/protobuf/protoc-gen-go + elif [[ "$VERSION" =~ .*"$V1_10" || "$VERSION" =~ .*"$V1_11" || "$VERSION" =~ .*"$V1_12" ]]; then + set +e + go get -u golang.org/x/net/http2 # this go get will fail to build + set -e + cd $GOPATH/src/golang.org/x/net/http2 + git checkout 7fd8e65b642006927f6cec5cb4241df7f98a2210 + cd - + go get -u github.com/golang/protobuf/protoc-gen-go else go get -u github.com/golang/protobuf/protoc-gen-go From 576f5584e878eeb22ffc31d323d7afdaf4d04e38 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Thu, 13 May 2021 20:57:15 -0700 Subject: [PATCH 03/15] grpc for 10, 11, 12 --- build-script.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build-script.sh b/build-script.sh index 6f3f87077..43c2e60e6 100755 --- a/build-script.sh +++ b/build-script.sh @@ -73,6 +73,7 @@ for dir in $DIRS; do cd - go get -u github.com/golang/protobuf/protoc-gen-go + go get -u google.golang.org/grpc else go get -u github.com/golang/protobuf/protoc-gen-go go get -u google.golang.org/grpc From 8e5fd192d01ecb8537f7236353bb876ab96c6836 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Thu, 13 May 2021 21:11:03 -0700 Subject: [PATCH 04/15] another try --- build-script.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/build-script.sh b/build-script.sh index 43c2e60e6..e4d73c4d6 100755 --- a/build-script.sh +++ b/build-script.sh @@ -46,8 +46,7 @@ for dir in $DIRS; do V1_12="1.12" if [[ "$VERSION" =~ .*"$V1_7".* || "$VERSION" =~ .*"$V1_8".* ]]; then echo "Not installing GRPC for old versions" - fi - if [[ "$VERSION" =~ .*"$V1_9" ]]; then + elif [[ "$VERSION" =~ .*"$V1_9" || "$VERSION" =~ .*"$V1_10" || "$VERSION" =~ .*"$V1_11" || "$VERSION" =~ .*"$V1_12"]]; then # install v3 dependencies that support this go version set +e go get -u google.golang.org/grpc # this go get will fail to build @@ -64,16 +63,6 @@ for dir in $DIRS; do cd - go get -u github.com/golang/protobuf/protoc-gen-go - elif [[ "$VERSION" =~ .*"$V1_10" || "$VERSION" =~ .*"$V1_11" || "$VERSION" =~ .*"$V1_12" ]]; then - set +e - go get -u golang.org/x/net/http2 # this go get will fail to build - set -e - cd $GOPATH/src/golang.org/x/net/http2 - git checkout 7fd8e65b642006927f6cec5cb4241df7f98a2210 - cd - - - go get -u github.com/golang/protobuf/protoc-gen-go - go get -u google.golang.org/grpc else go get -u github.com/golang/protobuf/protoc-gen-go go get -u google.golang.org/grpc From 6cb0eeef66fed3ea3d3088a555051fcde4cfe6a0 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Thu, 13 May 2021 21:20:30 -0700 Subject: [PATCH 05/15] missed a space --- build-script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-script.sh b/build-script.sh index e4d73c4d6..5f8035c41 100755 --- a/build-script.sh +++ b/build-script.sh @@ -46,7 +46,7 @@ for dir in $DIRS; do V1_12="1.12" if [[ "$VERSION" =~ .*"$V1_7".* || "$VERSION" =~ .*"$V1_8".* ]]; then echo "Not installing GRPC for old versions" - elif [[ "$VERSION" =~ .*"$V1_9" || "$VERSION" =~ .*"$V1_10" || "$VERSION" =~ .*"$V1_11" || "$VERSION" =~ .*"$V1_12"]]; then + elif [[ "$VERSION" =~ .*"$V1_9" || "$VERSION" =~ .*"$V1_10" || "$VERSION" =~ .*"$V1_11" || "$VERSION" =~ .*"$V1_12" ]]; then # install v3 dependencies that support this go version set +e go get -u google.golang.org/grpc # this go get will fail to build From c633cf81672ed5cb3596793d06555271a23d4ed8 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Mon, 24 May 2021 16:11:05 -0700 Subject: [PATCH 06/15] examples/server gets config from environment --- CHANGELOG.md | 3 +++ v3/examples/server/main.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a838811..a663cb9bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### 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. +### Changes +* The v3/examples/server/main.go example now uses `newrelic.ConfigFromEnvironment()`, rather than explicitly pulling in the license key with `newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY"))`. The team is starting to use this as a general systems integration testing script, and this facilitates testing with different settings enabled. + ## 3.12.0 ### Changes diff --git a/v3/examples/server/main.go b/v3/examples/server/main.go index a141a7019..16e6c0458 100644 --- a/v3/examples/server/main.go +++ b/v3/examples/server/main.go @@ -249,7 +249,7 @@ func browser(w http.ResponseWriter, r *http.Request) { func main() { app, err := newrelic.NewApplication( newrelic.ConfigAppName("Example App"), - newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigFromEnvironment(), newrelic.ConfigDebugLogger(os.Stdout), ) if nil != err { From 9f7ec93a39e8ec6882f6acec04efca2211d44c4c Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Mon, 24 May 2021 18:58:55 -0700 Subject: [PATCH 07/15] fix issue 221: grpc error reporting --- v3/integrations/nrgrpc/go.mod | 6 ++++-- v3/integrations/nrgrpc/nrgrpc_server.go | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index 248939bf5..664afd09d 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -5,10 +5,12 @@ module github.com/newrelic/go-agent/v3/integrations/nrgrpc go 1.11 require ( + github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect // protobuf v1.3.0 is the earliest version using modules, we use v1.3.1 // because all dependencies were removed in this version. - github.com/golang/protobuf v1.3.1 + github.com/golang/protobuf v1.3.3 + github.com/kisielk/gotool v1.0.0 // indirect github.com/newrelic/go-agent/v3 v3.0.0 // v1.15.0 is the earliest version of grpc using modules. - google.golang.org/grpc v1.15.0 + google.golang.org/grpc v1.27.0 ) diff --git a/v3/integrations/nrgrpc/nrgrpc_server.go b/v3/integrations/nrgrpc/nrgrpc_server.go index d813f129f..e8119c66e 100644 --- a/v3/integrations/nrgrpc/nrgrpc_server.go +++ b/v3/integrations/nrgrpc/nrgrpc_server.go @@ -69,7 +69,7 @@ func startTransaction(ctx context.Context, app *newrelic.Application, fullMethod // https://github.com/newrelic/go-agent/blob/master/v3/integrations/nrgrpc/example/server/server.go // func UnaryServerInterceptor(app *newrelic.Application) grpc.UnaryServerInterceptor { - if nil == app { + if app == nil { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return handler(ctx, req) } @@ -82,6 +82,9 @@ func UnaryServerInterceptor(app *newrelic.Application) grpc.UnaryServerIntercept ctx = newrelic.NewContext(ctx, txn) resp, err = handler(ctx, req) txn.SetWebResponse(nil).WriteHeader(int(status.Code(err))) + if err != nil { + txn.NoticeError(err) + } return } } @@ -130,7 +133,7 @@ func newWrappedServerStream(stream grpc.ServerStream, txn *newrelic.Transaction) // https://github.com/newrelic/go-agent/blob/master/v3/integrations/nrgrpc/example/server/server.go // func StreamServerInterceptor(app *newrelic.Application) grpc.StreamServerInterceptor { - if nil == app { + if app == nil { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { return handler(srv, ss) } @@ -142,6 +145,9 @@ func StreamServerInterceptor(app *newrelic.Application) grpc.StreamServerInterce err := handler(srv, newWrappedServerStream(ss, txn)) txn.SetWebResponse(nil).WriteHeader(int(status.Code(err))) + if err != nil { + txn.NoticeError(err) + } return err } } From e65fba678bbf76a8a4794889aa5249296ee009ac Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 27 May 2021 23:11:02 +0000 Subject: [PATCH 08/15] fix: v3/internal/tools/utilization/Dockerfile to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-DEBIAN8-GIT-340820 - https://snyk.io/vuln/SNYK-DEBIAN8-GIT-340873 - https://snyk.io/vuln/SNYK-DEBIAN8-GIT-340907 - https://snyk.io/vuln/SNYK-DEBIAN8-PROCPS-309313 - https://snyk.io/vuln/SNYK-DEBIAN8-WGET-300469 --- v3/internal/tools/utilization/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/internal/tools/utilization/Dockerfile b/v3/internal/tools/utilization/Dockerfile index 975fdc840..90e0a1af3 100644 --- a/v3/internal/tools/utilization/Dockerfile +++ b/v3/internal/tools/utilization/Dockerfile @@ -11,7 +11,7 @@ # docker run utilization # -FROM golang:1.7 +FROM golang:1 ENV GOPATH /tmp/gopath From d84a0a82fe35bfe19fc74805736504d36426fb74 Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Thu, 27 May 2021 16:45:00 -0700 Subject: [PATCH 09/15] adjusted module version required and fixed failing tests --- v3/integrations/nrgrpc/go.mod | 2 +- v3/integrations/nrgrpc/nrgrpc_server_test.go | 42 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index 664afd09d..7c89a86d1 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -10,7 +10,7 @@ require ( // because all dependencies were removed in this version. github.com/golang/protobuf v1.3.3 github.com/kisielk/gotool v1.0.0 // indirect - github.com/newrelic/go-agent/v3 v3.0.0 + github.com/newrelic/go-agent/v3 v3.12.0 // v1.15.0 is the earliest version of grpc using modules. google.golang.org/grpc v1.27.0 ) diff --git a/v3/integrations/nrgrpc/nrgrpc_server_test.go b/v3/integrations/nrgrpc/nrgrpc_server_test.go index a7900bbe1..3f1f6a8e3 100644 --- a/v3/integrations/nrgrpc/nrgrpc_server_test.go +++ b/v3/integrations/nrgrpc/nrgrpc_server_test.go @@ -211,6 +211,27 @@ func TestUnaryServerInterceptorError(t *testing.T) { "request.uri": "grpc://bufnet/TestApplication/DoUnaryUnaryError", }, UserAttributes: map[string]interface{}{}, + }, { + Intrinsics: map[string]interface{}{ + "error.class": "*status.statusError", + "error.message": "rpc error: code = DataLoss desc = oooooops!", + "guid": internal.MatchAnything, + "priority": internal.MatchAnything, + "sampled": internal.MatchAnything, + "spanId": internal.MatchAnything, + "traceId": internal.MatchAnything, + "transactionName": "WebTransaction/Go/TestApplication/DoUnaryUnaryError", + }, + AgentAttributes: map[string]interface{}{ + "httpResponseCode": 15, + "http.statusCode": 15, + "request.headers.User-Agent": internal.MatchAnything, + "request.headers.userAgent": internal.MatchAnything, + "request.headers.contentType": "application/grpc", + "request.method": "TestApplication/DoUnaryUnaryError", + "request.uri": "grpc://bufnet/TestApplication/DoUnaryUnaryError", + }, + UserAttributes: map[string]interface{}{}, }}) } @@ -613,6 +634,27 @@ func TestStreamServerInterceptorError(t *testing.T) { "request.uri": "grpc://bufnet/TestApplication/DoUnaryStreamError", }, UserAttributes: map[string]interface{}{}, + }, { + Intrinsics: map[string]interface{}{ + "error.class": "*status.statusError", + "error.message": "rpc error: code = DataLoss desc = oooooops!", + "guid": internal.MatchAnything, + "priority": internal.MatchAnything, + "sampled": internal.MatchAnything, + "spanId": internal.MatchAnything, + "traceId": internal.MatchAnything, + "transactionName": "WebTransaction/Go/TestApplication/DoUnaryStreamError", + }, + AgentAttributes: map[string]interface{}{ + "httpResponseCode": 15, + "http.statusCode": 15, + "request.headers.User-Agent": internal.MatchAnything, + "request.headers.userAgent": internal.MatchAnything, + "request.headers.contentType": "application/grpc", + "request.method": "TestApplication/DoUnaryStreamError", + "request.uri": "grpc://bufnet/TestApplication/DoUnaryStreamError", + }, + UserAttributes: map[string]interface{}{}, }}) } From bdc4b9492e984a9f23af7209e1c28f58b3b1d0db Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Thu, 27 May 2021 17:37:41 -0700 Subject: [PATCH 10/15] adjusted tests for arbitrary error.class from grpc calls --- v3/integrations/nrgrpc/nrgrpc_server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/integrations/nrgrpc/nrgrpc_server_test.go b/v3/integrations/nrgrpc/nrgrpc_server_test.go index 3f1f6a8e3..4dce85bdf 100644 --- a/v3/integrations/nrgrpc/nrgrpc_server_test.go +++ b/v3/integrations/nrgrpc/nrgrpc_server_test.go @@ -213,7 +213,7 @@ func TestUnaryServerInterceptorError(t *testing.T) { UserAttributes: map[string]interface{}{}, }, { Intrinsics: map[string]interface{}{ - "error.class": "*status.statusError", + "error.class": internal.MatchAnything, "error.message": "rpc error: code = DataLoss desc = oooooops!", "guid": internal.MatchAnything, "priority": internal.MatchAnything, @@ -636,7 +636,7 @@ func TestStreamServerInterceptorError(t *testing.T) { UserAttributes: map[string]interface{}{}, }, { Intrinsics: map[string]interface{}{ - "error.class": "*status.statusError", + "error.class": internal.MatchAnything, "error.message": "rpc error: code = DataLoss desc = oooooops!", "guid": internal.MatchAnything, "priority": internal.MatchAnything, From 070386e4e96338ceab2164452ad17413378cdac5 Mon Sep 17 00:00:00 2001 From: Abel Tay Date: Mon, 31 May 2021 13:46:30 +0800 Subject: [PATCH 11/15] Fix command for StartExternalSegment --- GUIDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUIDE.md b/GUIDE.md index fd844219f..8ee2735c1 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -369,8 +369,8 @@ ways to use this functionality: For example: ```go - func external(txn newrelic.Transaction, req *http.Request) (*http.Response, error) { - s := txn.StartExternalSegment(req) + func external(txn *newrelic.Transaction, req *http.Request) (*http.Response, error) { + s := newrelic.StartExternalSegment(txn, req) response, err := http.DefaultClient.Do(req) s.Response = response s.End() From 155fa8b301774768034d0bc383bed4a4006e9f0b Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Tue, 1 Jun 2021 15:31:38 -0700 Subject: [PATCH 12/15] changelog, version for 3.13.0 --- CHANGELOG.md | 9 +++++++++ v3/newrelic/version.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a663cb9bf..1c900b4b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,21 @@ ## Unreleased +## 3.13.0 + ### 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. +* Fixes issue [#221](https://github.com/newrelic/go-agent/issues/221): grpc errors reported in code watched by `UnaryServerInterceptor()` or `StreamServerInterceptor()` now create error events which are reported to the UI with the error message string included. + +* Fixes documentation in `GUIDE.md` for `txn.StartExternalSegment()` to reflect the v3 usage. Thanks to @abeltay for calling this to our attention and submitting PR [#320](https://github.com/newrelic/go-agent/pull/320). + ### Changes * The v3/examples/server/main.go example now uses `newrelic.ConfigFromEnvironment()`, rather than explicitly pulling in the license key with `newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY"))`. The team is starting to use this as a general systems integration testing script, and this facilitates testing with different settings enabled. +### Support Statement +* New Relic recommends that you upgrade the agent regularly to ensure that you're getting the latest features and performance benefits. Additionally, older releases will no longer be supported when they reach [end-of-life](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/install-configure/notification-changes-new-relic-saas-features-distributed-software). + ## 3.12.0 ### Changes diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index 6f321c6fd..4df810937 100644 --- a/v3/newrelic/version.go +++ b/v3/newrelic/version.go @@ -11,7 +11,7 @@ import ( const ( // Version is the full string version of this Go Agent. - Version = "3.12.0" + Version = "3.13.0" ) var ( From c42ac9deb462d3329827d48e5af9aff5f57af0d1 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Wed, 2 Jun 2021 14:00:32 -0700 Subject: [PATCH 13/15] Tidied up changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c900b4b7..1e553f372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,9 @@ ## 3.13.0 ### 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. +* 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. Issues [#250](https://github.com/newrelic/go-agent/issues/250) and [#288](https://github.com/newrelic/go-agent/issues/288) are fixed by this PR. [#309](https://github.com/newrelic/go-agent/pull/309) -* Fixes issue [#221](https://github.com/newrelic/go-agent/issues/221): grpc errors reported in code watched by `UnaryServerInterceptor()` or `StreamServerInterceptor()` now create error events which are reported to the UI with the error message string included. +* Fixes issue [#221](https://github.com/newrelic/go-agent/issues/221): grpc errors reported in code watched by `UnaryServerInterceptor()` or `StreamServerInterceptor()` now create error events which are reported to the UI with the error message string included. [#317](https://github.com/newrelic/go-agent/pull/317) * Fixes documentation in `GUIDE.md` for `txn.StartExternalSegment()` to reflect the v3 usage. Thanks to @abeltay for calling this to our attention and submitting PR [#320](https://github.com/newrelic/go-agent/pull/320). From d547c20b3d231a6ac4e5c914da10688f8f60e807 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Wed, 2 Jun 2021 14:19:59 -0700 Subject: [PATCH 14/15] Remove unneeded import --- v3/go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/v3/go.mod b/v3/go.mod index 6fd730773..2ada3801e 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -4,6 +4,5 @@ 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 ) From 07aa8cc5db6e95964d92559280b8c3e19037f4f8 Mon Sep 17 00:00:00 2001 From: Rich Vanderwal Date: Wed, 2 Jun 2021 14:37:20 -0700 Subject: [PATCH 15/15] More uneeded includes removed --- v3/integrations/nrgrpc/go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/v3/integrations/nrgrpc/go.mod b/v3/integrations/nrgrpc/go.mod index 7c89a86d1..e6ac3bd62 100644 --- a/v3/integrations/nrgrpc/go.mod +++ b/v3/integrations/nrgrpc/go.mod @@ -5,11 +5,9 @@ module github.com/newrelic/go-agent/v3/integrations/nrgrpc go 1.11 require ( - github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect // protobuf v1.3.0 is the earliest version using modules, we use v1.3.1 // because all dependencies were removed in this version. github.com/golang/protobuf v1.3.3 - github.com/kisielk/gotool v1.0.0 // indirect github.com/newrelic/go-agent/v3 v3.12.0 // v1.15.0 is the earliest version of grpc using modules. google.golang.org/grpc v1.27.0