diff --git a/src/Elastic.Apm.Azure.CosmosDb/AzureCosmosDbTracer.cs b/src/Elastic.Apm.Azure.CosmosDb/AzureCosmosDbTracer.cs index 7a0be6c05..0855f6cdb 100644 --- a/src/Elastic.Apm.Azure.CosmosDb/AzureCosmosDbTracer.cs +++ b/src/Elastic.Apm.Azure.CosmosDb/AzureCosmosDbTracer.cs @@ -128,7 +128,7 @@ public ISpan StartSpan(IApmAgent agent, string method, Uri requestUrl, Func kv, string action) } else { - var span = ApmAgent.GetCurrentExecutionSegment().StartSpan(transactionName, ApiConstants.TypeMessaging, ServiceBus.SubType, action); + var span = ApmAgent.GetCurrentExecutionSegment().StartSpan(transactionName, ApiConstants.TypeMessaging, ServiceBus.SubType, action, isExitSpan: true); + if (queueName != null) span.Context.Message = new Message { Queue = new Queue { Name = queueName } }; + segment = span; } diff --git a/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs b/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs index 16e6931b9..4457c25aa 100644 --- a/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.ServiceBus/MicrosoftAzureServiceBusDiagnosticListener.cs @@ -19,7 +19,7 @@ namespace Elastic.Apm.Azure.ServiceBus /// /// Creates spans for diagnostic events from Microsoft.Azure.ServiceBus /// - internal class MicrosoftAzureServiceBusDiagnosticListener: DiagnosticListenerBase + internal class MicrosoftAzureServiceBusDiagnosticListener : DiagnosticListenerBase { private readonly ApmAgent _realAgent; private readonly ConcurrentDictionary _processingSegments = new ConcurrentDictionary(); @@ -105,7 +105,7 @@ private void OnProcessStart(KeyValuePair kv, string action, Prop return; } - var queueName = cachedProperties.Fetch(kv.Value,"Entity") as string; + var queueName = cachedProperties.Fetch(kv.Value, "Entity") as string; if (MatchesIgnoreMessageQueues(queueName)) return; @@ -124,11 +124,12 @@ private void OnProcessStart(KeyValuePair kv, string action, Prop if (!_processingSegments.TryAdd(activityId, transaction)) { - Logger.Trace()?.Log( - "Could not add {Action} transaction {TransactionId} for activity {ActivityId} to tracked segments", - action, - transaction.Id, - activityId); + Logger.Trace() + ?.Log( + "Could not add {Action} transaction {TransactionId} for activity {ActivityId} to tracked segments", + action, + transaction.Id, + activityId); } } @@ -140,7 +141,7 @@ private void OnReceiveStart(KeyValuePair kv, string action, Prop return; } - var queueName = cachedProperties.Fetch(kv.Value,"Entity") as string; + var queueName = cachedProperties.Fetch(kv.Value, "Entity") as string; if (MatchesIgnoreMessageQueues(queueName)) return; @@ -159,9 +160,12 @@ private void OnReceiveStart(KeyValuePair kv, string action, Prop } else { - var span = ApmAgent.GetCurrentExecutionSegment().StartSpan(transactionName, ApiConstants.TypeMessaging, ServiceBus.SubType, action); + var span = ApmAgent.GetCurrentExecutionSegment() + .StartSpan(transactionName, ApiConstants.TypeMessaging, ServiceBus.SubType, action, true); + if (queueName != null) span.Context.Message = new Message { Queue = new Queue { Name = queueName } }; + segment = span; } @@ -170,12 +174,13 @@ private void OnReceiveStart(KeyValuePair kv, string action, Prop if (!_processingSegments.TryAdd(activityId, segment)) { - Logger.Trace()?.Log( - "Could not add {Action} {SegmentName} {TransactionId} for activity {ActivityId} to tracked segments", - action, - segment is ITransaction ? "transaction" : "span", - segment.Id, - activityId); + Logger.Trace() + ?.Log( + "Could not add {Action} {SegmentName} {TransactionId} for activity {ActivityId} to tracked segments", + action, + segment is ITransaction ? "transaction" : "span", + segment.Id, + activityId); } } @@ -186,10 +191,11 @@ private bool MatchesIgnoreMessageQueues(string name) var matcher = WildcardMatcher.AnyMatch(_realAgent.ConfigurationStore.CurrentSnapshot.IgnoreMessageQueues, name); if (matcher != null) { - Logger.Debug()?.Log( - "Not tracing message from {QueueName} because it matched IgnoreMessageQueues pattern {Matcher}", - name, - matcher.GetMatcher()); + Logger.Debug() + ?.Log( + "Not tracing message from {QueueName} because it matched IgnoreMessageQueues pattern {Matcher}", + name, + matcher.GetMatcher()); return true; } } @@ -213,7 +219,7 @@ private void OnSendStart(KeyValuePair kv, string action, Propert } var activity = Activity.Current; - var queueName = cachedProperties.Fetch(kv.Value,"Entity") as string; + var queueName = cachedProperties.Fetch(kv.Value, "Entity") as string; var destinationAddress = cachedProperties.Fetch(kv.Value, "Endpoint") as Uri; if (MatchesIgnoreMessageQueues(queueName)) @@ -241,11 +247,12 @@ private void OnSendStart(KeyValuePair kv, string action, Propert if (!_processingSegments.TryAdd(activity.Id, span)) { - Logger.Trace()?.Log( - "Could not add {Action} span {SpanId} for activity {ActivityId} to tracked segments", - action, - span.Id, - activity.Id); + Logger.Trace() + ?.Log( + "Could not add {Action} span {SpanId} for activity {ActivityId} to tracked segments", + action, + span.Id, + activity.Id); } } @@ -260,9 +267,10 @@ private void OnStop(KeyValuePair kv, PropertyFetcherCollection c if (!_processingSegments.TryRemove(activity.Id, out var segment)) { - Logger.Trace()?.Log( - "Could not find segment for activity {ActivityId} in tracked segments", - activity.Id); + Logger.Trace() + ?.Log( + "Could not find segment for activity {ActivityId} in tracked segments", + activity.Id); return; } @@ -290,9 +298,10 @@ private void OnException(KeyValuePair kv) if (!_processingSegments.TryRemove(activity.Id, out var segment)) { - Logger.Trace()?.Log( - "Could not find segment for activity {ActivityId} in tracked segments", - activity.Id); + Logger.Trace() + ?.Log( + "Could not find segment for activity {ActivityId} in tracked segments", + activity.Id); return; } diff --git a/src/Elastic.Apm.Azure.Storage/AzureBlobStorageDiagnosticListener.cs b/src/Elastic.Apm.Azure.Storage/AzureBlobStorageDiagnosticListener.cs index 1f8fad33c..3b3f6ea93 100644 --- a/src/Elastic.Apm.Azure.Storage/AzureBlobStorageDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.Storage/AzureBlobStorageDiagnosticListener.cs @@ -204,7 +204,7 @@ private void OnStart(KeyValuePair kv, string action) ? $"{AzureBlobStorage.SpanName} {action} {blobUrl.ResourceName}" : $"{AzureBlobStorage.SpanName} {action}"; - var span = currentSegment.StartSpan(spanName, ApiConstants.TypeStorage, AzureBlobStorage.SubType, action); + var span = currentSegment.StartSpan(spanName, ApiConstants.TypeStorage, AzureBlobStorage.SubType, action, true); if (span is Span realSpan) realSpan.InstrumentationFlag = InstrumentationFlag.Azure; @@ -227,9 +227,7 @@ private static void SetDestination(ISpan span, BlobUrl blobUrl) => Address = blobUrl.FullyQualifiedNamespace, Service = new Destination.DestinationService { - Name = AzureBlobStorage.SubType, - Resource = $"{AzureBlobStorage.SubType}/{blobUrl.StorageAccountName}", - Type = ApiConstants.TypeStorage + Resource = $"{AzureBlobStorage.SubType}/{blobUrl.StorageAccountName}" } }; diff --git a/src/Elastic.Apm.Azure.Storage/AzureFileShareStorageDiagnosticListener.cs b/src/Elastic.Apm.Azure.Storage/AzureFileShareStorageDiagnosticListener.cs index 19111e8a8..3ec480106 100644 --- a/src/Elastic.Apm.Azure.Storage/AzureFileShareStorageDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.Storage/AzureFileShareStorageDiagnosticListener.cs @@ -103,7 +103,7 @@ private void OnStart(KeyValuePair kv, string action) ? $"{AzureFileStorage.SpanName} {action} {fileShareUrl.ResourceName}" : $"{AzureFileStorage.SpanName} {action}"; - var span = currentSegment.StartSpan(spanName, ApiConstants.TypeStorage, AzureFileStorage.SubType, action); + var span = currentSegment.StartSpan(spanName, ApiConstants.TypeStorage, AzureFileStorage.SubType, action, true); if (span is Span realSpan) realSpan.InstrumentationFlag = InstrumentationFlag.Azure; @@ -125,12 +125,7 @@ private static void SetDestination(ISpan span, FileShareUrl fileShareUrl) => span.Context.Destination = new Destination { Address = fileShareUrl.FullyQualifiedNamespace, - Service = new Destination.DestinationService - { - Name = AzureFileStorage.SubType, - Resource = $"{AzureFileStorage.SubType}/{fileShareUrl.StorageAccountName}", - Type = ApiConstants.TypeStorage - } + Service = new Destination.DestinationService { Resource = $"{AzureFileStorage.SubType}/{fileShareUrl.StorageAccountName}" } }; private void OnStop() diff --git a/src/Elastic.Apm.Azure.Storage/AzureQueueStorageDiagnosticListener.cs b/src/Elastic.Apm.Azure.Storage/AzureQueueStorageDiagnosticListener.cs index 53b53bd88..84f31ef2b 100644 --- a/src/Elastic.Apm.Azure.Storage/AzureQueueStorageDiagnosticListener.cs +++ b/src/Elastic.Apm.Azure.Storage/AzureQueueStorageDiagnosticListener.cs @@ -110,7 +110,7 @@ private void OnSendStart(KeyValuePair kv) ? $"{AzureQueueStorage.SpanName} SEND" : $"{AzureQueueStorage.SpanName} SEND to {queueName}"; - var span = currentSegment.StartSpan(spanName, ApiConstants.TypeMessaging, AzureQueueStorage.SubType, "send"); + var span = currentSegment.StartSpan(spanName, ApiConstants.TypeMessaging, AzureQueueStorage.SubType, "send", true); if (span is Span realSpan) realSpan.InstrumentationFlag = InstrumentationFlag.Azure; diff --git a/src/Elastic.Apm.Azure.Storage/MicrosoftAzureBlobStorageTracer.cs b/src/Elastic.Apm.Azure.Storage/MicrosoftAzureBlobStorageTracer.cs index 2064323a6..cf628d789 100644 --- a/src/Elastic.Apm.Azure.Storage/MicrosoftAzureBlobStorageTracer.cs +++ b/src/Elastic.Apm.Azure.Storage/MicrosoftAzureBlobStorageTracer.cs @@ -110,16 +110,14 @@ public ISpan StartSpan(IApmAgent agent, string method, Uri requestUrl, Func kv) grpcMethodName = "unknown"; Logger.Trace()?.Log("Starting span for gRPC call, method:{methodName}", grpcMethodName); - var newSpan = currentTransaction.StartSpan(grpcMethodName, ApiConstants.TypeExternal, ApiConstants.SubTypeGrpc); + var newSpan = currentTransaction.StartSpan(grpcMethodName, ApiConstants.TypeExternal, ApiConstants.SubTypeGrpc, isExitSpan: true); ProcessingRequests.TryAdd(requestObject, newSpan); } } diff --git a/src/Elastic.Apm.MongoDb/DiagnosticSource/MongoDiagnosticListener.cs b/src/Elastic.Apm.MongoDb/DiagnosticSource/MongoDiagnosticListener.cs index 321b30507..db8506bcb 100644 --- a/src/Elastic.Apm.MongoDb/DiagnosticSource/MongoDiagnosticListener.cs +++ b/src/Elastic.Apm.MongoDb/DiagnosticSource/MongoDiagnosticListener.cs @@ -55,7 +55,7 @@ private void HandleCommandStartEvent(CommandStartedEvent @event) var span = currentExecutionSegment.StartSpan( @event.CommandName, ApiConstants.TypeDb, - "mongo"); + "mongo", isExitSpan: true); if (!_processingQueries.TryAdd(@event.RequestId, span)) { diff --git a/src/Elastic.Apm.MongoDb/LICENSE b/src/Elastic.Apm.MongoDb/LICENSE index b05260b0f..1efab7f1b 100644 --- a/src/Elastic.Apm.MongoDb/LICENSE +++ b/src/Elastic.Apm.MongoDb/LICENSE @@ -199,8 +199,7 @@ Apache License WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - - + ========== Elastic.Apm.MongoDb ---------- diff --git a/src/Elastic.Apm.SqlClient/SqlEventListener.cs b/src/Elastic.Apm.SqlClient/SqlEventListener.cs index e0c810d89..9f3046fc9 100644 --- a/src/Elastic.Apm.SqlClient/SqlEventListener.cs +++ b/src/Elastic.Apm.SqlClient/SqlEventListener.cs @@ -94,7 +94,7 @@ private void ProcessBeginExecute(IReadOnlyList payload) : database; var span = ExecutionSegmentCommon.StartSpanOnCurrentExecutionSegment(_apmAgent, spanName, ApiConstants.TypeDb, ApiConstants.SubtypeMssql, - InstrumentationFlag.SqlClient); + InstrumentationFlag.SqlClient, isExitSpan: true); if (span == null) return; diff --git a/src/Elastic.Apm.StackExchange.Redis/ElasticApmProfiler.cs b/src/Elastic.Apm.StackExchange.Redis/ElasticApmProfiler.cs index 2fc575986..70b5ff937 100644 --- a/src/Elastic.Apm.StackExchange.Redis/ElasticApmProfiler.cs +++ b/src/Elastic.Apm.StackExchange.Redis/ElasticApmProfiler.cs @@ -193,7 +193,7 @@ private static void ProcessCommand(IProfiledCommand profiledCommand, IExecutionS // TODO: clear the raw stacktrace as it won't be representative of the call stack at // the point at which the call to redis happens, and therefore misleading to include - }, ApiConstants.SubTypeRedis, "query"); + }, ApiConstants.SubTypeRedis, "query", true); } private static string GetCommand(IProfiledCommand profiledCommand) => diff --git a/src/Elastic.Apm/Api/Destination.cs b/src/Elastic.Apm/Api/Destination.cs index f0a4bb8a5..03559ba88 100644 --- a/src/Elastic.Apm/Api/Destination.cs +++ b/src/Elastic.Apm/Api/Destination.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System; using Elastic.Apm.Api.Constraints; namespace Elastic.Apm.Api @@ -72,6 +73,7 @@ public class DestinationService /// Identifier for the destination service (e.g. 'http://elastic.co', 'elasticsearch', 'rabbitmq')" /// [MaxLength] + [Obsolete("This field will be removed in future versions")] public string Name { get; set; } /// @@ -85,6 +87,7 @@ public class DestinationService /// Type of the destination service (e.g. 'db', 'elasticsearch'). Should typically be the same as span.type. /// [MaxLength] + [Obsolete("This field will be removed in future versions")] public string Type { get; set; } } diff --git a/src/Elastic.Apm/Api/IExecutionSegment.cs b/src/Elastic.Apm/Api/IExecutionSegment.cs index a57399361..54885e5d2 100644 --- a/src/Elastic.Apm/Api/IExecutionSegment.cs +++ b/src/Elastic.Apm/Api/IExecutionSegment.cs @@ -125,7 +125,8 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// /// The subtype of the span. /// The action of the span. - void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null); + /// Indicates if this span is an exit span. + void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -138,7 +139,8 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// The that points to the code that you want to capture as a span. /// The subtype of the span. /// The action of the span. - void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null); + /// Indicates if this span is an exit span. + void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -155,10 +157,11 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// The subtype of the span. /// The action of the span. /// The return type of the code that you want to capture as span. + /// Indicates if this span is an exit span. /// /// The result of the . /// - T CaptureSpan(string name, string type, Func func, string subType = null, string action = null); + T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -175,10 +178,11 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// The subtype of the span. /// The action of the span. /// The return type of the code that you want to capture as span. + /// Indicates if this span is an exit span. /// /// The result of the . /// - T CaptureSpan(string name, string type, Func func, string subType = null, string action = null); + T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -191,8 +195,9 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// The that points to the async code that you want to capture as a span. /// The subtype of the span. /// The action of the span. + /// Indicates if this span is an exit span. /// The that you can await on. - Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null); + Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -208,8 +213,9 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// /// The subtype of the span. /// The action of the span. + /// Indicates if this span is an exit span. /// The that you can await on. - Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null); + Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -225,9 +231,10 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// /// The subtype of the span. /// The action of the span. + /// Indicates if this span is an exit span. /// The return type of the that you want to capture as span. /// The that you can await on. - Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null); + Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false); /// /// This is a convenient method which starts and ends a span on the given execution segment and captures unhandled @@ -244,9 +251,10 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// /// The subtype of the span. /// The action of the span. + /// Indicates if this span is an exit span. /// The return type of the that you want to capture as span. /// The that you can await on. - Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null); + Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false); /// /// Ends the item and schedules it to be reported to the APM Server. @@ -329,8 +337,9 @@ void CaptureException(Exception exception, string culprit = null, bool isHandled /// The type of the span. /// The subtype of the span. /// The action of the span. + /// Indicates if this span is an exit span. /// Returns the newly created and active span. - ISpan StartSpan(string name, string type, string subType = null, string action = null); + ISpan StartSpan(string name, string type, string subType = null, string action = null, bool isExitSpan = false); /// /// Returns the value of a label. diff --git a/src/Elastic.Apm/Api/ISpan.cs b/src/Elastic.Apm/Api/ISpan.cs index e10904a85..7dfbabe40 100644 --- a/src/Elastic.Apm/Api/ISpan.cs +++ b/src/Elastic.Apm/Api/ISpan.cs @@ -23,6 +23,11 @@ public interface ISpan : IExecutionSegment /// SpanContext Context { get; } + /// + /// Indicates that this span is an exit span. + /// + public bool IsExitSpan { get; } + /// /// The stack trace which was captured for the given span. /// diff --git a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs index 6bd229142..4c6c453a6 100644 --- a/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs +++ b/src/Elastic.Apm/DiagnosticListeners/HttpDiagnosticListenerImplBase.cs @@ -24,15 +24,16 @@ internal abstract class HttpDiagnosticListenerImplBase : Di where TResponse : class { private const string EventExceptionPropertyName = "Exception"; - private const string EventResponsePropertyName = "Response"; protected const string EventRequestPropertyName = "Request"; - private readonly ApmAgent _realAgent; - private readonly HttpTraceConfiguration _configuration; + private const string EventResponsePropertyName = "Response"; /// /// Keeps track of ongoing requests /// - internal readonly ConcurrentDictionary ProcessingRequests = new ConcurrentDictionary(); + internal readonly ConcurrentDictionary ProcessingRequests = new(); + + private readonly HttpTraceConfiguration _configuration; + private readonly ApmAgent _realAgent; protected HttpDiagnosticListenerImplBase(IApmAgent agent) : base(agent) { @@ -48,10 +49,10 @@ protected HttpDiagnosticListenerImplBase(IApmAgent agent) : base(agent) protected abstract bool RequestHeadersContain(TRequest request, string headerName); - protected abstract int ResponseGetStatusCode(TResponse response); - protected abstract bool RequestTryGetHeader(TRequest request, string headerName, out string value); + protected abstract int ResponseGetStatusCode(TResponse response); + protected abstract string ExceptionEventKey { get; } internal abstract string StartEventKey { get; } @@ -134,16 +135,10 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl) return; } - if (_realAgent?.TracerInternal.CurrentSpan is Span currentSpan) + if (_realAgent?.TracerInternal.CurrentSpan is Span currentSpan && currentSpan.IsExitSpan) { - // if the current span is an exit span, don't create a span for the current request - // but still propagate trace context - if (currentSpan.InstrumentationFlag == InstrumentationFlag.Azure - || currentSpan.InstrumentationFlag == InstrumentationFlag.Elasticsearch) - { - PropagateTraceContext(request, transaction, currentSpan); - return; - } + PropagateTraceContext(request, transaction, currentSpan); + return; } var method = RequestGetMethod(request); @@ -171,7 +166,7 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl) if (_configuration?.CaptureSpan ?? false) { span = ExecutionSegmentCommon.StartSpanOnCurrentExecutionSegment(ApmAgent, $"{method} {requestUrl.Host}", - ApiConstants.TypeExternal, ApiConstants.SubtypeHttp, InstrumentationFlag.HttpClient, true); + ApiConstants.TypeExternal, ApiConstants.SubtypeHttp, InstrumentationFlag.HttpClient, true, true); if (span is null) { @@ -181,7 +176,8 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl) } else { - Logger.Trace()?.Log("Skip creating span for outgoing HTTP request to {RequestUrl} as not to known service", requestUrl.Sanitize()); + Logger.Trace() + ?.Log("Skip creating span for outgoing HTTP request to {RequestUrl} as not to known service", requestUrl.Sanitize()); return; } } diff --git a/src/Elastic.Apm/Helpers/UrlUtils.cs b/src/Elastic.Apm/Helpers/UrlUtils.cs index e4ee14150..a0e5574a6 100644 --- a/src/Elastic.Apm/Helpers/UrlUtils.cs +++ b/src/Elastic.Apm/Helpers/UrlUtils.cs @@ -37,7 +37,7 @@ internal static Destination.DestinationService ExtractService(Uri url, ISpan spa { var port = url.IsDefaultPort ? string.Empty : $":{url.Port}"; var scheme = $"{url.Scheme}://"; - return new Destination.DestinationService { Type = span.Type, Name = scheme + url.Host + port, Resource = $"{url.Host}:{url.Port}" }; + return new Destination.DestinationService { Resource = $"{url.Host}:{url.Port}" }; } } } diff --git a/src/Elastic.Apm/Model/DbSpanCommon.cs b/src/Elastic.Apm/Model/DbSpanCommon.cs index 2eb411ec5..f6a8da177 100644 --- a/src/Elastic.Apm/Model/DbSpanCommon.cs +++ b/src/Elastic.Apm/Model/DbSpanCommon.cs @@ -30,7 +30,7 @@ internal ISpan StartSpan(IApmAgent agent, IDbCommand dbCommand, InstrumentationF { var spanName = GetDbSpanName(dbCommand); return ExecutionSegmentCommon.StartSpanOnCurrentExecutionSegment(agent, spanName, ApiConstants.TypeDb, subType, instrumentationFlag, - captureStackTraceOnStart); + captureStackTraceOnStart, true); } internal static string GetDbSpanName(IDbCommand dbCommand) => diff --git a/src/Elastic.Apm/Model/ExecutionSegmentCommon.cs b/src/Elastic.Apm/Model/ExecutionSegmentCommon.cs index 66e5d0a1c..b806c2288 100644 --- a/src/Elastic.Apm/Model/ExecutionSegmentCommon.cs +++ b/src/Elastic.Apm/Model/ExecutionSegmentCommon.cs @@ -292,7 +292,7 @@ public static void CaptureError( } internal static ISpan StartSpanOnCurrentExecutionSegment(IApmAgent agent, string spanName, string spanType, string subType = null, - InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false + InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, bool isExitSpan = false ) { var currentExecutionSegment = agent.GetCurrentExecutionSegment(); @@ -303,10 +303,10 @@ internal static ISpan StartSpanOnCurrentExecutionSegment(IApmAgent agent, string return currentExecutionSegment switch { Span span => span.StartSpanInternal(spanName, spanType, subType, instrumentationFlag: instrumentationFlag, - captureStackTraceOnStart: captureStackTraceOnStart), + captureStackTraceOnStart: captureStackTraceOnStart, isExitSpan: isExitSpan), Transaction transaction => transaction.StartSpanInternal(spanName, spanType, subType, instrumentationFlag: instrumentationFlag, - captureStackTraceOnStart: captureStackTraceOnStart), - ISpan iSpan => iSpan.StartSpan(spanName, spanType, subType), + captureStackTraceOnStart: captureStackTraceOnStart, isExitSpan: isExitSpan), + ISpan iSpan => iSpan.StartSpan(spanName, spanType, subType, isExitSpan: isExitSpan), ITransaction iTransaction => iTransaction.StartSpan(spanName, spanType, subType), _ => null }; diff --git a/src/Elastic.Apm/Model/NoopSpan.cs b/src/Elastic.Apm/Model/NoopSpan.cs index 5ecccd06e..dc551c248 100644 --- a/src/Elastic.Apm/Model/NoopSpan.cs +++ b/src/Elastic.Apm/Model/NoopSpan.cs @@ -50,6 +50,8 @@ internal NoopSpan(string name, string type, string subtype, string action, public SpanContext Context => ReusableContextInstance; + public bool IsExitSpan { get; } + public double? Duration { get; set; } [MaxLength] @@ -94,35 +96,35 @@ public void CaptureException(Exception exception, string culprit = null, bool is public void CaptureErrorLog(ErrorLog errorLog, string parentId = null, Exception exception = null, Dictionary labels = null ) { } - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), capturedAction); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), capturedAction); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this), func); @@ -140,7 +142,7 @@ public void SetLabel(string key, long value) { } public void SetLabel(string key, decimal value) { } - public ISpan StartSpan(string name, string type, string subType = null, string action = null) => + public ISpan StartSpan(string name, string type, string subType = null, string action = null, bool isExitSpan = false) => new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId, this); public bool TryGetLabel(string key, out T value) diff --git a/src/Elastic.Apm/Model/NoopTransaction.cs b/src/Elastic.Apm/Model/NoopTransaction.cs index d6f7c5d7f..bf0aa1237 100644 --- a/src/Elastic.Apm/Model/NoopTransaction.cs +++ b/src/Elastic.Apm/Model/NoopTransaction.cs @@ -89,28 +89,28 @@ public void CaptureException(Exception exception, string culprit = null, bool is Dictionary labels = null ) { } - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), capturedAction); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), capturedAction); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) => ExecutionSegmentCommon.CaptureSpan(new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer), func); public void End() => _currentExecutionSegmentsContainer.CurrentTransaction = null; @@ -127,7 +127,7 @@ public void SetLabel(string key, long value) { } public void SetLabel(string key, decimal value) { } - public ISpan StartSpan(string name, string type, string subType = null, string action = null) => + public ISpan StartSpan(string name, string type, string subType = null, string action = null, bool isExitSpan = false) => new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer); public string EnsureParentId() => string.Empty; diff --git a/src/Elastic.Apm/Model/Span.cs b/src/Elastic.Apm/Model/Span.cs index 4258c5a96..9a6de97e4 100644 --- a/src/Elastic.Apm/Model/Span.cs +++ b/src/Elastic.Apm/Model/Span.cs @@ -11,10 +11,10 @@ using Elastic.Apm.Api.Constraints; using Elastic.Apm.Config; using Elastic.Apm.Helpers; +using Elastic.Apm.Libraries.Newtonsoft.Json; using Elastic.Apm.Logging; using Elastic.Apm.Report; using Elastic.Apm.ServerInfo; -using Elastic.Apm.Libraries.Newtonsoft.Json; namespace Elastic.Apm.Model { @@ -23,8 +23,8 @@ internal class Span : ISpan { private readonly IApmServerInfo _apmServerInfo; - private readonly ChildDurationTimer _childDurationTimer = new ChildDurationTimer(); - private readonly Lazy _context = new Lazy(); + private readonly ChildDurationTimer _childDurationTimer = new(); + private readonly Lazy _context = new(); private readonly ICurrentExecutionSegmentsContainer _currentExecutionSegmentsContainer; private readonly Transaction _enclosingTransaction; @@ -56,7 +56,8 @@ public Span( Span parentSpan = null, InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, - long? timestamp = null + long? timestamp = null, + bool isExitSpan = false ) { InstrumentationFlag = instrumentationFlag; @@ -69,6 +70,7 @@ public Span( _parentSpan = parentSpan; _enclosingTransaction = enclosingTransaction; _apmServerInfo = apmServerInfo; + IsExitSpan = isExitSpan; Name = name; Type = type; @@ -153,6 +155,9 @@ public Span( internal InstrumentationFlag InstrumentationFlag { get; set; } + [JsonIgnore] + public bool IsExitSpan { get; } + [JsonIgnore] public bool IsSampled => _enclosingTransaction.IsSampled; @@ -180,7 +185,7 @@ public Outcome Outcome } [JsonIgnore] - public DistributedTracingData OutgoingDistributedTracingData => new DistributedTracingData( + public DistributedTracingData OutgoingDistributedTracingData => new( TraceId, // When transaction is not sampled then outgoing distributed tracing data should have transaction ID for parent-id part // and not span ID as it does for sampled case. @@ -269,20 +274,21 @@ public bool TryGetLabel(string key, out T value) } - public ISpan StartSpan(string name, string type, string subType = null, string action = null) + public ISpan StartSpan(string name, string type, string subType = null, string action = null, bool isExitSpan = false) { if (Configuration.Enabled && Configuration.Recording) - return StartSpanInternal(name, type, subType, action); + return StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan); return new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId); } internal Span StartSpanInternal(string name, string type, string subType = null, string action = null, - InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, long? timestamp = null + InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, long? timestamp = null, + bool isExitSpan = false ) { var retVal = new Span(name, type, Id, TraceId, _enclosingTransaction, _payloadSender, _logger, _currentExecutionSegmentsContainer, - _apmServerInfo, this, instrumentationFlag, captureStackTraceOnStart, timestamp); + _apmServerInfo, this, instrumentationFlag, captureStackTraceOnStart, timestamp, isExitSpan); if (!string.IsNullOrEmpty(subType)) retVal.Subtype = subType; @@ -398,29 +404,36 @@ public void CaptureException(Exception exception, string culprit = null, bool is labels ); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), capturedAction); + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, + bool isExitSpan = false + ) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), capturedAction); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), capturedAction); + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), capturedAction); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false + ) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, + bool isExitSpan = false + ) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, + bool isExitSpan = false + ) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); public void CaptureError(string message, string culprit, StackFrame[] frames, string parentId = null, Dictionary labels = null) => ExecutionSegmentCommon.CaptureError( @@ -439,7 +452,7 @@ public void CaptureError(string message, string culprit, StackFrame[] frames, st private void DeduceDestination() { - if (!_context.IsValueCreated) + if (!IsExitSpan) return; if (Context.Http != null) @@ -457,33 +470,42 @@ private void DeduceDestination() // Fills Context.Destination.Service void FillDestinationService() { - // Context.Destination must be set by the instrumentation part - otherwise we won't fill Context.Destination.Service - if (Context.Destination == null) + if (!IsExitSpan) return; - // Context.Destination.Service can be set by the instrumentation part - only fill it if needed. - if (Context.Destination.Service != null) + if (!string.IsNullOrEmpty(_context.Value?.Destination?.Service?.Resource)) return; - Context.Destination.Service = new Destination.DestinationService { Type = Type }; + Context.Destination ??= new Destination(); + + if (Context.Destination.Service != null) + Context.Destination.Service = new Destination.DestinationService(); + + Context.Destination.Service = new Destination.DestinationService(); - if (_context.Value.Http != null) + if (Context.Db != null) { - if (!_context.Value.Http.OriginalUrl.IsAbsoluteUri) - { - // Can't fill Destination.Service - we just set it to null and return - Context.Destination.Service = null; - return; - } - - Context.Destination.Service = UrlUtils.ExtractService(_context.Value.Http.OriginalUrl, this); + if (Context.Db.Instance != null) + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type + Context.Db.Instance; + else + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; } - else + else if (Context.Http?.Url != null) + { + if (!string.IsNullOrEmpty(_context?.Value?.Http?.Url)) + Context.Destination.Service = UrlUtils.ExtractService(_context.Value.Http.OriginalUrl, this); + else + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; + } + else if (Context.Message != null) { - // Once messaging is added, for messaging, we'll additionally need to add the queue name here - Context.Destination.Service.Resource = Subtype; - Context.Destination.Service.Name = Subtype; + if (!string.IsNullOrEmpty(Context.Message.Queue?.Name)) + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type + Context.Message.Queue.Name; + else + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; } + else + Context.Destination.Service.Resource = !string.IsNullOrEmpty(Subtype) ? Subtype : Type; } void CopyMissingProperties(Destination src) diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 5b93c59bb..7edaca9b9 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -553,20 +553,21 @@ public bool TryGetLabel(string key, out T value) return false; } - public ISpan StartSpan(string name, string type, string subType = null, string action = null) + public ISpan StartSpan(string name, string type, string subType = null, string action = null, bool isExitSpan = false) { if (Configuration.Enabled && Configuration.Recording) - return StartSpanInternal(name, type, subType, action); + return StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan); return new NoopSpan(name, type, subType, action, _currentExecutionSegmentsContainer, Id, TraceId); } internal Span StartSpanInternal(string name, string type, string subType = null, string action = null, - InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, long? timestamp = null + InstrumentationFlag instrumentationFlag = InstrumentationFlag.None, bool captureStackTraceOnStart = false, long? timestamp = null, + bool isExitSpan = false ) { var retVal = new Span(name, type, Id, TraceId, this, _sender, _logger, _currentExecutionSegmentsContainer, _apmServerInfo, - instrumentationFlag: instrumentationFlag, captureStackTraceOnStart: captureStackTraceOnStart, timestamp: timestamp); + instrumentationFlag: instrumentationFlag, captureStackTraceOnStart: captureStackTraceOnStart, timestamp: timestamp, isExitSpan: isExitSpan); ChildDurationTimer.OnChildStart(retVal.Timestamp); if (!string.IsNullOrEmpty(subType)) @@ -611,29 +612,29 @@ public void CaptureError(string message, string culprit, StackFrame[] frames, st labels ); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), capturedAction); + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), capturedAction); - public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), capturedAction); + public void CaptureSpan(string name, string type, Action capturedAction, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), capturedAction); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public T CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); - public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null) - => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action), func); + public Task CaptureSpan(string name, string type, Func> func, string subType = null, string action = null, bool isExitSpan = false) + => ExecutionSegmentCommon.CaptureSpan(StartSpanInternal(name, type, subType, action, isExitSpan: isExitSpan), func); internal static string StatusCodeToResult(string protocolName, int statusCode) => $"{protocolName} {statusCode.ToString()[0]}xx"; diff --git a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs index 24257ffa5..3489e7ad4 100644 --- a/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs +++ b/test/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs @@ -341,20 +341,10 @@ public async Task HomeIndexDestinationTest(bool withDiagnosticSourceOnly) _capturedPayload.SpansOnFirstTransaction.First(n => n.Context.Http != null).Context.Destination.Should().NotBeNull(); _capturedPayload.SpansOnFirstTransaction.First(n => n.Context.Http != null).Context.Destination.Service.Should().NotBeNull(); - _capturedPayload.SpansOnFirstTransaction.First(n => n.Context.Http != null) - .Context.Destination.Service.Name.ToLower() - .Should() - .Be("https://api.github.com"); - _capturedPayload.SpansOnFirstTransaction.First(n => n.Context.Http != null) .Context.Destination.Service.Resource.ToLower() .Should() .Be("api.github.com:443"); - - _capturedPayload.SpansOnFirstTransaction.First(n => n.Context.Http != null) - .Context.Destination.Service.Type.ToLower() - .Should() - .Be(ApiConstants.TypeExternal); } /// diff --git a/test/Elastic.Apm.Azure.CosmosDb.Tests/MicrosoftAzureCosmosTests.cs b/test/Elastic.Apm.Azure.CosmosDb.Tests/MicrosoftAzureCosmosTests.cs index ecdeae1d3..780e0f06e 100644 --- a/test/Elastic.Apm.Azure.CosmosDb.Tests/MicrosoftAzureCosmosTests.cs +++ b/test/Elastic.Apm.Azure.CosmosDb.Tests/MicrosoftAzureCosmosTests.cs @@ -233,7 +233,12 @@ private void AssertSpan(string action, string db = null, int count = 1) span.Subtype.Should().Be(ApiConstants.SubTypeCosmosDb); if (db != null) + { span.Context.Db.Instance.Should().Be(db); + span.Context.Destination.Service.Resource.Should().Be($"{ApiConstants.SubTypeCosmosDb}:{db}"); + } + else + span.Context.Destination.Service.Resource.Should().Be($"{ApiConstants.SubTypeCosmosDb}"); } } } diff --git a/test/Elastic.Apm.Azure.Storage.Tests/BlobStorageTestsBase.cs b/test/Elastic.Apm.Azure.Storage.Tests/BlobStorageTestsBase.cs index 0d45ccfd7..9a0bfbe85 100644 --- a/test/Elastic.Apm.Azure.Storage.Tests/BlobStorageTestsBase.cs +++ b/test/Elastic.Apm.Azure.Storage.Tests/BlobStorageTestsBase.cs @@ -44,9 +44,7 @@ protected void AssertSpan(string action, string resource, int count = 1) var destination = span.Context.Destination; destination.Address.Should().Be(Environment.StorageAccountConnectionStringProperties.BlobFullyQualifiedNamespace); - destination.Service.Name.Should().Be(AzureBlobStorage.SubType); destination.Service.Resource.Should().Be($"{AzureBlobStorage.SubType}/{Environment.StorageAccountConnectionStringProperties.AccountName}"); - destination.Service.Type.Should().Be(ApiConstants.TypeStorage); } public void Dispose() => ((ApmAgent)Agent).Dispose(); diff --git a/test/Elastic.Apm.Elasticsearch.Tests/VirtualElasticsearchTests.cs b/test/Elastic.Apm.Elasticsearch.Tests/VirtualElasticsearchTests.cs index aa8e60723..cbfef905c 100644 --- a/test/Elastic.Apm.Elasticsearch.Tests/VirtualElasticsearchTests.cs +++ b/test/Elastic.Apm.Elasticsearch.Tests/VirtualElasticsearchTests.cs @@ -54,10 +54,6 @@ public async Task FailOverResultsInSpans() spans.First(n => n.Subtype == ApiConstants.SubtypeElasticsearch).Context.Destination.Port.Should().Be(9200); spans.First(n => n.Subtype == ApiConstants.SubtypeElasticsearch).Context.Destination.Service.Should().NotBeNull(); - spans.First(n => n.Subtype == ApiConstants.SubtypeElasticsearch).Context.Destination.Service.Type.Should().Be(ApiConstants.TypeDb); - spans.First(n => n.Subtype == ApiConstants.SubtypeElasticsearch) - .Context.Destination.Service.Name.Should() - .Be(ApiConstants.SubtypeElasticsearch); spans.First(n => n.Subtype == ApiConstants.SubtypeElasticsearch) .Context.Destination.Service.Resource.Should() .Be(ApiConstants.SubtypeElasticsearch); diff --git a/test/Elastic.Apm.Grpc.Tests/GrpcTests.cs b/test/Elastic.Apm.Grpc.Tests/GrpcTests.cs index 56672fdad..3177de9e8 100644 --- a/test/Elastic.Apm.Grpc.Tests/GrpcTests.cs +++ b/test/Elastic.Apm.Grpc.Tests/GrpcTests.cs @@ -72,15 +72,13 @@ await apmAgent.Tracer.CaptureTransaction("SampleCall", "test", async () => //Make sure all spans are collected Thread.Sleep(500); - payloadSender.Spans.Should().HaveCountGreaterThan(1); + payloadSender.Spans.Should().HaveCountGreaterOrEqualTo(1); payloadSender.Spans.Should() .Contain(span => span.Subtype == ApiConstants.SubTypeGrpc && span.Outcome == Outcome.Success && span.Name == $"/{Greeter.Descriptor.FullName}/{nameof(client.SayHello)}" && span.Context.Destination.Address == "localhost" && span.Context.Destination.Port == SampleAppHostBuilder.SampleAppPort - && span.Context.Destination.Service.Type == "external" - && span.Context.Destination.Service.Name == SampleAppHostBuilder.SampleAppUrl && span.Context.Destination.Service.Resource == $"localhost:{SampleAppHostBuilder.SampleAppPort}" ); @@ -137,15 +135,13 @@ await apmAgent.Tracer.CaptureTransaction("SampleCall", "test", async () => //Make sure all spans are collected Thread.Sleep(500); - payloadSender.Spans.Should().HaveCountGreaterThan(1); + payloadSender.Spans.Should().HaveCountGreaterOrEqualTo(1); payloadSender.Spans.Should() .Contain(span => span.Subtype == ApiConstants.SubTypeGrpc && span.Outcome == Outcome.Failure && span.Name == $"/{Greeter.Descriptor.FullName}/{nameof(client.ThrowAnException)}" && span.Context.Destination.Address == "localhost" && span.Context.Destination.Port == SampleAppHostBuilder.SampleAppPort - && span.Context.Destination.Service.Type == "external" - && span.Context.Destination.Service.Name == SampleAppHostBuilder.SampleAppUrl && span.Context.Destination.Service.Resource == $"localhost:{SampleAppHostBuilder.SampleAppPort}" ); diff --git a/test/Elastic.Apm.SqlClient.Tests/SqlClientListenerTests.cs b/test/Elastic.Apm.SqlClient.Tests/SqlClientListenerTests.cs index 82f762916..d68bea288 100644 --- a/test/Elastic.Apm.SqlClient.Tests/SqlClientListenerTests.cs +++ b/test/Elastic.Apm.SqlClient.Tests/SqlClientListenerTests.cs @@ -107,9 +107,7 @@ await _apmAgent.Tracer.CaptureTransaction("transaction", "type", async transacti span.Context.Destination.Port.Should().NotBeNull(); span.Context.Destination.Service.Should().NotBeNull(); - span.Context.Destination.Service.Name.Should().Be(ApiConstants.SubtypeMssql); span.Context.Destination.Service.Resource.Should().Be(ApiConstants.SubtypeMssql); - span.Context.Destination.Service.Type.Should().Be(ApiConstants.TypeDb); } [Theory] @@ -169,9 +167,7 @@ await _apmAgent.Tracer.CaptureTransaction("transaction", "type", async transacti span.Context.Destination.Port.Should().NotBeNull(); span.Context.Destination.Service.Should().NotBeNull(); - span.Context.Destination.Service.Name.Should().Be(ApiConstants.SubtypeMssql); span.Context.Destination.Service.Resource.Should().Be(ApiConstants.SubtypeMssql); - span.Context.Destination.Service.Type.Should().Be(ApiConstants.TypeDb); } public void Dispose() => _apmAgent?.Dispose(); diff --git a/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs b/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs index 9166ffb4e..dbd4cfef1 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ApiTests.cs @@ -798,22 +798,22 @@ public void destination_properties_set_manually_have_precedence_over_automatical { span.Context.Destination = new Destination { Address = manualAddress }; span.Context.Http = new Http { Method = "PUT", Url = url.ToString() }; - }); + }, isExitSpan: true); tx.CaptureSpan("manually set destination port", "test_span_type", span => { span.Context.Destination = new Destination { Port = manualPort }; span.Context.Http = new Http { Method = "PUT", Url = url.ToString() }; - }); + }, isExitSpan: true); tx.CaptureSpan("manually set destination address to null", "test_span_type", span => { span.Context.Destination = new Destination { Address = null }; span.Context.Http = new Http { Method = "PUT", Url = url.ToString() }; - }); + }, isExitSpan: true); tx.CaptureSpan("manually set destination port to null", "test_span_type", span => { span.Context.Destination = new Destination { Port = null }; span.Context.Http = new Http { Method = "PUT", Url = url.ToString() }; - }); + }, isExitSpan: true); }); } @@ -861,7 +861,8 @@ public void span_with_invalid_Context_Http_Url_should_not_have_destination() agent.Tracer.CaptureTransaction("test TX name", "test TX type", tx => { - tx.CaptureSpan("test span name", "test_span_type", span => { span.Context.Http = new Http { Method = "PUT", Url = "://" }; }); + tx.CaptureSpan("test span name", "test_span_type", span => { span.Context.Http = new Http { Method = "PUT", Url = "://" }; }, + isExitSpan: true); }); } diff --git a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs index 63a96dbce..2a7830568 100644 --- a/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs +++ b/test/Elastic.Apm.Tests/ApiTests/ConvenientApiSpanTests.cs @@ -705,13 +705,13 @@ public void FillSpanContext() { WaitHelpers.SleepMinimum(); t.CaptureSpan("SampleSpan1", "SampleSpanType", - span => { span.Context.Http = new Http { Url = "http://mysite.com", Method = "GET", StatusCode = 200 }; }); + span => { span.Context.Http = new Http { Url = "http://mysite.com", Method = "GET", StatusCode = 200 }; }, isExitSpan: true); t.CaptureSpan("SampleSpan2", "SampleSpanType", span => { span.Context.Db = new Database { Statement = "Select * from MyTable", Type = Database.TypeSql, Instance = "MyInstance" }; - }); + }, isExitSpan: true); }); payloadSender.Spans[0].Name.Should().Be("SampleSpan1"); diff --git a/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs b/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs new file mode 100644 index 000000000..a84cf9eb8 --- /dev/null +++ b/test/Elastic.Apm.Tests/ApiTests/ExitSpanTests.cs @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V under +// one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Apm.Tests.Utilities; +using FluentAssertions; +using Xunit; + +namespace Elastic.Apm.Tests.ApiTests +{ + public class ExitSpanTests + { + [Fact] + public void TestNonExitSpan() + { + + var payloadSender = new MockPayloadSender(); + using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); + agent.Tracer.CaptureTransaction("foo", "bar", t => + { + t.StartSpan("foo", "bar").End(); + }); + + payloadSender.FirstSpan.Context.Destination.Should().BeNull(); + } + + [Fact] + public void SimpleManualExitSpan() + { + var payloadSender = new MockPayloadSender(); + using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)); + agent.Tracer.CaptureTransaction("foo", "bar", t => + { + t.StartSpan("foo", "bar", isExitSpan: true).End(); + }); + + payloadSender.FirstSpan.Context.Destination.Should().NotBeNull(); + payloadSender.FirstSpan.Context.Destination.Service.Resource.Should().Be("bar"); + } + } +}