private static void OnSuccess <T>(Task task, IAgent agent, ISegment segment, bool holdTransactionOpen, Action <T> onComplete, TaskContinueWithOption options, TaskContinuationOptions?continuationOptions) where T : Task { segment.RemoveSegmentFromCallStack(); if (task == null) { return; } ITransaction transaction = null; if (holdTransactionOpen) { transaction = agent.CurrentTransaction; transaction.Hold(); } if (task.IsCompleted) { EndSegment(task); } else if (options == TaskContinueWithOption.None) { if (!continuationOptions.HasValue) { task.ContinueWith(EndSegment); } else { task.ContinueWith(EndSegment, continuationOptions.Value); } } else { task.ContinueWith(EndSegment, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } void EndSegment(Task completedTask) { agent.HandleExceptions(EndSegmentWithPossibleException); void EndSegmentWithPossibleException() { onComplete?.Invoke(completedTask as T); if (completedTask != null && completedTask.IsFaulted) { segment.End(completedTask.Exception); } else { segment.End(); } transaction?.Release(); } } }
private void EndTransaction(ISegment segment, ITransaction transaction, IOwinContext owinContext, Exception appException) { try { var responseStatusCode = owinContext.Response.StatusCode; if (appException != null) { transaction.NoticeError(appException); //Response code may not be 500 for exception cases, //because that appears to be handled at the web host or server level responseStatusCode = 500; } if (responseStatusCode >= 400) { //Attempt low-priority transaction name to reduce chance of metric grouping issues. transaction.SetWebTransactionName(WebTransactionType.StatusCode, $"{responseStatusCode}", TransactionNamePriority.StatusCode); } segment.End(); transaction.SetHttpResponseStatusCode(responseStatusCode); transaction.End(); } catch (Exception ex) { _agent.SafeHandleException(ex); } }
private void EndTransaction(ISegment segment, ITransaction transaction, HttpContext context, Exception appException) { try { var responseStatusCode = context.Response.StatusCode; //We only keep 1 error per transaction so we are prioritizing the error that made its way //all the way to our middleware over the error caught by the ExceptionHandlerMiddleware. //It's possible that the 2 errors are the same under certain circumstances. if (appException != null) { transaction.NoticeError(appException); //Looks like we won't accurately notice that a 500 is going to be returned for exception cases, //because that appears to be handled at the web host level or server (kestrel) level responseStatusCode = 500; } else if (_inspectingHttpContextForErrorsIsEnabled) { try { Statics.NoticeErrorFromContextIfAvailable(context, transaction); } catch (Exception e) { //We need to catch and handle exceptions here so that the transaction and segment can still end appropriately _inspectingHttpContextForErrorsIsEnabled = false; _agent.Logger.Log(Agent.Extensions.Logging.Level.Info, "Inspecting errors from the IExceptionHandlerFeature is disabled, usually because that AspNetCore feature is not available. Debug Level logs will contain more information."); _agent.Logger.Log(Agent.Extensions.Logging.Level.Debug, $"Error when requesting IExceptionHandlerFeature: {e}"); } } if (responseStatusCode >= 400) { //Attempt low-priority transaction name to reduce chance of metric grouping issues. transaction.SetWebTransactionName(WebTransactionType.StatusCode, $"{responseStatusCode}", TransactionNamePriority.StatusCode); } segment.End(); transaction.SetHttpResponseStatusCode(responseStatusCode); transaction.End(); } catch (Exception ex) { _agent.SafeHandleException(ex); } }
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { var transactionAlreadyExists = transaction.IsValid; var methodInfo = TryGetMethodInfo(instrumentedMethodCall); if (methodInfo == null) { throw new NullReferenceException("methodInfo"); } var instrumentedMethodName = instrumentedMethodCall.MethodCall.Method.MethodName; var parameters = GetParameters(instrumentedMethodCall.MethodCall, methodInfo, instrumentedMethodCall.MethodCall.MethodArguments, agent); ReportSupportabilityMetric_InvocationMethod(agent, instrumentedMethodName); var isTAP = instrumentedMethodCall.InstrumentedMethodInfo.Method.Type.Name == TAPTypeNameShort; var shouldTryProcessInboundCatOrDT = _methodNamesStart.Contains(instrumentedMethodName); var shouldTryEndTransaction = _methodNamesEndTrx.Contains(instrumentedMethodName); var uri = OperationContext.Current?.IncomingMessageHeaders?.To; var transactionName = GetTransactionName(agent, uri, methodInfo); // In all cases, we should record this work in a transaction. // either create it or use the one that is already there // For InvokeEnd, we expect a transaction to be there, but create it // just in case. if (!transactionAlreadyExists) { transaction = agent.CreateTransaction( isWeb: true, category: EnumNameCache <WebTransactionType> .GetName(WebTransactionType.WCF), transactionDisplayName: "Windows Communication Foundation", doNotTrackAsUnitOfWork: false); transaction.GetExperimentalApi().SetWrapperToken(_wrapperToken); } var requestPath = uri?.AbsolutePath; if (!string.IsNullOrEmpty(requestPath)) { transaction.SetUri(requestPath); } // For InvokeBegin, Invoke, or InvokeAsync, Set the transaction name and process // CAT or DT request information. if (shouldTryProcessInboundCatOrDT) { transaction.SetWebTransactionName(WebTransactionType.WCF, transactionName, TransactionNamePriority.FrameworkHigh); transaction.SetRequestParameters(parameters); if (!transactionAlreadyExists) { var transportType = TransportType.Other; var msgProperties = OperationContext.Current?.IncomingMessageProperties; if (msgProperties != null && msgProperties.TryGetValue(HttpRequestMessageProperty.Name, out var httpRequestMessageObject)) { if (httpRequestMessageObject is HttpRequestMessageProperty) { transportType = TransportType.HTTP; } } transaction.AcceptDistributedTraceHeaders(OperationContext.Current, ExtractHeaderValue, transportType); } } // Don't create a segment to cover the EndInvoke on TAP Invocation // but we need to instrument the EndInvoke so that we can close the // transaction and send the CAT Response. The continuation that // is used for TAP will not reliably have access to the OperationContext ISegment segment = null; if (!isTAP || _methodNamesStart.Contains(instrumentedMethodName)) { segment = transaction.StartTransactionSegment(instrumentedMethodCall.MethodCall, transactionName); if (isTAP) { segment.AlwaysDeductChildDuration = true; } } Guid?wrapperExecutionID = null; void WriteLogMessage(string message) { if (!agent.Logger.IsEnabledFor(Agent.Extensions.Logging.Level.Finest)) { return; } if (wrapperExecutionID == null) { wrapperExecutionID = Guid.NewGuid(); } transaction.LogFinest($"Execution {wrapperExecutionID} - {instrumentedMethodName}: {message}"); } var handledException = false; var isTAPContinuation = false; // This continuation method is meant for TAP Async only (InvokeAsync) // We don't end the transaction at this point because we dont // have access to the OperationContext. We rely on the (InvokeEnd) // to handle this part. void HandleContinuation(System.Threading.Tasks.Task t) { WriteLogMessage("Continuation"); if (t.IsFaulted) { WriteLogMessage("Continuation - Notice Exception"); transaction.NoticeError(t.Exception); } if (segment != null) { WriteLogMessage("Continuation - End Segment"); segment.End(); } } return(Delegates.GetDelegateFor( // This could occur on the Invoke, InvokeBegin, InvokeEnd onFailure: (Exception ex) => { WriteLogMessage("OnFailure"); transaction.NoticeError(ex); handledException = true; }, // This will get called on both Begin/End and TAP (InvokeAsync) onSuccess: (System.Threading.Tasks.Task result) => { // If it is TAP, need to wait until the async work is done. // attach continuation to determine if exception has occurred and // to end the segment. For Begin/End, there is nothing to do // in the continuation. if (isTAP) { WriteLogMessage("OnSuccess - Schedule Continuation"); isTAPContinuation = true; result.ContinueWith(HandleContinuation); } }, onComplete: () => { // If this is the TAP InvokeAsync, there is nothing to do here // The continuation has been set up and it will determine if there // are any problems and to end the segment. if (isTAPContinuation) { WriteLogMessage("OnComplete - NoOp wait for continuation"); return; } // In all cases, there is a segment, we want to end it. // The only case where we wouldn't have a segment would be // in the InvokeEnd for TAP if (segment != null) { WriteLogMessage("OnComplete - End Segment"); segment.End(); } // If an exception has occurred or if this is Invoke or InvokeEnd, // the transaction should be closed and CAT Response prepared. if (handledException || shouldTryEndTransaction) { var wcfStartedTransaction = transaction.GetExperimentalApi().GetWrapperToken() == _wrapperToken; if (wcfStartedTransaction) { WriteLogMessage("OnComplete - End transaction"); transaction.End(); ProcessResponse(transaction, OperationContext.Current); } } })); }