From 653e6410eda0a1b0dfca56a06b79f396558b6a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Foray?= Date: Fri, 4 Nov 2022 17:27:31 +0100 Subject: [PATCH 01/12] fix usage examples of nrawssdk.AppendMiddlewares --- v3/integrations/nrawssdk-v2/example/main.go | 11 +++-- v3/integrations/nrawssdk-v2/nrawssdk.go | 52 +++++++++++++++----- v3/integrations/nrawssdk-v2/nrawssdk_test.go | 7 +-- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/v3/integrations/nrawssdk-v2/example/main.go b/v3/integrations/nrawssdk-v2/example/main.go index 3f740302e..204cad03b 100644 --- a/v3/integrations/nrawssdk-v2/example/main.go +++ b/v3/integrations/nrawssdk-v2/example/main.go @@ -10,7 +10,7 @@ import ( "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/integrations/nrawssdk-v2" "github.com/newrelic/go-agent/v3/newrelic" ) @@ -39,14 +39,15 @@ func main() { txn := app.StartTransaction("My sample transaction") ctx := context.Background() - awsConfig, err := config.LoadDefaultConfig(ctx) + awsConfig, err := config.LoadDefaultConfig(ctx, func(awsConfig *config.LoadOptions) error { + // Instrument all new AWS clients with New Relic + nrawssdk.AppendMiddlewares(&awsConfig.APIOptions, nil) + return nil + }) if err != nil { log.Fatal(err) } - // Instrument all new AWS clients with New Relic - nraws.AppendMiddlewares(&awsConfig.APIOptions, nil) - s3Client := s3.NewFromConfig(awsConfig) output, err := s3Client.ListBuckets(ctx, nil) if err != nil { diff --git a/v3/integrations/nrawssdk-v2/nrawssdk.go b/v3/integrations/nrawssdk-v2/nrawssdk.go index 8ff3a8ab6..763792941 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk.go @@ -120,24 +120,50 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { // To see segments and spans for all AWS invocations, call AppendMiddlewares // with the AWS Config `apiOptions` and provide nil for `txn`. For example: // -// awsConfig, err := config.LoadDefaultConfig(ctx) -// if err != nil { -// log.Fatal(err) -// } -// nraws.AppendMiddlewares(&awsConfig.APIOptions, nil) +// awsConfig, err := config.LoadDefaultConfig(ctx, func(o *config.LoadOptions) error { +// // Instrument all new AWS clients with New Relic +// nrawssdk.AppendMiddlewares(&o.APIOptions, nil) +// return nil +// }) +// if err != nil { +// log.Fatal(err) +// } // -// If do not want the transaction to be retrived from the context, you can +// If do not want the transaction to be retrieved from the context, you can // explicitly set `txn`. For example: // -// awsConfig, err := config.LoadDefaultConfig(ctx) -// if err != nil { -// log.Fatal(err) -// } +// txn := loadNewRelicTransaction() +// awsConfig, err := config.LoadDefaultConfig(ctx, func(o *config.LoadOptions) error { +// // Instrument all new AWS clients with New Relic +// nrawssdk.AppendMiddlewares(&o.APIOptions, txn) +// return nil +// }) +// if err != nil { +// log.Fatal(err) +// } // -// ... +// The middleware can also be added later, per AWS service call using +// the `optFns` parameter. For example: // -// txn := loadNewRelicTransaction() -// nraws.AppendMiddlewares(&awsConfig.APIOptions, txn) +// awsConfig, err := config.LoadDefaultConfig(ctx) +// if err != nil { +// log.Fatal(err) +// } +// +// ... +// +// s3Client := s3.NewFromConfig(awsConfig) +// +// ... +// +// txn := loadNewRelicTransaction() +// output, err := s3Client.ListBuckets(ctx, nil, func(o *config.LoadOptions) error { +// nrawssdk.AppendMiddlewares(&o.APIOptions, txn) +// return nil +// }) +// if err != nil { +// log.Fatal(err) +// } 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 c57bba514..ea1c18a05 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk_test.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk_test.go @@ -60,15 +60,16 @@ var fakeCreds = func() interface{} { }() func newConfig(ctx context.Context, txn *newrelic.Transaction) aws.Config { - cfg, _ := config.LoadDefaultConfig(ctx) + cfg, _ := config.LoadDefaultConfig(ctx, func(o *config.LoadOptions) error { + AppendMiddlewares(&o.APIOptions, txn) + return nil + }) cfg.Credentials = fakeCreds.(aws.CredentialsProvider) cfg.Region = awsRegion cfg.HTTPClient = &http.Client{ Transport: &fakeTransport{}, } - AppendMiddlewares(&cfg.APIOptions, txn) - return cfg } From 441b3a99137c2780e1014201e02307ade22b3448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Foray?= Date: Fri, 4 Nov 2022 17:35:00 +0100 Subject: [PATCH 02/12] fixup! fix usage examples of nrawssdk.AppendMiddlewares --- v3/integrations/nrawssdk-v2/nrawssdk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/integrations/nrawssdk-v2/nrawssdk.go b/v3/integrations/nrawssdk-v2/nrawssdk.go index 763792941..a6c151588 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk.go @@ -157,7 +157,7 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { // ... // // txn := loadNewRelicTransaction() -// output, err := s3Client.ListBuckets(ctx, nil, func(o *config.LoadOptions) error { +// output, err := s3Client.ListBuckets(ctx, nil, func(o *s3.Options) error { // nrawssdk.AppendMiddlewares(&o.APIOptions, txn) // return nil // }) From 4ae7fb9b261c9bf1c5ef428060e27c55edfd7973 Mon Sep 17 00:00:00 2001 From: mirackara Date: Wed, 21 Aug 2024 16:06:46 -0500 Subject: [PATCH 03/12] initial commit --- v3/integrations/nrawssdk-v2/go.mod | 32 +++++++- v3/integrations/nrawssdk-v2/nrawssdk.go | 99 ++++++++++++++++++++++--- v3/newrelic/attributes.go | 9 +++ v3/newrelic/attributes_from_internal.go | 4 + 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/v3/integrations/nrawssdk-v2/go.mod b/v3/integrations/nrawssdk-v2/go.mod index 2a81df60a..a1b70ec45 100644 --- a/v3/integrations/nrawssdk-v2/go.mod +++ b/v3/integrations/nrawssdk-v2/go.mod @@ -2,17 +2,41 @@ module github.com/newrelic/go-agent/v3/integrations/nrawssdk-v2 // 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.20 +go 1.21 + +toolchain go1.21.0 require ( - github.com/aws/aws-sdk-go-v2 v1.16.15 + github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2 v1.30.4 github.com/aws/aws-sdk-go-v2/config v1.17.6 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.0 github.com/aws/aws-sdk-go-v2/service/lambda v1.24.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.27.10 - github.com/aws/smithy-go v1.13.3 + github.com/aws/aws-sdk-go-v2/service/sqs v1.34.4 + github.com/aws/smithy-go v1.20.4 github.com/newrelic/go-agent/v3 v3.33.1 ) +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.12.19 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.16 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.22 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.18 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect +) replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrawssdk-v2/nrawssdk.go b/v3/integrations/nrawssdk-v2/nrawssdk.go index 8ff3a8ab6..b3fcfd1dd 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk.go @@ -28,9 +28,14 @@ package nrawssdk import ( "context" + "net/url" "strconv" + "strings" + "github.com/aws/aws-sdk-go-v2/aws" awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware" + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/aws/smithy-go/middleware" smithymiddle "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/newrelic/go-agent/v3/internal/integrationsupport" @@ -41,6 +46,11 @@ type nrMiddleware struct { txn *newrelic.Transaction } +// Context key for SQS service queue +type contextKey string + +const queueURLKey contextKey = "QueueURL" + type endable interface{ End() } // See https://aws.github.io/aws-sdk-go-v2/docs/middleware/ for a description of @@ -88,6 +98,24 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { response, ok := out.RawResponse.(*smithyhttp.Response) if ok { + if serviceName == "sqs" || serviceName == "SQS" { + if queueURL, ok := ctx.Value(queueURLKey).(string); ok { + parsedURL, err := url.Parse(queueURL) + if err == nil { + // Example URL: https://sqs.{region}.amazonaws.com/{account.id}/{queue.name} + pathParts := strings.Split(parsedURL.Path, "/") + if len(pathParts) >= 3 { + accountID := pathParts[1] + queueName := pathParts[2] + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeCloudAccountID, accountID) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeCloudRegion, region) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageSystem, "aws_sqs") + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageDestinationName, queueName) + } + } + + } + } // Set additional span attributes integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeResponseCode, strconv.Itoa(response.StatusCode)) @@ -107,6 +135,51 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { smithymiddle.Before) } +func (m nrMiddleware) serializeMiddleware(stack *middleware.Stack) error { + return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("NRSerializeMiddleware", func( + ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( + out middleware.InitializeOutput, metadata middleware.Metadata, err error) { + + serviceName := awsmiddle.GetServiceID(ctx) + if serviceName == "sqs" || serviceName == "SQS" { + QueueURL := "" + switch params := in.Parameters.(type) { + case *sqs.SendMessageInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.DeleteQueueInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.ReceiveMessageInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.DeleteMessageInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.ChangeMessageVisibilityInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.ChangeMessageVisibilityBatchInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.DeleteMessageBatchInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.SendMessageBatchInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.PurgeQueueInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.GetQueueAttributesInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.SetQueueAttributesInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.TagQueueInput: + QueueURL = aws.ToString(params.QueueUrl) + case *sqs.UntagQueueInput: + QueueURL = aws.ToString(params.QueueUrl) + default: + QueueURL = "" + } + // Store the QueueURL in the context + ctx = context.WithValue(ctx, queueURLKey, QueueURL) + } + return next.HandleInitialize(ctx, in) + }), middleware.After) +} + // 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. // @@ -120,25 +193,27 @@ func (m nrMiddleware) deserializeMiddleware(stack *smithymiddle.Stack) error { // To see segments and spans for all AWS invocations, call AppendMiddlewares // with the AWS Config `apiOptions` and provide nil for `txn`. For example: // -// awsConfig, err := config.LoadDefaultConfig(ctx) -// if err != nil { -// log.Fatal(err) -// } -// nraws.AppendMiddlewares(&awsConfig.APIOptions, nil) +// awsConfig, err := config.LoadDefaultConfig(ctx) +// if err != nil { +// log.Fatal(err) +// } +// nraws.AppendMiddlewares(&awsConfig.APIOptions, nil) // // If do not want the transaction to be retrived from the context, you can // explicitly set `txn`. For example: // -// awsConfig, err := config.LoadDefaultConfig(ctx) -// if err != nil { -// log.Fatal(err) -// } +// awsConfig, err := config.LoadDefaultConfig(ctx) +// if err != nil { +// log.Fatal(err) +// } // -// ... +// ... // -// txn := loadNewRelicTransaction() -// nraws.AppendMiddlewares(&awsConfig.APIOptions, txn) +// txn := loadNewRelicTransaction() +// nraws.AppendMiddlewares(&awsConfig.APIOptions, txn) func AppendMiddlewares(apiOptions *[]func(*smithymiddle.Stack) error, txn *newrelic.Transaction) { m := nrMiddleware{txn: txn} *apiOptions = append(*apiOptions, m.deserializeMiddleware) + *apiOptions = append(*apiOptions, m.serializeMiddleware) + } diff --git a/v3/newrelic/attributes.go b/v3/newrelic/attributes.go index 2fd2f8d09..e4de3f893 100644 --- a/v3/newrelic/attributes.go +++ b/v3/newrelic/attributes.go @@ -118,6 +118,15 @@ const ( // // It is recommended that at most one message is consumed per transaction. const ( + // The account ID of a cloud service provider + AttributeCloudAccountID = "cloud.account.id" + // The region of a cloud service provider + AttributeCloudRegion = "cloud.region" + // The name of the messaging system + AttributeMessageSystem = "messaging.system" + // The name of the messagine broker destination + AttributeMessageDestinationName = "message.destination.name" + // The routing key of the consumed message. AttributeMessageRoutingKey = "message.routingKey" // The name of the queue the message was consumed from. diff --git a/v3/newrelic/attributes_from_internal.go b/v3/newrelic/attributes_from_internal.go index 430c5752a..2b51cb741 100644 --- a/v3/newrelic/attributes_from_internal.go +++ b/v3/newrelic/attributes_from_internal.go @@ -34,6 +34,10 @@ var ( // attributes.go and add its default destinations here. // agentAttributeDefaultDests = map[string]destinationSet{ + AttributeCloudAccountID: usualDests, + AttributeMessageDestinationName: usualDests, + AttributeCloudRegion: usualDests, + AttributeMessageSystem: usualDests, AttributeHostDisplayName: usualDests, AttributeRequestMethod: usualDests, AttributeRequestAccept: usualDests, From 139cd89721341cd1186fae9c9f8b26f97fbfd0ed Mon Sep 17 00:00:00 2001 From: mirackara Date: Tue, 27 Aug 2024 14:14:17 -0500 Subject: [PATCH 04/12] support server.address span attributes for amqp --- v3/integrations/nramqp/examples/publisher/main.go | 10 +++++++--- v3/integrations/nramqp/nramqp.go | 15 ++++++++------- v3/newrelic/attributes.go | 1 + v3/newrelic/attributes_from_internal.go | 1 + 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/v3/integrations/nramqp/examples/publisher/main.go b/v3/integrations/nramqp/examples/publisher/main.go index 445947a08..295071b82 100644 --- a/v3/integrations/nramqp/examples/publisher/main.go +++ b/v3/integrations/nramqp/examples/publisher/main.go @@ -40,13 +40,15 @@ type amqpServer struct { ch *amqp.Channel exchange string routingKey string + url string } -func NewServer(channel *amqp.Channel, exchangeName, routingKeyName string) *amqpServer { +func NewServer(channel *amqp.Channel, exchangeName, routingKeyName string, url string) *amqpServer { return &amqpServer{ channel, exchangeName, routingKeyName, + url, } } @@ -65,6 +67,7 @@ func (serv *amqpServer) publishPlainTxtMessage(w http.ResponseWriter, r *http.Re ctx, serv.exchange, // exchange serv.routingKey, // routing key + serv.url, // url false, // mandatory false, // immediate amqp.Publishing{ @@ -94,7 +97,8 @@ func main() { nrApp.WaitForConnection(time.Second * 5) - conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + amqpURL := "amqp://guest:guest@localhost:5672/" + conn, err := amqp.Dial(amqpURL) failOnError(err, "Failed to connect to RabbitMQ") defer conn.Close() @@ -112,7 +116,7 @@ func main() { ) failOnError(err, "Failed to declare a queue") - server := NewServer(ch, "", q.Name) + server := NewServer(ch, "", q.Name, amqpURL) http.HandleFunc(newrelic.WrapHandleFunc(nrApp, "/", server.index)) http.HandleFunc(newrelic.WrapHandleFunc(nrApp, "/message", server.publishPlainTxtMessage)) diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go index 2be8e7634..a4a72d057 100644 --- a/v3/integrations/nramqp/nramqp.go +++ b/v3/integrations/nramqp/nramqp.go @@ -16,7 +16,7 @@ const ( func init() { internal.TrackUsage("integration", "messagebroker", "nramqp") } -func creatProducerSegment(exchange, key string) *newrelic.MessageProducerSegment { +func createProducerSegment(exchange, key string) *newrelic.MessageProducerSegment { s := newrelic.MessageProducerSegment{ Library: RabbitMQLibrary, DestinationName: "Default", @@ -35,11 +35,11 @@ func creatProducerSegment(exchange, key string) *newrelic.MessageProducerSegment // PublishedWithContext looks for a newrelic transaction in the context object, and if found, creates a message producer segment. // It will also inject distributed tracing headers into the message. -func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error { +func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key, url string, mandatory, immediate bool, msg amqp.Publishing) error { txn := newrelic.FromContext(ctx) if txn != nil { // generate message broker segment - s := creatProducerSegment(exchange, key) + s := createProducerSegment(exchange, key) // capture telemetry for AMQP producer if msg.Headers != nil && len(msg.Headers) > 0 { @@ -49,15 +49,16 @@ func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key str } integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageHeaders, hdrStr) } + s.StartTime = txn.StartSegmentNow() + + // inject DT headers into headers object + msg.Headers = injectDtHeaders(txn, msg.Headers) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.SpanAttributeServerAddress, url) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageRoutingKey, key) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageCorrelationID, msg.CorrelationId) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageReplyTo, msg.ReplyTo) - // inject DT headers into headers object - msg.Headers = injectDtHeaders(txn, msg.Headers) - - s.StartTime = txn.StartSegmentNow() err := ch.PublishWithContext(ctx, exchange, key, mandatory, immediate, msg) s.End() return err diff --git a/v3/newrelic/attributes.go b/v3/newrelic/attributes.go index e4de3f893..e5728ada5 100644 --- a/v3/newrelic/attributes.go +++ b/v3/newrelic/attributes.go @@ -152,6 +152,7 @@ const ( // cfg.SpanEvents.Attributes.Exclude = append(cfg.SpanEvents.Attributes.Exclude, // newrelic.SpanAttributeDBStatement) const ( + SpanAttributeServerAddress = "server.address" SpanAttributeDBStatement = "db.statement" SpanAttributeDBInstance = "db.instance" SpanAttributeDBCollection = "db.collection" diff --git a/v3/newrelic/attributes_from_internal.go b/v3/newrelic/attributes_from_internal.go index 2b51cb741..d128578d7 100644 --- a/v3/newrelic/attributes_from_internal.go +++ b/v3/newrelic/attributes_from_internal.go @@ -70,6 +70,7 @@ var ( AttributeLLM: usualDests, // Span specific attributes + SpanAttributeServerAddress: usualDests, SpanAttributeDBStatement: usualDests, SpanAttributeDBInstance: usualDests, SpanAttributeDBCollection: usualDests, From 91300a205791437e0587a4f5388a74f39855db32 Mon Sep 17 00:00:00 2001 From: mirackara Date: Wed, 28 Aug 2024 16:37:57 -0500 Subject: [PATCH 05/12] Updated amqp to align with new span attributes --- .../nramqp/examples/consumer/main.go | 3 +- v3/integrations/nramqp/nramqp.go | 27 ++++++- v3/newrelic/attributes.go | 13 +++- v3/newrelic/attributes_from_internal.go | 75 ++++++++++--------- 4 files changed, 77 insertions(+), 41 deletions(-) diff --git a/v3/integrations/nramqp/examples/consumer/main.go b/v3/integrations/nramqp/examples/consumer/main.go index 5cfc92ec4..ae44c4b85 100644 --- a/v3/integrations/nramqp/examples/consumer/main.go +++ b/v3/integrations/nramqp/examples/consumer/main.go @@ -32,7 +32,8 @@ func main() { nrApp.WaitForConnection(time.Second * 5) - conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + amqpURL := "amqp://guest:guest@localhost:5672/" + conn, err := amqp.Dial(amqpURL) failOnError(err, "Failed to connect to RabbitMQ") defer conn.Close() diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go index a4a72d057..5ceee63e9 100644 --- a/v3/integrations/nramqp/nramqp.go +++ b/v3/integrations/nramqp/nramqp.go @@ -2,6 +2,7 @@ package nramqp import ( "context" + "strings" amqp "github.com/rabbitmq/amqp091-go" @@ -33,9 +34,25 @@ func createProducerSegment(exchange, key string) *newrelic.MessageProducerSegmen return &s } +func GetHostAndPortFromURL(url string) (string, string) { + // url is of format amqp://user:password@host:port + // dynamically extract host from url after "@" symbol + parts := strings.Split(url, "@") + if len(parts) != 2 { + return "", "" + } + strippedURL := strings.Split(parts[1], ":") + + if len(strippedURL) != 2 { + return "", "" + } + return strippedURL[0], strippedURL[1] +} + // PublishedWithContext looks for a newrelic transaction in the context object, and if found, creates a message producer segment. // It will also inject distributed tracing headers into the message. func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key, url string, mandatory, immediate bool, msg amqp.Publishing) error { + host, port := GetHostAndPortFromURL(url) txn := newrelic.FromContext(ctx) if txn != nil { // generate message broker segment @@ -53,8 +70,10 @@ func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key, ur // inject DT headers into headers object msg.Headers = injectDtHeaders(txn, msg.Headers) - - integrationsupport.AddAgentSpanAttribute(txn, newrelic.SpanAttributeServerAddress, url) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeSpanKind, "producer") + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeServerAddress, host) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeServerPort, port) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageDestinationName, exchange) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageRoutingKey, key) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageCorrelationID, msg.CorrelationId) integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageReplyTo, msg.ReplyTo) @@ -92,8 +111,10 @@ func Consume(app *newrelic.Application, ch *amqp.Channel, queue, consumer string integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageHeaders, hdrStr, nil) } } - + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeSpanKind, "consumer", nil) integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageQueueName, queue, nil) + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageDestinationName, queue, nil) + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessagingDestinationPublishName, delivery.Exchange, nil) integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageRoutingKey, delivery.RoutingKey, nil) integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageCorrelationID, delivery.CorrelationId, nil) integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageReplyTo, delivery.ReplyTo, nil) diff --git a/v3/newrelic/attributes.go b/v3/newrelic/attributes.go index e5728ada5..a3770b47f 100644 --- a/v3/newrelic/attributes.go +++ b/v3/newrelic/attributes.go @@ -140,6 +140,18 @@ const ( AttributeMessageCorrelationID = "message.correlationId" // The headers of the message without CAT keys/values AttributeMessageHeaders = "message.headers" + // Host identifier of the message broker + AttributeServerAddress = "server.address" + // Port number of the message broker + AttributeServerPort = "server.port" + // Will take on either the values "producer" or "consumer" + AttributeSpanKind = "span.kind" +) + +// Experimental OTEL Attributes for consumed message transactions +const ( + AttributeMessagingDestinationPublishName = "messaging.destination_publish.name" + AttributeRabbitMQDestinationRoutingKey = "messaging.rabbitmq.destination.routing_key" ) // Attributes destined for Span Events. These attributes appear only on Span @@ -152,7 +164,6 @@ const ( // cfg.SpanEvents.Attributes.Exclude = append(cfg.SpanEvents.Attributes.Exclude, // newrelic.SpanAttributeDBStatement) const ( - SpanAttributeServerAddress = "server.address" SpanAttributeDBStatement = "db.statement" SpanAttributeDBInstance = "db.instance" SpanAttributeDBCollection = "db.collection" diff --git a/v3/newrelic/attributes_from_internal.go b/v3/newrelic/attributes_from_internal.go index d128578d7..aabc6c6a1 100644 --- a/v3/newrelic/attributes_from_internal.go +++ b/v3/newrelic/attributes_from_internal.go @@ -34,43 +34,46 @@ var ( // attributes.go and add its default destinations here. // agentAttributeDefaultDests = map[string]destinationSet{ - AttributeCloudAccountID: usualDests, - AttributeMessageDestinationName: usualDests, - AttributeCloudRegion: usualDests, - AttributeMessageSystem: usualDests, - AttributeHostDisplayName: usualDests, - AttributeRequestMethod: usualDests, - AttributeRequestAccept: usualDests, - AttributeRequestContentType: usualDests, - AttributeRequestContentLength: usualDests, - AttributeRequestHost: usualDests, - AttributeRequestUserAgent: tracesDests, - AttributeRequestUserAgentDeprecated: tracesDests, - AttributeRequestReferer: tracesDests, - AttributeRequestURI: usualDests, - AttributeResponseContentType: usualDests, - AttributeResponseContentLength: usualDests, - AttributeResponseCode: usualDests, - AttributeResponseCodeDeprecated: usualDests, - AttributeAWSRequestID: usualDests, - AttributeAWSLambdaARN: usualDests, - AttributeAWSLambdaColdStart: usualDests, - AttributeAWSLambdaEventSourceARN: usualDests, - AttributeMessageRoutingKey: usualDests, - AttributeMessageQueueName: usualDests, - AttributeMessageHeaders: usualDests, - AttributeMessageExchangeType: destNone, - AttributeMessageReplyTo: destNone, - AttributeMessageCorrelationID: destNone, - AttributeCodeFunction: usualDests, - AttributeCodeNamespace: usualDests, - AttributeCodeFilepath: usualDests, - AttributeCodeLineno: usualDests, - AttributeUserID: usualDests, - AttributeLLM: usualDests, - + AttributeCloudAccountID: usualDests, + AttributeMessageDestinationName: usualDests, + AttributeCloudRegion: usualDests, + AttributeMessageSystem: usualDests, + AttributeHostDisplayName: usualDests, + AttributeRequestMethod: usualDests, + AttributeRequestAccept: usualDests, + AttributeRequestContentType: usualDests, + AttributeRequestContentLength: usualDests, + AttributeRequestHost: usualDests, + AttributeRequestUserAgent: tracesDests, + AttributeRequestUserAgentDeprecated: tracesDests, + AttributeRequestReferer: tracesDests, + AttributeRequestURI: usualDests, + AttributeResponseContentType: usualDests, + AttributeResponseContentLength: usualDests, + AttributeResponseCode: usualDests, + AttributeResponseCodeDeprecated: usualDests, + AttributeAWSRequestID: usualDests, + AttributeAWSLambdaARN: usualDests, + AttributeAWSLambdaColdStart: usualDests, + AttributeAWSLambdaEventSourceARN: usualDests, + AttributeMessageRoutingKey: usualDests, + AttributeMessageQueueName: usualDests, + AttributeMessageHeaders: usualDests, + AttributeMessageExchangeType: destNone, + AttributeMessageReplyTo: destNone, + AttributeMessageCorrelationID: destNone, + AttributeCodeFunction: usualDests, + AttributeCodeNamespace: usualDests, + AttributeCodeFilepath: usualDests, + AttributeCodeLineno: usualDests, + AttributeUserID: usualDests, + AttributeLLM: usualDests, + AttributeServerAddress: usualDests, + AttributeServerPort: usualDests, + AttributeSpanKind: usualDests, + AttributeMessagingDestinationPublishName: usualDests, + AttributeRabbitMQDestinationRoutingKey: usualDests, // Span specific attributes - SpanAttributeServerAddress: usualDests, SpanAttributeDBStatement: usualDests, SpanAttributeDBInstance: usualDests, SpanAttributeDBCollection: usualDests, From ef4c16ad2becabd90f5bbd46aaf7c374de05c707 Mon Sep 17 00:00:00 2001 From: mirackara Date: Thu, 5 Sep 2024 16:07:47 -0500 Subject: [PATCH 06/12] Add SQS test cases --- v3/integrations/nrawssdk-v2/go.mod | 32 ++- v3/integrations/nrawssdk-v2/nrawssdk_test.go | 283 +++++++++++++++++++ 2 files changed, 301 insertions(+), 14 deletions(-) diff --git a/v3/integrations/nrawssdk-v2/go.mod b/v3/integrations/nrawssdk-v2/go.mod index a1b70ec45..a286df982 100644 --- a/v3/integrations/nrawssdk-v2/go.mod +++ b/v3/integrations/nrawssdk-v2/go.mod @@ -7,28 +7,32 @@ go 1.21 toolchain go1.21.0 require ( - github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go-v2 v1.30.4 - github.com/aws/aws-sdk-go-v2/config v1.17.6 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.0 - github.com/aws/aws-sdk-go-v2/service/lambda v1.24.5 - github.com/aws/aws-sdk-go-v2/service/sqs v1.34.4 + github.com/aws/aws-sdk-go-v2/config v1.27.31 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.6 + github.com/aws/aws-sdk-go-v2/service/lambda v1.58.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 + github.com/aws/aws-sdk-go-v2/service/sqs v1.34.6 github.com/aws/smithy-go v1.20.4 github.com/newrelic/go-agent/v3 v3.33.1 ) require ( - github.com/aws/aws-sdk-go-v2/credentials v1.12.19 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.16 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.30 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.16 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.22 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.16.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect golang.org/x/net v0.9.0 // indirect diff --git a/v3/integrations/nrawssdk-v2/nrawssdk_test.go b/v3/integrations/nrawssdk-v2/nrawssdk_test.go index 79b1f389a..b7cbb68c9 100644 --- a/v3/integrations/nrawssdk-v2/nrawssdk_test.go +++ b/v3/integrations/nrawssdk-v2/nrawssdk_test.go @@ -17,9 +17,14 @@ import ( "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/aws/aws-sdk-go-v2/service/sqs" + "github.com/newrelic/go-agent/v3/internal" "github.com/newrelic/go-agent/v3/internal/integrationsupport" "github.com/newrelic/go-agent/v3/newrelic" + + sqstypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" ) func testApp() integrationsupport.ExpectApp { @@ -139,6 +144,28 @@ var ( "http.statusCode": "200", }, } + SQSSpan = internal.WantEvent{ + Intrinsics: map[string]interface{}{ + "name": "External/sqs.us-west-2.amazonaws.com/http/POST", + "category": "http", + "parentId": internal.MatchAnything, + "component": "http", + "span.kind": "client", + "sampled": true, + }, + UserAttributes: map[string]interface{}{}, + AgentAttributes: map[string]interface{}{ + "message.destination.name": "MyQueue", + "cloud.account.id": "123456789012", + "cloud.region": "us-west-2", + "http.url": "https://sqs.us-west-2.amazonaws.com/", + "http.method": "POST", + "messaging.system": "aws_sqs", + "aws.requestId": "testing request id", + "http.statusCode": "200", + "aws.region": "us-west-2", + }, + } datastoreSpan = internal.WantEvent{ Intrinsics: map[string]interface{}{ "name": "Datastore/operation/DynamoDB/DescribeTable", @@ -257,6 +284,262 @@ func TestInstrumentRequestExternal(t *testing.T) { ) } +type sqsTestTableEntry struct { + Name string + BuildContext func(txn *newrelic.Transaction) context.Context + BuildConfig func(ctx context.Context, txn *newrelic.Transaction) aws.Config + Input interface{} +} + +func runSQSTestTable(t *testing.T, entries []*sqsTestTableEntry, testFunc func(t *testing.T, entry *sqsTestTableEntry)) { + for _, entry := range entries { + t.Run(entry.Name, func(t *testing.T) { + testFunc(t, entry) + }) + } +} + +func TestSQSMiddleware(t *testing.T) { + runSQSTestTable(t, + []*sqsTestTableEntry{ + { + Name: "DeleteQueueInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.DeleteQueueInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue")}, + }, + { + Name: "ReceiveMessageInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.ReceiveMessageInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue")}, + }, + { + Name: "SendMessageInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.SendMessageInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), MessageBody: aws.String("Hello, world!")}, + }, + { + Name: "PurgeQueueInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.PurgeQueueInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue")}, + }, + { + Name: "DeleteMessageInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.DeleteMessageInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), ReceiptHandle: aws.String("receipt-handle")}, + }, + { + Name: "ChangeMessageVisibilityInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.ChangeMessageVisibilityInput{QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), ReceiptHandle: aws.String("receipt-handle"), VisibilityTimeout: 10}, + }, + + { + Name: "ChangeMessageVisibilityBatchInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.ChangeMessageVisibilityBatchInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + Entries: []sqstypes.ChangeMessageVisibilityBatchRequestEntry{ + { + Id: aws.String("id1"), + ReceiptHandle: aws.String("receipt-handle"), + VisibilityTimeout: 10, + }, + }, + }, + }, + { + Name: "DeleteMessageBatchInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.DeleteMessageBatchInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + Entries: []sqstypes.DeleteMessageBatchRequestEntry{ + { + Id: aws.String("id1"), + ReceiptHandle: aws.String("receipt-handle"), + }, + }, + }, + }, + { + Name: "SendMessageBatchInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.SendMessageBatchInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + Entries: []sqstypes.SendMessageBatchRequestEntry{ + { + Id: aws.String("id1"), + MessageBody: aws.String("Hello, world!"), + }, + }, + }, + }, + { + Name: "GetQueueAttributesInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.GetQueueAttributesInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + AttributeNames: []sqstypes.QueueAttributeName{ + "ApproximateNumberOfMessages", + }, + }, + }, + { + Name: "SetQueueAttributesInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.SetQueueAttributesInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + Attributes: map[string]string{ + "VisibilityTimeout": "10", + }, + }, + }, + { + Name: "TagQueueInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.TagQueueInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + Tags: map[string]string{ + "tag1": "value1", + }, + }, + }, + { + Name: "UntagQueueInput", + BuildContext: func(txn *newrelic.Transaction) context.Context { + return context.Background() + }, + BuildConfig: func(ctx context.Context, txn *newrelic.Transaction) aws.Config { + return newConfig(ctx, txn) + }, + Input: &sqs.UntagQueueInput{ + QueueUrl: aws.String("https://sqs.us-west-2.amazonaws.com/123456789012/MyQueue"), + TagKeys: []string{"tag1"}, + }, + }, + }, + + func(t *testing.T, entry *sqsTestTableEntry) { + app := testApp() + txn := app.StartTransaction(txnName) + ctx := entry.BuildContext(txn) + awsOp := "" + client := sqs.NewFromConfig(entry.BuildConfig(ctx, txn)) + switch input := entry.Input.(type) { + case *sqs.SendMessageInput: + client.SendMessage(ctx, input) + awsOp = "SendMessage" + case *sqs.DeleteQueueInput: + client.DeleteQueue(ctx, input) + awsOp = "DeleteQueue" + case *sqs.ReceiveMessageInput: + client.ReceiveMessage(ctx, input) + awsOp = "ReceiveMessage" + case *sqs.DeleteMessageInput: + client.DeleteMessage(ctx, input) + awsOp = "DeleteMessage" + case *sqs.ChangeMessageVisibilityInput: + client.ChangeMessageVisibility(ctx, input) + awsOp = "ChangeMessageVisibility" + case *sqs.ChangeMessageVisibilityBatchInput: + client.ChangeMessageVisibilityBatch(ctx, input) + awsOp = "ChangeMessageVisibilityBatch" + case *sqs.DeleteMessageBatchInput: + client.DeleteMessageBatch(ctx, input) + awsOp = "DeleteMessageBatch" + case *sqs.PurgeQueueInput: + client.PurgeQueue(ctx, input) + awsOp = "PurgeQueue" + case *sqs.GetQueueAttributesInput: + client.GetQueueAttributes(ctx, input) + awsOp = "GetQueueAttributes" + case *sqs.SetQueueAttributesInput: + client.SetQueueAttributes(ctx, input) + awsOp = "SetQueueAttributes" + case *sqs.TagQueueInput: + client.TagQueue(ctx, input) + awsOp = "TagQueue" + case *sqs.UntagQueueInput: + client.UntagQueue(ctx, input) + awsOp = "UntagQueue" + case *sqs.SendMessageBatchInput: + client.SendMessageBatch(ctx, input) + awsOp = "SendMessageBatch" + + default: + t.Errorf("unexpected input type: %T", input) + + } + + txn.End() + SQSSpanModified := SQSSpan + SQSSpanModified.AgentAttributes["aws.operation"] = awsOp + app.ExpectSpanEvents(t, []internal.WantEvent{ + SQSSpan, genericSpan}) + + }, + ) +} + func TestInstrumentRequestDatastore(t *testing.T) { runTestTable(t, []*testTableEntry{ From c0079fcef6d37d143da094ae7b334bdb44c0a753 Mon Sep 17 00:00:00 2001 From: mirackara Date: Thu, 5 Sep 2024 16:30:06 -0500 Subject: [PATCH 07/12] Added tests for amqp --- v3/integrations/nramqp/nramqp.go | 2 +- v3/integrations/nramqp/nramqp_test.go | 52 +++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go index 5ceee63e9..3094d5429 100644 --- a/v3/integrations/nramqp/nramqp.go +++ b/v3/integrations/nramqp/nramqp.go @@ -36,7 +36,7 @@ func createProducerSegment(exchange, key string) *newrelic.MessageProducerSegmen func GetHostAndPortFromURL(url string) (string, string) { // url is of format amqp://user:password@host:port - // dynamically extract host from url after "@" symbol + // extract host from url after "@" symbol parts := strings.Split(url, "@") if len(parts) != 2 { return "", "" diff --git a/v3/integrations/nramqp/nramqp_test.go b/v3/integrations/nramqp/nramqp_test.go index 3db9e4ce9..cd7a41feb 100644 --- a/v3/integrations/nramqp/nramqp_test.go +++ b/v3/integrations/nramqp/nramqp_test.go @@ -15,7 +15,7 @@ func BenchmarkCreateProducerSegment(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - creatProducerSegment("exchange", "key") + createProducerSegment("exchange", "key") } } @@ -66,7 +66,7 @@ func TestCreateProducerSegment(t *testing.T) { } for _, test := range tests { - s := creatProducerSegment(test.exchange, test.key) + s := createProducerSegment(test.exchange, test.key) if s.DestinationName != test.expect.DestinationName { t.Errorf("expected destination name %s, got %s", test.expect.DestinationName, s.DestinationName) } @@ -76,3 +76,51 @@ func TestCreateProducerSegment(t *testing.T) { } } + +func TestPublishWithContext(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + +} + +func TestHostAndPortParsing(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + + type testObject struct { + url string + expectHost string + expectPort string + } + + tests := []testObject{ + { + "amqp://user:password@host:port", + "host", + "port", + }, + { + "amqp://user:password@host", + "", + "", + }, + { + "amqp://user:password@host:port:extra", + "", + "", + }, + } + + for _, test := range tests { + host, port := GetHostAndPortFromURL(test.url) + if host != test.expectHost { + t.Errorf("expected host %s, got %s", test.expectHost, host) + } + if port != test.expectPort { + t.Errorf("expected port %s, got %s", test.expectPort, port) + } + } + +} From 2454e148927689f2d8350e0e96ce338a9ca9b062 Mon Sep 17 00:00:00 2001 From: mirackara Date: Mon, 9 Sep 2024 12:45:09 -0500 Subject: [PATCH 08/12] Add support for no user:pass in amqp url --- v3/integrations/nramqp/nramqp.go | 17 +++++++++++------ v3/integrations/nramqp/nramqp_test.go | 18 +++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go index 3094d5429..8e2094dbe 100644 --- a/v3/integrations/nramqp/nramqp.go +++ b/v3/integrations/nramqp/nramqp.go @@ -35,14 +35,19 @@ func createProducerSegment(exchange, key string) *newrelic.MessageProducerSegmen } func GetHostAndPortFromURL(url string) (string, string) { - // url is of format amqp://user:password@host:port - // extract host from url after "@" symbol - parts := strings.Split(url, "@") - if len(parts) != 2 { - return "", "" + // url is of format amqp://user:password@host:port or amqp://host:port + var hostPortPart string + + // extract the part after "@" symbol, if present + if parts := strings.Split(url, "@"); len(parts) == 2 { + hostPortPart = parts[1] + } else { + // assume the whole url after "amqp://" is the host:port part + hostPortPart = strings.TrimPrefix(url, "amqp://") } - strippedURL := strings.Split(parts[1], ":") + // split the host:port part + strippedURL := strings.Split(hostPortPart, ":") if len(strippedURL) != 2 { return "", "" } diff --git a/v3/integrations/nramqp/nramqp_test.go b/v3/integrations/nramqp/nramqp_test.go index cd7a41feb..213a4cc5c 100644 --- a/v3/integrations/nramqp/nramqp_test.go +++ b/v3/integrations/nramqp/nramqp_test.go @@ -77,13 +77,6 @@ func TestCreateProducerSegment(t *testing.T) { } -func TestPublishWithContext(t *testing.T) { - app := createTestApp() - txn := app.StartTransaction("test") - defer txn.End() - -} - func TestHostAndPortParsing(t *testing.T) { app := createTestApp() txn := app.StartTransaction("test") @@ -101,6 +94,17 @@ func TestHostAndPortParsing(t *testing.T) { "host", "port", }, + { + "amqp://host:port", + "host", + "port", + }, + { + "aaa://host:port", + "", + "", + }, + { "amqp://user:password@host", "", From fa564c7dc024bcb3666d373d112e0f00f16c9c8b Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Tue, 17 Sep 2024 20:59:35 -0700 Subject: [PATCH 09/12] corrected example import line --- v3/integrations/nrzerolog/example_test.go | 3 ++- v3/integrations/nrzerolog/go.mod | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/v3/integrations/nrzerolog/example_test.go b/v3/integrations/nrzerolog/example_test.go index 22e9e7b42..33ca22256 100644 --- a/v3/integrations/nrzerolog/example_test.go +++ b/v3/integrations/nrzerolog/example_test.go @@ -6,7 +6,8 @@ package nrzerolog_test import ( "os" - newrelic "github.com/newrelic/go-agent" + "github.com/newrelic/go-agent/v3/integrations/nrzerolog" + "github.com/newrelic/go-agent/v3/newrelic" "github.com/rs/zerolog" ) diff --git a/v3/integrations/nrzerolog/go.mod b/v3/integrations/nrzerolog/go.mod index e2eb6cf41..37e0b2926 100644 --- a/v3/integrations/nrzerolog/go.mod +++ b/v3/integrations/nrzerolog/go.mod @@ -3,6 +3,6 @@ module github.com/newrelic/go-agent/v3/integrations/nrzerolog go 1.19 require ( - github.com/newrelic/go-agent/v3 v3.20.2 + github.com/newrelic/go-agent/v3 v3.34.0 github.com/rs/zerolog v1.28.0 ) From 8330dac5bc23da34944343046368763ac8ff4588 Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Tue, 17 Sep 2024 21:17:00 -0700 Subject: [PATCH 10/12] fix go versions in test workflow --- .github/workflows/ci.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9780de023..f7972bb14 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,14 +15,12 @@ jobs: matrix: include: # Core Tests on 3 most recent major Go versions - - go-version: 1.20.14 + - go-version: 1.21.13 dirs: v3/newrelic,v3/internal,v3/examples - - go-version: 1.21 + - go-version: 1.22.7 dirs: v3/newrelic,v3/internal,v3/examples - go-version: latest dirs: v3/newrelic,v3/internal,v3/examples - - go-version: 1.23rc2 - dirs: v3/newrelic,v3/internal,v3/examples # Integration Tests on highest Supported Go Version - dirs: v3/integrations/nramqp @@ -109,9 +107,9 @@ jobs: matrix: include: # Core Tests on 3 most recent major Go versions - - go-version: 1.20.14 + - go-version: 1.21.13 dirs: v3/newrelic,v3/internal,v3/examples - - go-version: 1.21 + - go-version: 1.22.7 dirs: v3/newrelic,v3/internal,v3/examples - go-version: latest dirs: v3/newrelic,v3/internal,v3/examples From f56aa0c06cf9f5f0707aed2947c641b2b5795177 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 1 Oct 2024 10:11:32 +0200 Subject: [PATCH 11/12] Fix `GOOS=js` builds with nops --- v3/internal/sysinfo/memtotal_js.go | 11 +++++++++++ v3/internal/sysinfo/usage_js.go | 11 +++++++++++ v3/internal/sysinfo/usage_posix.go | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 v3/internal/sysinfo/memtotal_js.go create mode 100644 v3/internal/sysinfo/usage_js.go diff --git a/v3/internal/sysinfo/memtotal_js.go b/v3/internal/sysinfo/memtotal_js.go new file mode 100644 index 000000000..a0d81766f --- /dev/null +++ b/v3/internal/sysinfo/memtotal_js.go @@ -0,0 +1,11 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package sysinfo + +import "errors" + +// PhysicalMemoryBytes returns the total amount of host memory. +func PhysicalMemoryBytes() (uint64, error) { + return 0, errors.New("not supported on GOOS=js") +} diff --git a/v3/internal/sysinfo/usage_js.go b/v3/internal/sysinfo/usage_js.go new file mode 100644 index 000000000..74f8ce160 --- /dev/null +++ b/v3/internal/sysinfo/usage_js.go @@ -0,0 +1,11 @@ +// Copyright 2020 New Relic Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package sysinfo + +import "errors" + +// GetUsage gathers process times. +func GetUsage() (Usage, error) { + return Usage{}, errors.New("not supported on GOOS=js") +} diff --git a/v3/internal/sysinfo/usage_posix.go b/v3/internal/sysinfo/usage_posix.go index 4d758dea1..5b553fd90 100644 --- a/v3/internal/sysinfo/usage_posix.go +++ b/v3/internal/sysinfo/usage_posix.go @@ -1,7 +1,7 @@ // Copyright 2020 New Relic Corporation. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -// +build !windows +// +build !windows,!js package sysinfo From 7a38380bf5541dd0691423a58858034be72b872e Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Thu, 10 Oct 2024 08:04:05 -0700 Subject: [PATCH 12/12] fix integer size issues --- v3/integrations/nrlogxi/nrlogxi.go | 6 ++++++ v3/newrelic/utilities.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/v3/integrations/nrlogxi/nrlogxi.go b/v3/integrations/nrlogxi/nrlogxi.go index 8f0f4bdd8..121984a89 100644 --- a/v3/integrations/nrlogxi/nrlogxi.go +++ b/v3/integrations/nrlogxi/nrlogxi.go @@ -8,6 +8,8 @@ package nrlogxi import ( + "math" + log "github.com/mgutz/logxi/v1" "github.com/newrelic/go-agent/v3/internal" newrelic "github.com/newrelic/go-agent/v3/newrelic" @@ -36,6 +38,10 @@ func (l *shim) DebugEnabled() bool { } func convert(c map[string]interface{}) []interface{} { + if len(c) >= math.MaxInt32/2 { + return []interface{} + } + output := make([]interface{}, 0, 2*len(c)) for k, v := range c { output = append(output, k, v) diff --git a/v3/newrelic/utilities.go b/v3/newrelic/utilities.go index 9458110f8..69e776000 100644 --- a/v3/newrelic/utilities.go +++ b/v3/newrelic/utilities.go @@ -83,8 +83,8 @@ func stringLengthByteLimit(str string, byteLimit int) string { } func timeFromUnixMilliseconds(millis uint64) time.Time { - secs := int64(millis) / 1000 - msecsRemaining := int64(millis) % 1000 + secs := int64(millis / 1000) + msecsRemaining := int64(millis % 1000) nsecsRemaining := msecsRemaining * (1000 * 1000) return time.Unix(secs, nsecsRemaining) }