Exemple #1
0
        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();
                }
            }
        }
Exemple #2
0
        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);
                    }
                }
            }));
        }