From 9500d4d52fe583015799bdb02e9b2585b3769b6f Mon Sep 17 00:00:00 2001 From: Chris Hynes <111462425+chynesNR@users.noreply.github.com> Date: Fri, 20 Oct 2023 08:44:27 -0700 Subject: [PATCH] summary: Add new span attributes to more closely match OTel spec. (#1976) feat: Add new span attributes to more closely match OTel spec. This includes server.address and server.port for database calls, and thread.id where appropriate. --- .../Attributes/AttributeDefinitionService.cs | 51 +++++++++++++++++- .../Core/Segments/DatastoreSegmentData.cs | 15 +++++- .../Core/Segments/ExternalSegmentData.cs | 2 + .../NewRelic/Agent/Core/Segments/Segment.cs | 11 ++++ .../Agent/Core/Transactions/Transaction.cs | 4 +- .../AgentWrapperApi/Data/MethodCallData.cs | 8 ++- .../Agent/Core/Wrapper/WrapperService.cs | 2 +- .../Parsing/IConnectionInfo.cs | 24 +++++++-- .../Providers/Wrapper/Constants.cs | 18 +++++++ .../Providers/Wrapper/MethodCall.cs | 4 +- .../AspNetCore/WrapPipelineMiddleware.cs | 2 +- .../CosmosDb/ExecuteItemQueryAsyncWrapper.cs | 2 +- .../CosmosDb/RequestInvokerHandlerWrapper.cs | 2 +- .../Wrapper/Elasticsearch/RequestWrapper.cs | 4 +- .../Wrapper/MongoDb26/MongoDbHelper.cs | 15 +++--- .../Wrapper/Owin/OwinStartupMiddleware.cs | 2 +- .../ServiceStackRedis/SendCommandWrapper.cs | 8 ++- .../Wrapper/StackExchangeRedis/Common.cs | 8 +-- .../StackExchangeRedis2Plus/SessionCache.cs | 8 +-- .../IConnectionStringParser.cs | 2 +- .../IbmDb2ConnectionStringParser.cs | 3 +- .../MsSqlConnectionStringParser.cs | 3 +- .../MySqlConnectionStringParser.cs | 21 ++++++-- .../OracleConnectionStringParser.cs | 18 +++++-- .../PostgresConnectionStringParser.cs | 10 +++- ...tackExchangeRedisConnectionStringParser.cs | 9 +++- .../CosmosDB/CosmosDBTests.cs | 22 +++++++- .../Elasticsearch/ElasticsearchTests.cs | 13 ++++- .../AgentWrapperApiExtensions.cs | 10 ++-- .../CompositeTests/DistributedTracingTests.cs | 5 +- .../Spans/SpanEventMakerTests.cs | 52 +++++++++++++++++-- .../Spans/SpanEventWireModelTests.cs | 6 +-- .../DatastoreSegmentTransformerTests.cs | 2 +- .../SqlTraceMakerTests.cs | 2 +- .../TestTransactions.cs | 2 +- .../TransactionTraceMakerTests.cs | 2 +- .../AgentWrapperApi/AgentWrapperApiTests.cs | 8 +-- .../Core.UnitTest/Wrapper/WrapperService.cs | 6 +-- 38 files changed, 310 insertions(+), 76 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinitionService.cs b/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinitionService.cs index cc83b495e3..43053e6704 100644 --- a/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinitionService.cs +++ b/src/Agent/NewRelic/Agent/Core/Attributes/AttributeDefinitionService.cs @@ -43,7 +43,11 @@ public interface IAttributeDefinitions AttributeDefinition DatabaseDuration { get; } AttributeDefinition DbCollection { get; } AttributeDefinition DbInstance { get; } + AttributeDefinition DbOperation { get; } + AttributeDefinition DbServerAddress { get; } + AttributeDefinition DbServerPort { get; } AttributeDefinition DbStatement { get; } + AttributeDefinition DbSystem { get; } AttributeDefinition DistributedTraceId { get; } AttributeDefinition Duration { get; } AttributeDefinition IsErrorExpected { get; } @@ -91,6 +95,8 @@ public interface IAttributeDefinitions AttributeDefinition RequestUri { get; } AttributeDefinition ResponseStatus { get; } AttributeDefinition Sampled { get; } + AttributeDefinition ServerAddress { get; } + AttributeDefinition ServerPort { get; } AttributeDefinition SpanCategory { get; } AttributeDefinition SpanErrorClass { get; } AttributeDefinition SpanErrorMessage { get; } @@ -102,6 +108,7 @@ public interface IAttributeDefinitions AttributeDefinition SyntheticsMonitorIdForTraces { get; } AttributeDefinition SyntheticsResourceId { get; } AttributeDefinition SyntheticsResourceIdForTraces { get; } + AttributeDefinition ThreadId { get; } AttributeDefinition Timestamp { get; } AttributeDefinition TimestampForError { get; } AttributeDefinition TotalTime { get; } @@ -476,6 +483,12 @@ public AttributeDefinition GetTypeAttribute(TypeAttr .AppliesTo(AttributeDestinations.SpanEvent) .Build(_attribFilter)); + private AttributeDefinition _threadId; + public AttributeDefinition ThreadId => _threadId ?? (_threadId = + AttributeDefinitionBuilder.CreateLong("thread.id", AttributeClassification.Intrinsics) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + private AttributeDefinition _component; public AttributeDefinition Component => _component ?? (_component = AttributeDefinitionBuilder.CreateString("component", AttributeClassification.Intrinsics) @@ -513,6 +526,18 @@ public AttributeDefinition GetTypeAttribute(TypeAttr .AppliesTo(AttributeDestinations.SpanEvent) .Build(_attribFilter)); + private AttributeDefinition _dbSystem; + public AttributeDefinition DbSystem => _dbSystem ?? (_dbSystem = + AttributeDefinitionBuilder.CreateString("db.system", AttributeClassification.AgentAttributes) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + + private AttributeDefinition _dbOperation; + public AttributeDefinition DbOperation => _dbOperation ?? (_dbOperation = + AttributeDefinitionBuilder.CreateString("db.operation", AttributeClassification.AgentAttributes) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + private AttributeDefinition _dbCollection; public AttributeDefinition DbCollection => _dbCollection ?? (_dbCollection = AttributeDefinitionBuilder.CreateString("db.collection", AttributeClassification.AgentAttributes) @@ -546,7 +571,31 @@ public AttributeDefinition GetTypeAttribute(TypeAttr private AttributeDefinition _httpMethod; public AttributeDefinition HttpMethod => _httpMethod ?? (_httpMethod = - AttributeDefinitionBuilder.CreateString("http.method", AttributeClassification.AgentAttributes) + AttributeDefinitionBuilder.CreateString("http.request.method", AttributeClassification.AgentAttributes) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + + private AttributeDefinition _serverAddress; + public AttributeDefinition ServerAddress => _serverAddress ?? (_serverAddress = + AttributeDefinitionBuilder.CreateString("server.address", AttributeClassification.Intrinsics) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + + private AttributeDefinition _serverPort; + public AttributeDefinition ServerPort => _serverPort ?? (_serverPort = + AttributeDefinitionBuilder.CreateLong("server.port", AttributeClassification.Intrinsics) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + + private AttributeDefinition _dbServerAddress; + public AttributeDefinition DbServerAddress => _dbServerAddress ?? (_dbServerAddress = + AttributeDefinitionBuilder.CreateString("server.address", AttributeClassification.AgentAttributes) + .AppliesTo(AttributeDestinations.SpanEvent) + .Build(_attribFilter)); + + private AttributeDefinition _dbServerPort; + public AttributeDefinition DbServerPort => _dbServerPort ?? (_dbServerPort = + AttributeDefinitionBuilder.CreateLong("server.port", AttributeClassification.AgentAttributes) .AppliesTo(AttributeDestinations.SpanEvent) .Build(_attribFilter)); diff --git a/src/Agent/NewRelic/Agent/Core/Segments/DatastoreSegmentData.cs b/src/Agent/NewRelic/Agent/Core/Segments/DatastoreSegmentData.cs index 636669d57d..680a87baba 100644 --- a/src/Agent/NewRelic/Agent/Core/Segments/DatastoreSegmentData.cs +++ b/src/Agent/NewRelic/Agent/Core/Segments/DatastoreSegmentData.cs @@ -23,7 +23,7 @@ namespace NewRelic.Agent.Core.Segments { public class DatastoreSegmentData : AbstractSegmentData, IDatastoreSegmentData { - private readonly static ConnectionInfo EmptyConnectionInfo = new ConnectionInfo(null, null, null); + private readonly static ConnectionInfo EmptyConnectionInfo = new ConnectionInfo(null, null, null, null); public override SpanCategory SpanCategory => SpanCategory.Datastore; @@ -31,7 +31,10 @@ public class DatastoreSegmentData : AbstractSegmentData, IDatastoreSegmentData public DatastoreVendor DatastoreVendorName => _parsedSqlStatement.DatastoreVendor; public string Model => _parsedSqlStatement.Model; public string CommandText { get; set; } + public string Vendor => _connectionInfo.Vendor; public string Host => _connectionInfo.Host; + public int? Port => _connectionInfo.Port; + public string PathOrId => _connectionInfo.PathOrId; public string PortPathOrId => _connectionInfo.PortPathOrId; public string DatabaseName => _connectionInfo.DatabaseName; public Func GetExplainPlanResources { get; set; } @@ -219,10 +222,18 @@ public override void SetSpanTypeSpecificAttributes(SpanAttributeValueCollection AttribDefs.DbCollection.TrySetValue(attribVals, _parsedSqlStatement.Model); } + AttribDefs.DbSystem.TrySetValue(attribVals, Vendor); AttribDefs.DbInstance.TrySetValue(attribVals, DatabaseName); + AttribDefs.DbOperation.TrySetValue(attribVals, Operation); AttribDefs.PeerAddress.TrySetValue(attribVals, $"{Host}:{PortPathOrId}"); - AttribDefs.PeerHostname.TrySetValue(attribVals, Host); AttribDefs.SpanKind.TrySetDefault(attribVals); + // peer.hostname and server.address must match + AttribDefs.PeerHostname.TrySetValue(attribVals, Host); + AttribDefs.DbServerAddress.TrySetValue(attribVals, Host); + if (Port != null) + { + AttribDefs.DbServerPort.TrySetValue(attribVals, Port.Value); + } } public void SetConnectionInfo(ConnectionInfo connInfo) diff --git a/src/Agent/NewRelic/Agent/Core/Segments/ExternalSegmentData.cs b/src/Agent/NewRelic/Agent/Core/Segments/ExternalSegmentData.cs index 82337c2079..7c085c6b0f 100644 --- a/src/Agent/NewRelic/Agent/Core/Segments/ExternalSegmentData.cs +++ b/src/Agent/NewRelic/Agent/Core/Segments/ExternalSegmentData.cs @@ -93,6 +93,8 @@ public override void SetSpanTypeSpecificAttributes(SpanAttributeValueCollection AttribDefs.Component.TrySetValue(attribVals, _segmentState.TypeName); AttribDefs.SpanKind.TrySetDefault(attribVals); AttribDefs.HttpStatusCode.TrySetValue(attribVals, _httpStatusCode); //Attrib handles null + AttribDefs.ServerAddress.TrySetValue(attribVals, Uri.Host); + AttribDefs.ServerPort.TrySetValue(attribVals, Uri.Port); } public override string GetTransactionTraceName() diff --git a/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs b/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs index 8588a72c96..5041f5e31d 100644 --- a/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs +++ b/src/Agent/NewRelic/Agent/Core/Segments/Segment.cs @@ -53,6 +53,7 @@ public Segment(ITransactionSegmentState transactionSegmentState, MethodCallData Data.AttachSegmentDataState(this); Combinable = false; IsLeaf = false; + IsAsync = methodCallData.IsAsync; } /// @@ -75,6 +76,7 @@ public Segment(ITransactionSegmentState transactionSegmentState, MethodCallData Data.AttachSegmentDataState(this); Combinable = false; IsLeaf = true; + IsAsync = methodCallData.IsAsync; } /// @@ -105,6 +107,7 @@ public Segment(TimeSpan relativeStartTime, TimeSpan? duration, Segment segment, } SpanId = segment.SpanId; + IsAsync = segment.IsAsync; } public bool IsDone @@ -229,6 +232,8 @@ public void RemoveSegmentFromCallStack() /// public int ThreadId { get; private set; } + public bool IsAsync { get; private set; } + // used to set the ["unfinished"] parameter, not for real-time state of segment public bool Unfinished { get; private set; } @@ -335,6 +340,11 @@ public SpanAttributeValueCollection GetAttributeValues() AttribDefs.CodeFunction.TrySetValue(attribValues, codeFunction); } + if (!IsAsync) + { + AttribDefs.ThreadId.TrySetValue(attribValues, ThreadId); + } + Data.SetSpanTypeSpecificAttributes(attribValues); return attribValues; @@ -441,5 +451,6 @@ public string GetCategory() { return EnumNameCache.GetName(Data.SpanCategory); } + } } diff --git a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs index f48d85d5f5..14fdbf243a 100644 --- a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs +++ b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs @@ -332,13 +332,13 @@ private static MethodCallData GetMethodCallData(MethodCall methodCall) var typeName = methodCall.Method.Type.FullName ?? "[unknown]"; var methodName = methodCall.Method.MethodName; var invocationTargetHashCode = RuntimeHelpers.GetHashCode(methodCall.InvocationTarget); - return new MethodCallData(typeName, methodName, invocationTargetHashCode); + return new MethodCallData(typeName, methodName, invocationTargetHashCode, methodCall.IsAsync); } // Used for StackExchange.Redis since we will not be instrumenting any methods when creating the many DataStore segments private static MethodCallData GetMethodCallData(string typeName, string methodName, int invocationTargetHashCode) { - return new MethodCallData(typeName, methodName, invocationTargetHashCode); + return new MethodCallData(typeName, methodName, invocationTargetHashCode, true); // assume async } private static MetricNames.MessageBrokerDestinationType AgentWrapperApiEnumToMetricNamesEnum( diff --git a/src/Agent/NewRelic/Agent/Core/Wrapper/AgentWrapperApi/Data/MethodCallData.cs b/src/Agent/NewRelic/Agent/Core/Wrapper/AgentWrapperApi/Data/MethodCallData.cs index 4ba6c8e87e..ed95d23c0a 100644 --- a/src/Agent/NewRelic/Agent/Core/Wrapper/AgentWrapperApi/Data/MethodCallData.cs +++ b/src/Agent/NewRelic/Agent/Core/Wrapper/AgentWrapperApi/Data/MethodCallData.cs @@ -8,12 +8,14 @@ public class MethodCallData public readonly string TypeName; public readonly string MethodName; public readonly int InvocationTargetHashCode; + public readonly bool IsAsync; - public MethodCallData(string typeName, string methodName, int invocationTargetHashCode) + public MethodCallData(string typeName, string methodName, int invocationTargetHashCode, bool isAsync = false) { TypeName = typeName; MethodName = methodName; InvocationTargetHashCode = invocationTargetHashCode; + IsAsync = isAsync; } public override string ToString() @@ -27,6 +29,7 @@ public override int GetHashCode() hash = hash * 23 + TypeName.GetHashCode(); hash = hash * 23 + MethodName.GetHashCode(); hash = hash * 23 + InvocationTargetHashCode; + hash = hash * 29 + IsAsync.GetHashCode(); return hash; } @@ -37,7 +40,8 @@ public override bool Equals(object obj) var other = (MethodCallData)obj; return InvocationTargetHashCode == other.InvocationTargetHashCode && MethodName.Equals(other.MethodName) && - TypeName.Equals(other.TypeName); + TypeName.Equals(other.TypeName) && + IsAsync == other.IsAsync; } return false; } diff --git a/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs b/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs index 963be1e810..5518538048 100644 --- a/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs +++ b/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs @@ -142,7 +142,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(Type type, string methodNa } } - var methodCall = new MethodCall(instrumentedMethodInfo.Method, invocationTarget, methodArguments); + var methodCall = new MethodCall(instrumentedMethodInfo.Method, invocationTarget, methodArguments, instrumentedMethodInfo.IsAsync); var instrumentedMethodCall = new InstrumentedMethodCall(methodCall, instrumentedMethodInfo); // if the wrapper throws an exception when executing the pre-method code, make sure the wrapper isn't called again in the future diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Parsing/IConnectionInfo.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Parsing/IConnectionInfo.cs index 4774f5767b..7b1a4bbc3e 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Parsing/IConnectionInfo.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Parsing/IConnectionInfo.cs @@ -5,10 +5,25 @@ namespace NewRelic.Agent.Extensions.Parsing { public class ConnectionInfo { - public ConnectionInfo(string host, string portPathOrId, string databaseName, string instanceName = null) + public ConnectionInfo(string vendor, string host, int port, string databaseName, string instanceName = null) { + Vendor = vendor; Host = ValueOrUnknown(host); - PortPathOrId = ValueOrUnknown(portPathOrId); + if (port >= 0) + { + Port = port; + } + PathOrId = ValueOrUnknown(string.Empty); + DatabaseName = ValueOrUnknown(databaseName); + InstanceName = instanceName; + } + + public ConnectionInfo(string vendor, string host, string pathOrId, string databaseName, string instanceName = null) + { + Vendor = vendor; + Host = ValueOrUnknown(host); + Port = null; + PathOrId = ValueOrUnknown(pathOrId); DatabaseName = ValueOrUnknown(databaseName); InstanceName = instanceName; } @@ -18,8 +33,11 @@ private static string ValueOrUnknown(string value) return string.IsNullOrEmpty(value) ? "unknown" : value; } + public string Vendor { get; private set; } public string Host { get; private set; } - public string PortPathOrId { get; private set; } + public string PortPathOrId { get => (Port != null) ? Port.ToString() : PathOrId; } + public int? Port { get; private set; } = null; + public string PathOrId { get; private set; } = string.Empty; public string DatabaseName { get; private set; } public string InstanceName { get; private set; } } diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/Constants.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/Constants.cs index cf6cbc8c7e..b8d1fa5e2f 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/Constants.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/Constants.cs @@ -82,6 +82,24 @@ public enum DatastoreVendor Other } + public static class DatastoreVendorExtensions + { + // Convert our internal enum to the matching OTel "known" name for a database provider + public static string ToKnownName(this DatastoreVendor vendor) + { + switch (vendor) + { + case DatastoreVendor.Other: + return "other_sql"; + case DatastoreVendor.IBMDB2: + return "db2"; + // The others match our enum name + default: + return EnumNameCache.GetNameToLower(vendor); + } + } + } + public static class EnumNameCache // c# 7.3: where TEnum : System.Enum { private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/MethodCall.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/MethodCall.cs index 837a120ac7..d6686b4ab3 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/MethodCall.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Providers/Wrapper/MethodCall.cs @@ -11,12 +11,14 @@ public class MethodCall public readonly Method Method; public readonly object InvocationTarget; public readonly object[] MethodArguments; + public readonly bool IsAsync; - public MethodCall(Method method, object invocationTarget, object[] methodArguments) + public MethodCall(Method method, object invocationTarget, object[] methodArguments, bool isAsync) { Method = method; InvocationTarget = invocationTarget; MethodArguments = methodArguments ?? new object[0]; + IsAsync = isAsync; } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs index 3b3c223a55..ad5bb3f7a6 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore/WrapPipelineMiddleware.cs @@ -148,7 +148,7 @@ private ISegment SetupSegment(ITransaction transaction, HttpContext context) { // Seems like it would be cool to not require all of this for a segment??? var method = new Method(typeof(WrapPipelineMiddleware), nameof(Invoke), nameof(context)); - var methodCall = new MethodCall(method, this, new object[] { context }); + var methodCall = new MethodCall(method, this, new object[] { context }, true); var segment = transaction.StartTransactionSegment(methodCall, "Middleware Pipeline"); return segment; diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/ExecuteItemQueryAsyncWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/ExecuteItemQueryAsyncWrapper.cs index d446b9c0b3..d93ab837ad 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/ExecuteItemQueryAsyncWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/ExecuteItemQueryAsyncWrapper.cs @@ -78,7 +78,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins var segment = transaction.StartDatastoreSegment( instrumentedMethodCall.MethodCall, new ParsedSqlStatement(DatastoreVendor.CosmosDB, model, operation), - connectionInfo: endpoint != null ? new ConnectionInfo(endpoint.Host, endpoint.Port.ToString(), databaseName) : new ConnectionInfo(string.Empty, string.Empty, databaseName), + connectionInfo: endpoint != null ? new ConnectionInfo(DatastoreVendor.CosmosDB.ToKnownName(), endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName), commandText : querySpec != null ? _queryGetter.Invoke(querySpec) : string.Empty, isLeaf: true); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/RequestInvokerHandlerWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/RequestInvokerHandlerWrapper.cs index a675c09b9d..2f081674c1 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/RequestInvokerHandlerWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/CosmosDb/RequestInvokerHandlerWrapper.cs @@ -67,7 +67,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins var segment = transaction.StartDatastoreSegment( instrumentedMethodCall.MethodCall, new ParsedSqlStatement(DatastoreVendor.CosmosDB, model, operation), - connectionInfo: endpoint != null ? new ConnectionInfo(endpoint.Host, endpoint.Port.ToString(), databaseName) : new ConnectionInfo(string.Empty, string.Empty, databaseName), + connectionInfo: endpoint != null ? new ConnectionInfo(DatastoreVendor.CosmosDB.ToKnownName(), endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName), isLeaf: true); return Delegates.GetAsyncDelegateFor(agent, segment); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Elasticsearch/RequestWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Elasticsearch/RequestWrapper.cs index e99ed18b22..da41e4cfc0 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Elasticsearch/RequestWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Elasticsearch/RequestWrapper.cs @@ -66,7 +66,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins } var transactionExperimental = transaction.GetExperimentalApi(); - var datastoreSegmentData = transactionExperimental.CreateDatastoreSegmentData(new ParsedSqlStatement(DatastoreVendor.Elasticsearch, model, operation), new ConnectionInfo(string.Empty, string.Empty, string.Empty), string.Empty, null); + var datastoreSegmentData = transactionExperimental.CreateDatastoreSegmentData(new ParsedSqlStatement(DatastoreVendor.Elasticsearch, model, operation), new ConnectionInfo(DatastoreVendor.Elasticsearch.ToKnownName(), string.Empty, string.Empty, string.Empty), string.Empty, null); var segment = transactionExperimental.StartSegment(instrumentedMethodCall.MethodCall); segment.GetExperimentalApi().SetSegmentData(datastoreSegmentData).MakeLeaf(); @@ -269,7 +269,7 @@ private static void SetUriOnDatastoreSegment(ISegment segment, Uri uri) { var segmentExperimentalApi = segment.GetExperimentalApi(); var data = segmentExperimentalApi.SegmentData as IDatastoreSegmentData; - data.SetConnectionInfo(new ConnectionInfo(uri.Host, uri.Port.ToString(), string.Empty)); + data.SetConnectionInfo(new ConnectionInfo(DatastoreVendor.Elasticsearch.ToKnownName(), uri.Host, uri.Port, string.Empty)); segmentExperimentalApi.SetSegmentData(data); } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/MongoDbHelper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/MongoDbHelper.cs index 076950cfb7..54a24a1ab6 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/MongoDbHelper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MongoDb26/MongoDbHelper.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Net; using NewRelic.Agent.Extensions.Parsing; +using NewRelic.Agent.Extensions.Providers.Wrapper; using NewRelic.Parsing.ConnectionString; using NewRelic.Reflection; @@ -104,7 +105,7 @@ private static object GetServerFromFromInterface(object owner) public static ConnectionInfo GetConnectionInfoFromCursor(object asyncCursor, object collectionNamespace, string utilizationHostName) { string host = null; - string port = null; + int port = -1; var channelSource = GetChannelSourceFieldFromGeneric(asyncCursor); var server = GetServerFromFromInterface(channelSource); @@ -115,19 +116,19 @@ public static ConnectionInfo GetConnectionInfoFromCursor(object asyncCursor, obj if (dnsEndpoint != null) { - port = dnsEndpoint.Port.ToString(); + port = dnsEndpoint.Port; host = ConnectionStringParserHelper.NormalizeHostname(dnsEndpoint.Host, utilizationHostName); } if (ipEndpoint != null) { - port = ipEndpoint.Port.ToString(); + port = ipEndpoint.Port; host = ConnectionStringParserHelper.NormalizeHostname(ipEndpoint.Address.ToString(), utilizationHostName); } var databaseName = GetDatabaseNameFromCollectionNamespace(collectionNamespace); - return new ConnectionInfo(host, port, databaseName); + return new ConnectionInfo(DatastoreVendor.MongoDB.ToKnownName(), host, port, databaseName); } public static ConnectionInfo GetConnectionInfoFromDatabase(object database, string utilizationHostName) @@ -135,17 +136,17 @@ public static ConnectionInfo GetConnectionInfoFromDatabase(object database, stri var databaseName = GetDatabaseNameFromDatabase(database); var servers = GetServersFromDatabase(database); - string port = null; + int port = -1; string host = null; if (servers.Count == 1) { GetHostAndPortFromServer(servers[0], out var rawHost, out var rawPort); - port = rawPort.ToString(); + port = rawPort; host = ConnectionStringParserHelper.NormalizeHostname(rawHost, utilizationHostName); } - return new ConnectionInfo(host, port, databaseName); + return new ConnectionInfo(DatastoreVendor.MongoDB.ToKnownName(), host, port, databaseName); } private static IList GetServersFromDatabase(object database) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Owin/OwinStartupMiddleware.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Owin/OwinStartupMiddleware.cs index e4ff821dd9..0d56e3b05d 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Owin/OwinStartupMiddleware.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Owin/OwinStartupMiddleware.cs @@ -120,7 +120,7 @@ private IEnumerable GetHeaderValue(IOwinContext owinContext, string key) private ISegment SetupSegment(ITransaction transaction, IOwinContext owinContext) { var method = new Method(typeof(OwinStartupMiddleware), nameof(Invoke), nameof(owinContext)); - var methodCall = new MethodCall(method, this, new object[] { owinContext }); + var methodCall = new MethodCall(method, this, new object[] { owinContext }, true); var segment = transaction.StartTransactionSegment(methodCall, "Owin Middleware Pipeline"); return segment; diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/ServiceStackRedis/SendCommandWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/ServiceStackRedis/SendCommandWrapper.cs index fa3a90e219..f8d08eb9b2 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/ServiceStackRedis/SendCommandWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/ServiceStackRedis/SendCommandWrapper.cs @@ -76,9 +76,13 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins var host = TryGetPropertyName(PropertyHost, contextObject) ?? "unknown"; host = ConnectionStringParserHelper.NormalizeHostname(host, agent.Configuration.UtilizationHostName); - var portPathOrId = TryGetPropertyName(PropertyPortPathOrId, contextObject); + var port = TryGetPropertyName(PropertyPortPathOrId, contextObject); + if (!int.TryParse(port, out int portNum)) + { + portNum = -1; + } var databaseName = TryGetPropertyName(PropertyDatabaseName, contextObject); - var connectionInfo = new ConnectionInfo(host, portPathOrId, databaseName); + var connectionInfo = new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, portNum, databaseName); var segment = transaction.StartDatastoreSegment(instrumentedMethodCall.MethodCall, ParsedSqlStatement.FromOperation(DatastoreVendor.Redis, operation), connectionInfo); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis/Common.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis/Common.cs index a28d47baeb..10720f81e6 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis/Common.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis/Common.cs @@ -102,18 +102,18 @@ public static ConnectionInfo GetConnectionInfoFromConnectionMultiplexer(MethodCa var dnsEndpoint = endpoint as DnsEndPoint; var ipEndpoint = endpoint as IPEndPoint; - string port = null; + int port = -1; string host = null; if (dnsEndpoint != null) { - port = dnsEndpoint.Port.ToString(); + port = dnsEndpoint.Port; host = ConnectionStringParserHelper.NormalizeHostname(dnsEndpoint.Host, utilizationHostName); } if (ipEndpoint != null) { - port = ipEndpoint.Port.ToString(); + port = ipEndpoint.Port; host = ConnectionStringParserHelper.NormalizeHostname(ipEndpoint.Address.ToString(), utilizationHostName); } @@ -122,7 +122,7 @@ public static ConnectionInfo GetConnectionInfoFromConnectionMultiplexer(MethodCa return null; } - return new ConnectionInfo(host, port, null); + return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null); } private static string GetCommandNameFromEnumValue(Enum commandValue) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs index 0d09792201..78edc24bce 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/StackExchangeRedis2Plus/SessionCache.cs @@ -117,16 +117,16 @@ private ConnectionInfo GetConnectionInfo(EndPoint endpoint) { if (endpoint is DnsEndPoint dnsEndpoint) { - var port = dnsEndpoint.Port.ToString(); + var port = dnsEndpoint.Port; var host = ConnectionStringParserHelper.NormalizeHostname(dnsEndpoint.Host, _agent.Configuration.UtilizationHostName); - return new ConnectionInfo(host, port, null); + return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null); } if (endpoint is IPEndPoint ipEndpoint) { - var port = ipEndpoint.Port.ToString(); + var port = ipEndpoint.Port; var host = ConnectionStringParserHelper.NormalizeHostname(ipEndpoint.Address.ToString(), _agent.Configuration.UtilizationHostName); - return new ConnectionInfo(host, port, null); + return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null); } return null; diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IConnectionStringParser.cs index 9313fef5d3..781106a24a 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IConnectionStringParser.cs @@ -17,7 +17,7 @@ public static class ConnectionInfoParser private const int CacheCapacity = 1000; private static readonly SimpleCache _connectionInfoCache = new SimpleCache(CacheCapacity); - private static readonly ConnectionInfo Empty = new ConnectionInfo(null, null, null); + private static readonly ConnectionInfo Empty = new ConnectionInfo(null, null, null, null); public static ConnectionInfo FromConnectionString(DatastoreVendor vendor, string connectionString, string utilizationHostName) { diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IbmDb2ConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IbmDb2ConnectionStringParser.cs index 44562d9b76..ec1f302d13 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IbmDb2ConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/IbmDb2ConnectionStringParser.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Extensions.Parsing; +using NewRelic.Agent.Extensions.Providers.Wrapper; using System.Collections.Generic; using System.Data.Common; using System.Linq; @@ -27,7 +28,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName) var portPathOrId = ParsePortPathOrId(); var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value; - return new ConnectionInfo(host, portPathOrId, databaseName); + return new ConnectionInfo(DatastoreVendor.IBMDB2.ToKnownName(), host, portPathOrId, databaseName); } private string ParseHost() diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MsSqlConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MsSqlConnectionStringParser.cs index cda78bd914..890f0dc417 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MsSqlConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MsSqlConnectionStringParser.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Extensions.Parsing; +using NewRelic.Agent.Extensions.Providers.Wrapper; using System.Collections.Generic; using System.Data.Common; using System.Linq; @@ -27,7 +28,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName) var portPathOrId = ParsePortPathOrId(); var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value; var instanceName = ParseInstanceName(); - return new ConnectionInfo(host, portPathOrId, databaseName, instanceName); + return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, portPathOrId, databaseName, instanceName); } private string ParseHost() diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MySqlConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MySqlConnectionStringParser.cs index 3941ff12b4..f9c3c15341 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MySqlConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/MySqlConnectionStringParser.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Extensions.Parsing; +using NewRelic.Agent.Extensions.Providers.Wrapper; using System.Collections.Generic; using System.Data.Common; @@ -30,12 +31,24 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName) else if (host != null) host = ConnectionStringParserHelper.NormalizeHostname(host, utilizationHostName); - var portPathOrId = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _portKeys)?.Value; - if (portPathOrId == null && host != null) portPathOrId = "default"; - var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value; - return new ConnectionInfo(host, portPathOrId, databaseName); + var port = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _portKeys)?.Value; + if (port == null && host != null) + { + return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, "default", databaseName); + } + else + { + int portNum; + if (!int.TryParse(port, out portNum)) + { + portNum = -1; + } + return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, portNum, databaseName); + } + + } } } diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/OracleConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/OracleConnectionStringParser.cs index d3f8d6c2f2..139120e385 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/OracleConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/OracleConnectionStringParser.cs @@ -6,6 +6,7 @@ using System.Data.Common; using System.Linq; using NewRelic.Agent.Helpers; +using NewRelic.Agent.Extensions.Providers.Wrapper; namespace NewRelic.Parsing.ConnectionString { @@ -26,8 +27,17 @@ public OracleConnectionStringParser(string connectionString) public ConnectionInfo GetConnectionInfo(string utilizationHostName) { var host = ParseHost(); - var portPathOrId = ParsePortPathOrId(); - return new ConnectionInfo(host, portPathOrId, null); + var portStr = ParsePortString(); + if (string.IsNullOrEmpty(portStr)) + { + return new ConnectionInfo(DatastoreVendor.Oracle.ToKnownName(), host, "default", null); + } + int port; + if (!int.TryParse(portStr, out port)) + { + port = -1; + } + return new ConnectionInfo(DatastoreVendor.Oracle.ToKnownName(), host, port, null); } private string ParseHost() @@ -81,7 +91,7 @@ private string ParseHost() return null; } - private string ParsePortPathOrId() + private string ParsePortString() { var host = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _hostKeys)?.Value; if (host == null) return null; @@ -110,7 +120,7 @@ private string ParsePortPathOrId() startOfValue = secondaryPortSection.IndexOf(StringSeparators.ColonChar); if (startOfValue > -1) return secondaryPortSection.Substring(startOfValue + 1); - return "default"; + return null; } else { diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/PostgresConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/PostgresConnectionStringParser.cs index a2d30a24c7..feae1654da 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/PostgresConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/PostgresConnectionStringParser.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using NewRelic.Agent.Extensions.Parsing; +using NewRelic.Agent.Extensions.Providers.Wrapper; using System.Collections.Generic; using System.Data.Common; @@ -27,10 +28,15 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName) if (host != null) host = ConnectionStringParserHelper.NormalizeHostname(host, utilizationHostName); - var portPathOrId = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _portKeys)?.Value; var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value; - return new ConnectionInfo(host, portPathOrId, databaseName); + var port = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _portKeys)?.Value; + if (port == null || !int.TryParse(port, out int portNum)) + { + portNum = -1; + } + + return new ConnectionInfo(DatastoreVendor.Postgres.ToKnownName(), host, portNum, databaseName); } } } diff --git a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/StackExchangeRedisConnectionStringParser.cs b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/StackExchangeRedisConnectionStringParser.cs index c580303355..dd29b5488d 100644 --- a/src/Agent/NewRelic/Agent/Parsing/ConnectionString/StackExchangeRedisConnectionStringParser.cs +++ b/src/Agent/NewRelic/Agent/Parsing/ConnectionString/StackExchangeRedisConnectionStringParser.cs @@ -4,6 +4,7 @@ using NewRelic.Agent.Extensions.Parsing; using System.Linq; using NewRelic.Agent.Helpers; +using NewRelic.Agent.Extensions.Providers.Wrapper; namespace NewRelic.Parsing.ConnectionString { @@ -40,10 +41,14 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName) // We can only capture the first server we detect. It could be that there are many.... var hostPortPair = section.Split(StringSeparators.Colon); var port = hostPortPair.Length == 2 ? hostPortPair[1] : null; - return new ConnectionInfo(ConnectionStringParserHelper.NormalizeHostname(hostPortPair[0], utilizationHostName), port, null); + if(!int.TryParse(port, out int portNum)) + { + portNum = -1; + } + return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), ConnectionStringParserHelper.NormalizeHostname(hostPortPair[0], utilizationHostName), portNum, null); } - return new ConnectionInfo(null, null, null); + return new ConnectionInfo(null, null, null, null); } } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs index d008deb364..e68c635ca7 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/CosmosDB/CosmosDBTests.cs @@ -2,12 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 +using Azure; +using Couchbase.Core; using NewRelic.Agent.IntegrationTestHelpers; using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; using NewRelic.Testing.Assertions; using System; +using System.CodeDom.Compiler; using System.Collections.Generic; +using System.Configuration; using System.Linq; +using System.Net.NetworkInformation; +using System.Net.PeerToPeer.Collaboration; +using System.Reflection; +using System.Windows.Forms; using Xunit; using Xunit.Abstractions; @@ -76,6 +84,16 @@ public void CreateReadAndDeleteDatabaseTests() new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/ReadDatabase", metricScope = expectedTransactionName, callCount = 2 }, new Assertions.ExpectedMetric { metricName = $"Datastore/operation/CosmosDB/DeleteDatabase", metricScope = expectedTransactionName, callCount = 1 }, }; + var expectedAgentAttributes = new List + { + "db.system", + "db.operation", + "db.instance", + "peer.address", + "peer.hostname", + "server.address", + "server.port" + }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); var spanEvents = _fixture.AgentLog.GetSpanEvents(); @@ -85,7 +103,9 @@ public void CreateReadAndDeleteDatabaseTests() NrAssert.Multiple ( () => Assertions.MetricsExist(expectedMetrics, metrics), - () => Assert.Equal(6, operationDatastoreSpans.Count()) + () => Assert.Equal(6, operationDatastoreSpans.Count()), + () => Assertions.SpanEventHasAttributes(expectedAgentAttributes, IntegrationTestHelpers.Models.SpanEventAttributeType.Agent, operationDatastoreSpans.FirstOrDefault()) + ); } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs index 4a95305eea..147fc0fe99 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Elasticsearch/ElasticsearchTests.cs @@ -155,6 +155,16 @@ private void ValidateOperation(string operationName) { new Assertions.ExpectedMetric { metricName = $"Datastore/statement/Elasticsearch/{expectedIndexName}/{expectedOperationName}", metricScope = expectedTransactionName, callCount = 1 }, }; + var expectedAgentAttributes = new List + { + "db.system", + "db.operation", + "db.instance", + "peer.address", + "peer.hostname", + "server.address", + "server.port" + }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); @@ -172,7 +182,8 @@ private void ValidateOperation(string operationName) ( () => Assertions.MetricsExist(expectedMetrics, metrics), () => Assert.Single(operationDatastoreSpans), - () => Assert.Equal(_host, uri) + () => Assert.Equal(_host, uri), + () => Assertions.SpanEventHasAttributes(expectedAgentAttributes, IntegrationTestHelpers.Models.SpanEventAttributeType.Agent, operationDatastoreSpans.FirstOrDefault()) ); } diff --git a/tests/Agent/UnitTests/CompositeTests/AgentWrapperApiExtensions.cs b/tests/Agent/UnitTests/CompositeTests/AgentWrapperApiExtensions.cs index 090b2854c9..4ee6431af3 100644 --- a/tests/Agent/UnitTests/CompositeTests/AgentWrapperApiExtensions.cs +++ b/tests/Agent/UnitTests/CompositeTests/AgentWrapperApiExtensions.cs @@ -56,7 +56,7 @@ public static ISegment StartExternalRequestSegmentOrThrow(this IAgent agent, Uri public static ISegment StartDatastoreRequestSegmentOrThrow(this IAgent agent, string operation, DatastoreVendor vendor, string model, string commandText = null, MethodCall methodCall = null, string host = null, string portPathOrId = null, string databaseName = null, IDictionary queryParameters = null) { methodCall = methodCall ?? GetDefaultMethodCall(agent); - var segment = agent.CurrentTransaction.StartDatastoreSegment(methodCall, new ParsedSqlStatement(vendor, model, operation), new ConnectionInfo(host, portPathOrId, databaseName), commandText, queryParameters); + var segment = agent.CurrentTransaction.StartDatastoreSegment(methodCall, new ParsedSqlStatement(vendor, model, operation), new ConnectionInfo(vendor.ToKnownName(), host, portPathOrId, databaseName), commandText, queryParameters); if (segment == null) throw new NullReferenceException("segment"); @@ -67,7 +67,7 @@ public static ISegment StartStackExchangeRedisDatastoreRequestSegmentOrThrow(thi { methodCall = methodCall ?? GetDefaultMethodCall(agent); var xTransaction = (ITransactionExperimental)agent.CurrentTransaction; - var segment = xTransaction.StartStackExchangeRedisSegment(RuntimeHelpers.GetHashCode(methodCall), ParsedSqlStatement.FromOperation(vendor, operation), new ConnectionInfo(host, portPathOrId, databaseName), relativeStartTime, relativeEndTime); + var segment = xTransaction.StartStackExchangeRedisSegment(RuntimeHelpers.GetHashCode(methodCall), ParsedSqlStatement.FromOperation(vendor, operation), new ConnectionInfo(vendor.ToKnownName(), host, portPathOrId, databaseName), relativeStartTime, relativeEndTime); if (segment == null) throw new NullReferenceException("segment"); @@ -89,7 +89,8 @@ private static MethodCall GetDefaultMethodCall(IAgent agent) return new MethodCall( new Method(agent.GetType(), "methodName", "parameterTypeNames"), agent, - new object[0] + new object[0], + false ); } @@ -98,7 +99,8 @@ private static MethodCall GetCustomSegmentMethodCall(IAgent agent) return new MethodCall( new Method(agent.GetType(), "methodName", "parameterTypeNames"), agent, - new object[] { "customName" } + new object[] { "customName" }, + false ); } } diff --git a/tests/Agent/UnitTests/CompositeTests/DistributedTracingTests.cs b/tests/Agent/UnitTests/CompositeTests/DistributedTracingTests.cs index 5603c6cd8c..5915ab5348 100644 --- a/tests/Agent/UnitTests/CompositeTests/DistributedTracingTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/DistributedTracingTests.cs @@ -255,10 +255,13 @@ public void ExternalHttpSpanEvent_HasExpectedAttributes() TestPayloadInfoMatchesSpanInfo(Payload, rootSpan, actualSpan, "http"); var agentAttributes = actualSpan.AgentAttributes(); + var intrinsicAttributes = actualSpan.IntrinsicAttributes(); //The specific test Assert.AreEqual(url, agentAttributes["http.url"]); - Assert.AreEqual(method, agentAttributes["http.method"]); + Assert.AreEqual(method, agentAttributes["http.request.method"]); + Assert.AreEqual("127.0.0.2", intrinsicAttributes["server.address"]); + Assert.AreEqual(123, intrinsicAttributes["server.port"]); } private static Dictionary NewRelicHeaders diff --git a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs index d24eaff41d..f9612dee4c 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventMakerTests.cs @@ -64,6 +64,7 @@ public class SpanEventMakerTests private string _transactionGuid; private DateTime _startTime; private Segment _baseGenericSegment; + private Segment _baseGenericAsyncSegment; private Segment _childGenericSegment; private Segment _baseDatastoreSegment; private Segment _baseHttpSegment; @@ -158,11 +159,14 @@ public void SetUp() _baseGenericSegment = new Segment(CreateTransactionSegmentState(3, null, 777), new MethodCallData(MethodCallType, MethodCallMethod, 1)); _baseGenericSegment.SetSegmentData(new SimpleSegmentData(SegmentName)); + _baseGenericAsyncSegment = new Segment(CreateTransactionSegmentState(5, null, 888), new MethodCallData(MethodCallType, MethodCallMethod, 1, true)); + _baseGenericAsyncSegment.SetSegmentData(new SimpleSegmentData(SegmentName)); + _childGenericSegment = new Segment(CreateTransactionSegmentState(4, 3, 777), new MethodCallData(MethodCallType, MethodCallMethod, 1)); _childGenericSegment.SetSegmentData(new SimpleSegmentData(SegmentName)); // Datastore Segments - _connectionInfo = new ConnectionInfo("localhost", "1234", "default", "maininstance"); + _connectionInfo = new ConnectionInfo(DatastoreVendor.MSSQL.ToKnownName(), "localhost", 1234, "default", "maininstance"); _parsedSqlStatement = SqlParser.GetParsedDatabaseStatement(DatastoreVendor.MSSQL, System.Data.CommandType.Text, ShortQuery); _obfuscatedSql = _databaseService.GetObfuscatedSql(ShortQuery, DatastoreVendor.MSSQL); @@ -440,8 +444,10 @@ public void GetSpanEvent_ReturnsSpanEventPerSegment_ValidateDatastoreValues() ( () => Assert.AreEqual(DatastoreCategory, (string)spanEventIntrinsicAttributes["category"]), () => Assert.AreEqual(DatastoreVendor.MSSQL.ToString(), (string)spanEventIntrinsicAttributes["component"]), - () => Assert.AreEqual(_parsedSqlStatement.Model, (string)spanEventAgentAttributes["db.collection"]), - + () => Assert.AreEqual(DatastoreVendor.MSSQL.ToKnownName(), (string)spanEventAgentAttributes["db.system"]), + () => Assert.AreEqual(_parsedSqlStatement.Operation, (string)spanEventAgentAttributes["db.operation"]), + () => Assert.AreEqual(_connectionInfo.Host, (string)spanEventAgentAttributes["server.address"]), + () => Assert.AreEqual(_connectionInfo.Port.Value, spanEventAgentAttributes["server.port"]), //This also tests the lazy instantiation on span event attrib values () => Assert.AreEqual(_obfuscatedSql, (string)spanEventAgentAttributes["db.statement"]), @@ -459,7 +465,7 @@ public void Do_Not_Generate_DbCollection_Attribute_When_Model_IsNullOrEmpty() var testSegment = new Segment(CreateTransactionSegmentState(3, null, 777), new MethodCallData(MethodCallType, MethodCallMethod, 1)); testSegment.SetSegmentData(new DatastoreSegmentData(_databaseService, parsedSqlStatement: new ParsedSqlStatement(DatastoreVendor.CosmosDB, string.Empty, "ReadDatabase"), - connectionInfo: new ConnectionInfo("localhost", "1234", "default", "maininstance"))); + connectionInfo: new ConnectionInfo("none", "localhost", "1234", "default", "maininstance"))); // ARRANGE var segments = new List() @@ -713,7 +719,7 @@ public void GetSpanEvent_ReturnsSpanEventPerSegment_ValidateHttpValues() // ASSERT Assert.AreEqual(HttpUri, (string)spanEventAgentAttributes["http.url"]); - Assert.AreEqual(HttpMethod, (string)spanEventAgentAttributes["http.method"]); + Assert.AreEqual(HttpMethod, (string)spanEventAgentAttributes["http.request.method"]); Assert.AreEqual("type", (string)spanEventIntrinsicAttributes["component"]); Assert.AreEqual("client", (string)spanEventIntrinsicAttributes["span.kind"]); } @@ -765,6 +771,42 @@ public void GetSpanEvent_ReturnsSpanEventPerSegment_HasHttpStatusCode() #endregion + [Test] + public void GetSpanEvent_CheckThreadIdAttribute() + { + var segments = new List() + { + _baseGenericSegment.CreateSimilar(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5), new List>()) + }; + var immutableTransaction = BuildTestTransaction(segments, true, false); + var transactionMetricName = _transactionMetricNameMaker.GetTransactionMetricName(immutableTransaction.TransactionName); + var metricStatsCollection = new TransactionMetricStatsCollection(transactionMetricName); + var transactionAttribs = _transactionAttribMaker.GetAttributes(immutableTransaction, transactionMetricName, TimeSpan.FromSeconds(1), immutableTransaction.Duration, metricStatsCollection); + + var spanEvents = _spanEventMaker.GetSpanEvents(immutableTransaction, TransactionName, transactionAttribs); + var spanEvent = spanEvents.ToList()[1]; + + Assert.AreEqual(777, spanEvent.IntrinsicAttributes()["thread.id"]); + } + + [Test] + public void GetSpanEvent_CheckMissingThreadIdAttribute() + { + var segments = new List() + { + _baseGenericAsyncSegment.CreateSimilar(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5), new List>()) + }; + var immutableTransaction = BuildTestTransaction(segments, true, false); + var transactionMetricName = _transactionMetricNameMaker.GetTransactionMetricName(immutableTransaction.TransactionName); + var metricStatsCollection = new TransactionMetricStatsCollection(transactionMetricName); + var transactionAttribs = _transactionAttribMaker.GetAttributes(immutableTransaction, transactionMetricName, TimeSpan.FromSeconds(1), immutableTransaction.Duration, metricStatsCollection); + + var spanEvents = _spanEventMaker.GetSpanEvents(immutableTransaction, TransactionName, transactionAttribs); + var spanEvent = spanEvents.ToList()[1]; + + Assert.IsFalse(spanEvent.IntrinsicAttributes().ContainsKey("thread.id")); + } + private ImmutableTransaction BuildTestTransaction(List segments, bool sampled, bool hasIncomingPayload) { var builder = new ImmutableTransactionBuilder() diff --git a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventWireModelTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventWireModelTests.cs index 43c5a19a95..e2ebe601f3 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventWireModelTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Spans/SpanEventWireModelTests.cs @@ -70,7 +70,7 @@ public void SpanEventWireModelTests_Serialization() }, new Dictionary { - { "http.method","GET" } + { "http.request.method","GET" } } }; @@ -113,7 +113,7 @@ public void SpanEventWireModelTests_MultipleEvents_Serialization() }, new Dictionary { - { "http.method","GET" } + { "http.request.method","GET" } } }, new[] { @@ -130,7 +130,7 @@ public void SpanEventWireModelTests_MultipleEvents_Serialization() }, new Dictionary { - { "http.method","POST" } + { "http.request.method","POST" } } } }; diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/DatastoreSegmentTransformerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/DatastoreSegmentTransformerTests.cs index 1005e58b30..9d67a4aeda 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/DatastoreSegmentTransformerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/DatastoreSegmentTransformerTests.cs @@ -283,7 +283,7 @@ private Segment GetSegment(DatastoreVendor vendor, string operation, string mode private Segment GetSegment(DatastoreVendor vendor, string operation, string model, double duration, CrossApplicationResponseData catResponseData = null, string host = null, string portPathOrId = null) { var methodCallData = new MethodCallData("foo", "bar", 1); - var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(vendor, model, operation), null, new ConnectionInfo(host, portPathOrId, null)); + var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(vendor, model, operation), null, new ConnectionInfo("none", host, portPathOrId, null)); var segment = new Segment(TransactionSegmentStateHelpers.GetItransactionSegmentState(), methodCallData); segment.SetSegmentData(data); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/SqlTraceMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/SqlTraceMakerTests.cs index b79cc0958a..1f5414bf10 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/SqlTraceMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/SqlTraceMakerTests.cs @@ -152,7 +152,7 @@ private ImmutableTransaction BuildTestTransaction(string uri = null, string guid private Segment BuildSegment(DatastoreVendor vendor, string model, string commandText, TimeSpan startTime = new TimeSpan(), TimeSpan? duration = null, string name = "", MethodCallData methodCallData = null, IEnumerable> parameters = null, string host = null, string portPathOrId = null, string databaseName = null) { var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(vendor, model, null), commandText, - new ConnectionInfo(host, portPathOrId, databaseName)); + new ConnectionInfo("none", host, portPathOrId, databaseName)); methodCallData = methodCallData ?? new MethodCallData("typeName", "methodName", 1); var segment = new Segment(TransactionSegmentStateHelpers.GetItransactionSegmentState(), methodCallData); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TestTransactions.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TestTransactions.cs index b15e100f38..67a9d2e04c 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TestTransactions.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TestTransactions.cs @@ -118,7 +118,7 @@ public static ImmutableTransaction CreateTestTransactionWithSegments(IEnumerable txSegmentState = TransactionSegmentStateHelpers.GetItransactionSegmentState(); methodCallData = methodCallData ?? new MethodCallData("typeName", "methodName", 1); - var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(vendor, model, null), commandText, new ConnectionInfo(host, portPathOrId, databaseName)); + var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(vendor, model, null), commandText, new ConnectionInfo("none", host, portPathOrId, databaseName)); var segment = new Segment(txSegmentState, methodCallData); segment.SetSegmentData(data); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionTraceMakerTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionTraceMakerTests.cs index b58f14ee13..2fe5614845 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionTraceMakerTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Transformers/TransactionTransformer/TransactionTraceMakerTests.cs @@ -338,7 +338,7 @@ private static ImmutableSegmentTreeNode BuildNode(ImmutableTransaction transacti var data = new DatastoreSegmentData(_databaseService, new ParsedSqlStatement(DatastoreVendor.MSSQL, "test_table", "SELECT"), "SELECT * FROM test_table", - new ConnectionInfo("My Host", "My Port", "My Database")); + new ConnectionInfo("My Vendor", "My Host", "My Port", "My Database")); var segment = new Segment(TransactionSegmentStateHelpers.GetItransactionSegmentState(), methodCallData); segment.SetSegmentData(data); diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs index e5bf21b42e..3917877704 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/AgentWrapperApi/AgentWrapperApiTests.cs @@ -415,7 +415,7 @@ public void StartTransactionSegment_ReturnsAnOpaqueSimpleSegmentBuilder() var invocationTarget = new object(); var method = new Method(typeof(string), "methodName", "parameterTypeNames"); - var methodCall = new MethodCall(method, invocationTarget, new object[0]); + var methodCall = new MethodCall(method, invocationTarget, new object[0], false); var opaqueSegment = _agent.CurrentTransaction.StartTransactionSegment(methodCall, "foo"); Assert.NotNull(opaqueSegment); @@ -443,7 +443,7 @@ public void StartTransactionSegment_PushesNewSegmentUniqueIdToCallStack() Mock.Arrange(() => _callStackManager.Push(Arg.IsAny())) .DoInstead(pushed => pushedUniqueId = pushed); - var opaqueSegment = _agent.CurrentTransaction.StartTransactionSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0]), "foo"); + var opaqueSegment = _agent.CurrentTransaction.StartTransactionSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0], false), "foo"); Assert.NotNull(opaqueSegment); var segment = opaqueSegment as Segment; @@ -458,7 +458,7 @@ public void StartExternalSegment_Throws_IfUriIsNotAbsolute() { SetupTransaction(); var uri = new Uri("/test", UriKind.Relative); - NrAssert.Throws(() => _agent.CurrentTransaction.StartExternalRequestSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0]), uri, "GET")); + NrAssert.Throws(() => _agent.CurrentTransaction.StartExternalRequestSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0], false), uri, "GET")); } #endregion Segments @@ -470,7 +470,7 @@ public void EndSegment_RemovesSegmentFromCallStack() { SetupTransaction(); - var opaqueSegment = _agent.CurrentTransaction.StartTransactionSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0]), "foo"); + var opaqueSegment = _agent.CurrentTransaction.StartTransactionSegment(new MethodCall(new Method(typeof(string), "", ""), "", new object[0], false), "foo"); var segment = opaqueSegment as Segment; var expectedUniqueId = segment.UniqueId; var expectedParentId = segment.ParentUniqueId; diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs index e130ad51cd..4b951cd12a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs @@ -69,7 +69,7 @@ public void BeforeWrappedMethod_PassesCorrectParametersToWrapperLoader() _wrapperService.BeforeWrappedMethod(type, methodName, string.Empty, target, arguments, tracerFactoryName, null, EmptyTracerArgs, 0); var method = new Method(type, methodName, string.Empty); - var expectedMethodCall = new MethodCall(method, target, arguments); + var expectedMethodCall = new MethodCall(method, target, arguments, false); var instrumetedMethodInfo = new InstrumentedMethodInfo(0, expectedMethodCall.Method, tracerFactoryName, false, null, null, false); Mock.Assert(() => _wrapperMap.Get(instrumetedMethodInfo)); @@ -182,7 +182,7 @@ public void BeforeWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() var metricName = string.Empty; var method = new Method(type, methodName, argumentSignature); - var methodCall = new MethodCall(method, invocationTarget, arguments); + var methodCall = new MethodCall(method, invocationTarget, arguments, false); var info = new InstrumentedMethodInfo(0, methodCall.Method, tracerFactoryName, false, null, null, false); Mock.Arrange(() => wrapper.CanWrap(info)).Returns(new CanWrapResponse(true)); @@ -234,7 +234,7 @@ public void AfterWrappedMethod_SetsNoOpWhenThrowsExceptionTooManyTimes() var metricName = string.Empty; var method = new Method(type, methodName, argumentSignature); - var methodCall = new MethodCall(method, invocationTarget, arguments); + var methodCall = new MethodCall(method, invocationTarget, arguments, false); var info = new InstrumentedMethodInfo(0, methodCall.Method, tracerFactoryName, false, null, null, false); Mock.Arrange(() => wrapper.CanWrap(info)).Returns(new CanWrapResponse(true));