示例#1
0
        public void Call_WhenMethodThrows_PreservesStackTrace()
        {
            try
            {
                // Arrange
                InvalidOperationException expectedException     = new InvalidOperationException();
                ExceptionDispatchInfo     expectedExceptionInfo = CreateExceptionInfo(expectedException);
                string expectedStackTrace = expectedExceptionInfo.SourceException.StackTrace;
                ThrowingProgram.ExceptionInfo = expectedExceptionInfo;

                var        host       = JobHostFactory.Create <ThrowingProgram>(null);
                MethodInfo methodInfo = typeof(ThrowingProgram).GetMethod("Throw");

                // Act & Assert
                FunctionInvocationException exception = Assert.Throws <FunctionInvocationException>(
                    () => host.Call(methodInfo));
                Assert.Same(exception.InnerException, expectedException);
                Assert.NotNull(exception.InnerException.StackTrace);
                Assert.True(exception.InnerException.StackTrace.StartsWith(expectedStackTrace));
            }
            finally
            {
                ThrowingProgram.ExceptionInfo = null;
            }
        }
示例#2
0
        public void FunctionFailures_LogsExpectedTraceEvent()
        {
            TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose);

            _hostConfig.Tracing.Tracers.Add(trace);
            JobHost host = new JobHost(_hostConfig);

            MethodInfo methodInfo = GetType().GetMethod("AlwaysFailJob");

            try
            {
                host.Call(methodInfo);
            }
            catch {}

            // We expect 3 error messages total
            TraceEvent[] traceErrors = trace.Traces.Where(p => p.Level == TraceLevel.Error).ToArray();
            Assert.Equal(3, traceErrors.Length);

            // Ensure that all errors include the same exception, with function
            // invocation details
            FunctionInvocationException functionException = traceErrors.First().Exception as FunctionInvocationException;

            Assert.NotNull(functionException);
            Assert.NotEqual(Guid.Empty, functionException.InstanceId);
            Assert.Equal(string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name), functionException.MethodName);
            Assert.True(traceErrors.All(p => functionException == p.Exception));
        }
        public void CreateTraceMonitor_FunctionErrorHandler()
        {
            ParameterInfo parameter = typeof(Functions).GetMethod("TestErrorHandler").GetParameters()[0];

            TraceMonitor traceMonitor = ErrorTriggerListener.CreateTraceMonitor(parameter, _mockExecutor.Object);

            TraceFilter.AnonymousTraceFilter traceFilter = (TraceFilter.AnonymousTraceFilter)traceMonitor.Filters.Single();
            Assert.Equal("Function 'Functions.Test' failed.", traceFilter.Message);

            // first log a function exception for a *different* function
            // don't expect it to pass filter
            FunctionInvocationException functionException = new FunctionInvocationException("Function failed", new Exception("Kaboom!"))
            {
                MethodName = "Microsoft.Azure.WebJobs.Extensions.Tests.Core.ErrorTriggerListenerTests+Functions.Foo"
            };
            TraceEvent traceEvent = new TraceEvent(TraceLevel.Error, "Kaboom!", null, functionException);

            traceMonitor.Trace(traceEvent);
            Assert.Equal(0, traceFilter.GetEvents().Count());

            functionException = new FunctionInvocationException("Function failed", new Exception("Kaboom!"))
            {
                MethodName = "Microsoft.Azure.WebJobs.Extensions.Tests.Core.ErrorTriggerListenerTests+Functions.Test"
            };
            traceEvent = new TraceEvent(TraceLevel.Error, "Kaboom!", null, functionException);
            traceMonitor.Trace(traceEvent);
            Assert.Equal(1, traceFilter.GetEvents().Count());
            Assert.Same(functionException, traceFilter.GetEvents().Single().Exception);
        }
示例#4
0
        public void LogFunctionResult_Failed_SendsCorrectTelemetry()
        {
            var result = CreateDefaultInstanceLogEntry();
            FunctionInvocationException fex = new FunctionInvocationException("Failed");
            ILogger logger = CreateLogger(LogCategories.Results);

            using (logger.BeginFunctionScope(CreateFunctionInstance(_invocationId)))
            {
                logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(durationMs), fex);
            }

            // Errors log an associated Exception
            RequestTelemetry   requestTelemetry   = _channel.Telemetries.OfType <RequestTelemetry>().Single();
            ExceptionTelemetry exceptionTelemetry = _channel.Telemetries.OfType <ExceptionTelemetry>().Single();

            Assert.Equal(2, _channel.Telemetries.Count);
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Id);
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Context.Operation.Id);
            Assert.Equal(_functionShortName, requestTelemetry.Name);
            Assert.Equal(_functionShortName, requestTelemetry.Context.Operation.Name);
            Assert.Equal(defaultIp, requestTelemetry.Context.Location.Ip);
            // TODO: Beef up validation to include properties

            // Exception needs to have associated id
            Assert.Equal(_invocationId.ToString(), exceptionTelemetry.Context.Operation.Id);
            Assert.Equal(_functionShortName, exceptionTelemetry.Context.Operation.Name);
            Assert.Same(fex, exceptionTelemetry.Exception);
            // TODO: Beef up validation to include properties
        }
        public void CreateTraceMonitor_FunctionErrorHandler_CustomFilterType()
        {
            ParameterInfo parameter = typeof(Functions).GetMethod("Test2ErrorHandler").GetParameters()[0];

            TraceMonitor traceMonitor = ErrorTriggerListener.CreateTraceMonitor(parameter, _mockExecutor.Object);

            ErrorTriggerListener.CompositeTraceFilter traceFilter = (ErrorTriggerListener.CompositeTraceFilter)traceMonitor.Filters.Single();
            Assert.NotNull(traceFilter);
            Assert.Equal(typeof(Functions.CustomTraceFilter), traceFilter.InnerTraceFilter.GetType());

            // first log a function exception for a *different* function
            // don't expect it to pass filter
            FunctionInvocationException functionException = new FunctionInvocationException("Function failed", Guid.Empty,
                                                                                            "Microsoft.Azure.WebJobs.Extensions.Tests.Core.ErrorTriggerListenerTests+Functions.Foo", new Exception("Kaboom!"));

            TraceEvent traceEvent = new TraceEvent(TraceLevel.Error, "Kaboom!", null, functionException);

            traceMonitor.Trace(traceEvent);
            Assert.Equal(0, traceFilter.GetEvents().Count());

            functionException = new FunctionInvocationException("Function failed", Guid.Empty,
                                                                "Microsoft.Azure.WebJobs.Extensions.Tests.Core.ErrorTriggerListenerTests+Functions.Test2", new Exception("Kaboom!"));

            traceEvent = new TraceEvent(TraceLevel.Error, "Kaboom!", null, functionException);
            traceMonitor.Trace(traceEvent);
            Assert.Equal(1, traceFilter.GetEvents().Count());
            Assert.Same(functionException, traceFilter.GetEvents().Single().Exception);
        }
        public void LogFunctionResult_Failed_CreatesCorrectState()
        {
            int     logCount = 0;
            ILogger logger   = CreateMockLogger <FormattedLogValuesCollection>((l, e, o, ex, f) =>
            {
                logCount++;
                Assert.Equal(LogLevel.Error, l);
                Assert.Equal(0, e);
                Assert.NotNull(ex);
                Assert.IsType <FunctionInvocationException>(ex);
                Assert.Equal($"Executed '{_functionFullName}' (Failed, Id={_invocationId})", f(o, ex));

                var payload = VerifyResultDefaultsAndConvert(o);
                Assert.False((bool)payload[LoggingKeys.Succeeded]);
                Assert.Equal("Executed '{FullName}' (Failed, Id={InvocationId})", payload[LoggingKeys.OriginalFormat]);
            });

            var result = CreateDefaultInstanceLogEntry();

            var fex = new FunctionInvocationException("Failed");

            logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(450), fex);

            Assert.Equal(1, logCount);
        }
示例#7
0
        public async Task GlobalErrorHandler_SlidingWindow_InvokedAsExpected()
        {
            ErrorTriggerProgram_GlobalSlidingWindowHandler instance = new ErrorTriggerProgram_GlobalSlidingWindowHandler();

            JobHostConfiguration config = new JobHostConfiguration()
            {
                TypeLocator  = new ExplicitTypeLocator(instance.GetType()),
                JobActivator = new ExplicitJobActivator(instance)
            };

            config.UseCore();
            JobHost host = new JobHost(config);
            await host.StartAsync();

            MethodInfo method = instance.GetType().GetMethod("Throw");

            await CallSafe(host, method);
            await CallSafe(host, method);

            Assert.Null(instance.TraceFilter);

            await CallSafe(host, method);

            Assert.NotNull(instance.TraceFilter);

            Assert.Equal("3 events at level 'Error' or lower have occurred within time window 00:05:00.", instance.TraceFilter.Message);
            Assert.Equal(3, instance.TraceFilter.Events.Count);
            foreach (TraceEvent traceEvent in instance.TraceFilter.Events)
            {
                FunctionInvocationException functionException = (FunctionInvocationException)traceEvent.Exception;
                Assert.Equal("Kaboom!", functionException.InnerException.Message);
                Assert.Equal("Microsoft.Azure.WebJobs.Extensions.Tests.Core.ErrorTriggerEndToEndTests+ErrorTriggerProgram_GlobalSlidingWindowHandler.Throw", functionException.MethodName);
            }
        }
示例#8
0
        public void QueueTrigger_WithNonTextualByteArrayMessageUsingQueueTriggerBindingData_Throws()
        {
            try
            {
                // Arrange
                var host = JobHostFactory.Create <QueueTriggerBindingDataProgram>(
                    CloudStorageAccount.DevelopmentStorageAccount);
                MethodInfo        methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod("OnQueue");
                byte[]            contents   = new byte[] { 0x00, 0xFF }; // Not valid UTF-8
                CloudQueueMessage message    = new CloudQueueMessage(contents);

                // Act & Assert
                FunctionInvocationException exception = Assert.Throws <FunctionInvocationException>(
                    () => host.Call(methodInfo, new { message = message }));
                // This exeption shape/message could be better, but it's meets a minimum acceptibility threshold.
                Assert.Equal("Exception binding parameter 'queueTrigger'", exception.InnerException.Message);
                Exception innerException = exception.InnerException.InnerException;
                Assert.IsType <InvalidOperationException>(innerException);
                Assert.Equal("Binding data does not contain expected value 'queueTrigger'.", innerException.Message);
            }
            finally
            {
                QueueTriggerBindingDataProgram.QueueTrigger = null;
            }
        }
示例#9
0
        private void HandleHostError(Microsoft.Azure.WebJobs.Extensions.TraceFilter traceFilter)
        {
            foreach (TraceEvent traceEvent in traceFilter.Events)
            {
                if (traceEvent.Exception is FunctionInvocationException)
                {
                    // For all function invocation events, we notify the invoker so it can
                    // log the error as needed to its function specific logs.
                    FunctionInvocationException invocationException = traceEvent.Exception as FunctionInvocationException;
                    NotifyInvoker(invocationException.MethodName, invocationException);
                }
                else if (traceEvent.Exception is FunctionIndexingException)
                {
                    // For all startup time indexing errors, we accumulate them per function
                    FunctionIndexingException indexingException = traceEvent.Exception as FunctionIndexingException;
                    string formattedError = Utility.FlattenException(indexingException);
                    AddFunctionError(indexingException.MethodName, formattedError);

                    // Also notify the invoker so the error can also be written to the function
                    // log file
                    NotifyInvoker(indexingException.MethodName, indexingException);

                    // Mark the error as handled so indexing will continue
                    indexingException.Handled = true;
                }
            }
        }
        private bool ShouldIgnore(TraceEvent traceEvent)
        {
            if (traceEvent.Exception != null)
            {
                FunctionInvocationException functionException = traceEvent.Exception as FunctionInvocationException;
                if (functionException != null && ErrorTriggerListener.ErrorHandlers.Contains(functionException.MethodName))
                {
                    // We ignore any errors coming from error handlers themselves to prevent
                    // infinite recursion
                    return(true);
                }

                // often we may see multiple sequential error messages for the same
                // exception, so we want to skip the duplicates
                bool isDuplicate = object.ReferenceEquals(_lastException, traceEvent.Exception);
                if (isDuplicate)
                {
                    return(true);
                }

                _lastException = traceEvent.Exception;
            }

            return(false);
        }
        public async Task QueueTrigger_WithNonTextualByteArrayMessageUsingQueueTriggerBindingData_Throws()
        {
            try
            {
                // Arrange
                var host = new HostBuilder()
                           .ConfigureDefaultTestHost <QueueTriggerBindingDataProgram>(c =>
                {
                    c.AddAzureStorage();
                })
                           .Build()
                           .GetJobHost();

                using (host)
                {
                    MethodInfo        methodInfo = typeof(QueueTriggerBindingDataProgram).GetMethod(nameof(QueueTriggerBindingDataProgram.OnQueue));
                    byte[]            contents   = new byte[] { 0x00, 0xFF }; // Not valid UTF-8
                    CloudQueueMessage message    = CloudQueueMessage.CreateCloudQueueMessageFromByteArray(contents);

                    // Act & Assert
                    FunctionInvocationException exception = await Assert.ThrowsAsync <FunctionInvocationException>(
                        () => host.CallAsync(methodInfo, new { message = message }));

                    // This exeption shape/message could be better, but it's meets a minimum acceptibility threshold.
                    Assert.Equal("Exception binding parameter 'queueTrigger'", exception.InnerException.Message);
                    Exception innerException = exception.InnerException.InnerException;
                    Assert.IsType <InvalidOperationException>(innerException);
                    Assert.Equal("Binding data does not contain expected value 'queueTrigger'.", innerException.Message);
                }
            }
            finally
            {
                QueueTriggerBindingDataProgram.QueueTrigger = null;
            }
        }
示例#12
0
        public void FileLogger_LogsFunctionInvocationException()
        {
            var logger = new FileLogger(_category, _fileWriter, () => true, isPrimary: () => true, logType: LogType.Host);

            // throw an exception so we have a stack
            try
            {
                var innerEx = new InvalidOperationException("boom!");
                var ex      = new FunctionInvocationException("Error executing function.", innerEx);
                throw ex;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Test Message");
            }

            _fileWriter.Flush();

            string logFile = Directory.EnumerateFiles(_logFilePath).Single();

            string[] lines = File.ReadAllLines(logFile);

            // A FunctionInvocationException only logs the message.
            Assert.Collection(lines,
                              t => Assert.EndsWith("[Error] Test Message", t),
                              t => Assert.Equal("boom!", t));
        }
        public async Task Invoke_HandlesFunctionInvocationExceptions()
        {
            var ex = new FunctionInvocationException("Kaboom!");

            using (var server = GetTestServer(_ => throw ex))
            {
                var client = server.CreateClient();
                HttpResponseMessage response = await client.GetAsync(string.Empty);

                Assert.Equal(StatusCodes.Status500InternalServerError, (int)response.StatusCode);
                Assert.Null(_loggerProvider.GetAllLogMessages().SingleOrDefault(p => p.Category.Contains(nameof(ExceptionMiddleware))));
            }
        }
示例#14
0
        public async Task Publish_NoTopicSpecified()
        {
            // No topic is specified
            var input = new DaprPubSubEvent("Hello, world!");
            FunctionInvocationException error = await Assert.ThrowsAsync <FunctionInvocationException>(() =>
                                                                                                       this.CallFunctionAsync(nameof(Functions.DaprPubSubEventReturnValueAnyTopic), "input", input));

            // The exception message should reflect the fact that no topic was specified
            ArgumentException innerError = Assert.IsType <ArgumentException>(error.GetBaseException());

            Assert.Contains("No topic", innerError.Message);

            // No requests should have been sent
            Assert.Empty(this.GetDaprRequests());
        }
        public async Task Invoke_HandlesFunctionInvocationExceptions()
        {
            var             ex   = new FunctionInvocationException("Kaboom!");
            RequestDelegate next = (ctxt) =>
            {
                throw ex;
            };
            var middleware = new ExceptionMiddleware(next, _logger);

            var context = new DefaultHttpContext();
            await middleware.Invoke(context);

            Assert.Equal(StatusCodes.Status500InternalServerError, context.Response.StatusCode);
            Assert.Empty(_loggerProvider.GetAllLogMessages());
        }
示例#16
0
        public async Task SendMessage_NoOperationSpecified()
        {
            // No operation is specified in the attribute or in the message
            var input = new DaprBindingMessage("Hello, world!", new Dictionary <string, object> {
                { "key", "myKey" }
            }, binding: "bindingName");
            FunctionInvocationException error = await Assert.ThrowsAsync <FunctionInvocationException>(() =>
                                                                                                       this.CallFunctionAsync(nameof(Functions.DaprConnectorReturnValueAnyMessage), "input", input));

            // The exception message should reflect the fact that no operation was specified
            ArgumentException innerError = Assert.IsType <ArgumentException>(error.GetBaseException());

            Assert.Contains("A non-null operation must be specified", innerError.Message);

            // No requests should have been sent
            Assert.Empty(this.GetDaprRequests());
        }
示例#17
0
        /// <summary>
        /// Set up monitoring + notifications for WebJob errors. This shows how to set things up
        /// manually on startup. You can also use <see cref="ErrorTriggerAttribute"/> to designate
        /// error handler functions.
        /// </summary>
        private static void ConfigureTraceMonitor(JobHostConfiguration config, SendGridConfiguration sendGridConfiguration)
        {
            var notifier = new ErrorNotifier(sendGridConfiguration);

            var traceMonitor = new TraceMonitor()
                               .Filter(new SlidingWindowTraceFilter(TimeSpan.FromMinutes(5), 3))
                               .Filter(p =>
            {
                FunctionInvocationException functionException = p.Exception as FunctionInvocationException;
                return(p.Level == TraceLevel.Error && functionException != null &&
                       functionException.MethodName == "ExtensionsSample.FileSamples.ImportFile");
            }, "ImportFile Job Failed")
                               .Subscribe(notifier.WebNotify, notifier.EmailNotify)
                               .Throttle(TimeSpan.FromMinutes(30));

            config.Tracing.Tracers.Add(traceMonitor);
        }
        private void HandleHostError(Exception exception)
        {
            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            // First, ensure that we've logged to the host log
            // Also ensure we flush immediately to ensure any buffered logs
            // are written
            TraceWriter.Error("A ScriptHost error has occurred", exception);
            TraceWriter.Flush();

            if (exception is FunctionInvocationException)
            {
                // For all function invocation errors, we notify the invoker so it can
                // log the error as needed to its function specific logs.
                FunctionInvocationException invocationException = exception as FunctionInvocationException;
                NotifyInvoker(invocationException.MethodName, invocationException);
            }
            else if (exception is FunctionIndexingException)
            {
                // For all startup time indexing errors, we accumulate them per function
                FunctionIndexingException indexingException = exception as FunctionIndexingException;
                string formattedError = Utility.FlattenException(indexingException);
                AddFunctionError(indexingException.MethodName, formattedError);

                // Also notify the invoker so the error can also be written to the function
                // log file
                NotifyInvoker(indexingException.MethodName, indexingException);

                // Mark the error as handled so indexing will continue
                indexingException.Handled = true;
            }
            else
            {
                // See if we can identify which function caused the error, and if we can
                // log the error as needed to its function specific logs.
                FunctionDescriptor function = null;
                if (TryGetFunctionFromException(Functions, exception, out function))
                {
                    NotifyInvoker(function.Name, exception);
                }
            }
        }
        public void LogFunctionResult_Failed_SendsCorrectTelemetry()
        {
            FunctionInvocationException fex = new FunctionInvocationException("Failed");
            var     result = CreateDefaultInstanceLogEntry(fex);
            ILogger logger = CreateLogger(LogCategories.Results);

            string expectedOperationId, expectedRequestId;

            using (logger.BeginFunctionScope(CreateFunctionInstance(_invocationId), _hostInstanceId))
            {
                expectedRequestId   = Activity.Current.Id;
                expectedOperationId = Activity.Current.RootId;

                logger.LogFunctionResult(result);
            }

            // Errors log an associated Exception
            RequestTelemetry   requestTelemetry   = _channel.Telemetries.OfType <RequestTelemetry>().Single();
            ExceptionTelemetry exceptionTelemetry = _channel.Telemetries.OfType <ExceptionTelemetry>().Single();

            Assert.Equal(2, _channel.Telemetries.Count);
            Assert.Equal(expectedRequestId, requestTelemetry.Id);
            Assert.Equal(expectedOperationId, requestTelemetry.Context.Operation.Id);
            Assert.Null(requestTelemetry.Context.Operation.ParentId);
            Assert.Equal(_functionShortName, requestTelemetry.Name);
            Assert.Equal(_functionShortName, requestTelemetry.Context.Operation.Name);
            Assert.Equal(defaultIp, requestTelemetry.Context.Location.Ip);
            Assert.Contains(LogConstants.InvocationIdKey, requestTelemetry.Properties.Keys);
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Properties[LogConstants.InvocationIdKey]);
            Assert.Equal(LogCategories.Results, requestTelemetry.Properties[LogConstants.CategoryNameKey]);
            Assert.Equal(LogLevel.Error.ToString(), requestTelemetry.Properties[LogConstants.LogLevelKey]);
            // TODO: Beef up validation to include properties

            // Exception needs to have associated id
            Assert.Equal(expectedOperationId, exceptionTelemetry.Context.Operation.Id);
            Assert.Equal(expectedRequestId, exceptionTelemetry.Context.Operation.ParentId);
            Assert.Equal(_functionShortName, exceptionTelemetry.Context.Operation.Name);
            Assert.Same(fex, exceptionTelemetry.Exception);
            Assert.Equal(LogCategories.Results, exceptionTelemetry.Properties[LogConstants.CategoryNameKey]);
            Assert.Equal(LogLevel.Error.ToString(), exceptionTelemetry.Properties[LogConstants.LogLevelKey]);
            // TODO: Beef up validation to include properties
        }
示例#20
0
        private void HandleHostError(Exception exception)
        {
            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            // Note: We do not log to ILogger here as any error has already been logged.

            if (exception is FunctionInvocationException)
            {
                // For all function invocation errors, we notify the invoker so it can
                // log the error as needed to its function specific logs.
                FunctionInvocationException invocationException = exception as FunctionInvocationException;
                NotifyInvoker(invocationException.MethodName, invocationException);
            }
            else if (exception is FunctionIndexingException || exception is FunctionListenerException)
            {
                // For all startup time indexing/listener errors, we accumulate them per function
                FunctionException functionException = exception as FunctionException;
                string            formattedError    = Utility.FlattenException(functionException);
                Utility.AddFunctionError(FunctionErrors, functionException.MethodName, formattedError);

                // Also notify the invoker so the error can also be written to the function
                // log file
                NotifyInvoker(functionException.MethodName, functionException);
            }
            else if (exception is LanguageWorkerChannelException)
            {
                AddLanguageWorkerChannelErrors(_functionDispatcher, FunctionErrors);
            }
            else
            {
                // See if we can identify which function caused the error, and if we can
                // log the error as needed to its function specific logs.
                if (TryGetFunctionFromException(Functions, exception, out FunctionDescriptor function))
                {
                    NotifyInvoker(function.Name, exception);
                }
            }
        }
        public void FunctionFailures_LogsExpectedTraceEvent()
        {
            TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose);

            _hostConfig.Tracing.Tracers.Add(trace);
            JobHost host = new JobHost(_hostConfig);

            MethodInfo methodInfo = GetType().GetMethod("AlwaysFailJob");

            try
            {
                host.Call(methodInfo);
            }
            catch { }

            string expectedName = $"{methodInfo.DeclaringType.FullName}.{methodInfo.Name}";

            // Validate TraceWriter
            // We expect 3 error messages total
            TraceEvent[] traceErrors = trace.Traces.Where(p => p.Level == TraceLevel.Error).ToArray();
            Assert.Equal(3, traceErrors.Length);

            // Ensure that all errors include the same exception, with function
            // invocation details
            FunctionInvocationException functionException = traceErrors.First().Exception as FunctionInvocationException;

            Assert.NotNull(functionException);
            Assert.NotEqual(Guid.Empty, functionException.InstanceId);
            Assert.Equal(expectedName, functionException.MethodName);
            Assert.True(traceErrors.All(p => functionException == p.Exception));

            // Validate Logger
            // Logger only writes out a single log message (which includes the Exception).
            var logger          = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Results).Single();
            var logMessage      = logger.LogMessages.Single();
            var loggerException = logMessage.Exception as FunctionException;

            Assert.NotNull(loggerException);
            Assert.Equal(expectedName, loggerException.MethodName);
        }
        public void LogFunctionResult_Failed_CreatesCorrectState()
        {
            int     logCount = 0;
            ILogger logger   = CreateMockLogger <IDictionary <string, object> >((l, e, o, ex, f) =>
            {
                logCount++;
                Assert.Equal(LogLevel.Error, l);
                Assert.Equal(0, e);
                Assert.NotNull(ex);
                Assert.IsType <FunctionInvocationException>(ex);
                Assert.Null(f(o, ex));

                var payload = VerifyResultDefaultsAndConvert(o);
                Assert.False((bool)payload[LogConstants.SucceededKey]);
            });

            var fex    = new FunctionInvocationException("Failed");
            var result = CreateDefaultInstanceLogEntry(fex);

            logger.LogFunctionResult(result);

            Assert.Equal(1, logCount);
        }
        public void LogFunctionResult_Failed_SendsCorrectTelemetry()
        {
            FunctionInvocationException fex = new FunctionInvocationException("Failed");
            var     result = CreateDefaultInstanceLogEntry(fex);
            ILogger logger = CreateLogger(LogCategories.Results);

            using (logger.BeginFunctionScope(CreateFunctionInstance(_invocationId), _hostInstanceId))
            {
                logger.LogFunctionResult(result);
            }

            // Errors log an associated Exception
            RequestTelemetry   requestTelemetry   = _channel.Telemetries.OfType <RequestTelemetry>().Single();
            ExceptionTelemetry exceptionTelemetry = _channel.Telemetries.OfType <ExceptionTelemetry>().Single();

            Assert.Equal(2, _channel.Telemetries.Count);
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Id);
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Context.Operation.Id);
            Assert.Equal(_functionShortName, requestTelemetry.Name);
            Assert.Equal(_functionShortName, requestTelemetry.Context.Operation.Name);
            Assert.Equal(defaultIp, requestTelemetry.Context.Location.Ip);
            Assert.Equal(LogCategories.Results, requestTelemetry.Properties[LogConstants.CategoryNameKey]);
            Assert.Equal(LogLevel.Error.ToString(), requestTelemetry.Properties[LogConstants.LogLevelKey]);
            // TODO: Beef up validation to include properties

            // Starting the telemetry prefixes/postfixes values to the request id, but the original guid is there
            Assert.Equal(_invocationId.ToString(), requestTelemetry.Id);

            // Exception needs to have associated id
            Assert.Equal(_invocationId.ToString(), exceptionTelemetry.Context.Operation.Id);
            Assert.Equal(_functionShortName, exceptionTelemetry.Context.Operation.Name);
            Assert.Same(fex, exceptionTelemetry.Exception);
            Assert.Equal(LogCategories.Results, exceptionTelemetry.Properties[LogConstants.CategoryNameKey]);
            Assert.Equal(LogLevel.Error.ToString(), exceptionTelemetry.Properties[LogConstants.LogLevelKey]);
            // TODO: Beef up validation to include properties
        }
示例#24
0
        internal static TraceMonitor CreateTraceMonitor(ParameterInfo parameter, ITriggeredFunctionExecutor executor)
        {
            ErrorTriggerAttribute attribute = parameter.GetCustomAttribute <ErrorTriggerAttribute>(inherit: false);

            // Determine whether this is a method level filter, and if so, create the filter
            Func <TraceEvent, bool> methodFilter = null;
            MethodInfo method = (MethodInfo)parameter.Member;
            string     functionLevelMessage = null;

            if (method.Name.EndsWith(ErrorHandlerSuffix, StringComparison.OrdinalIgnoreCase))
            {
                string     sourceMethodName = method.Name.Substring(0, method.Name.Length - ErrorHandlerSuffix.Length);
                MethodInfo sourceMethod     = method.DeclaringType.GetMethod(sourceMethodName);
                if (sourceMethod != null)
                {
                    string sourceMethodFullName = string.Format("{0}.{1}", method.DeclaringType.FullName, sourceMethod.Name);
                    methodFilter = p =>
                    {
                        FunctionInvocationException functionException = p.Exception as FunctionInvocationException;
                        return(p.Level == System.Diagnostics.TraceLevel.Error && functionException != null &&
                               string.Compare(functionException.MethodName, sourceMethodFullName, StringComparison.OrdinalIgnoreCase) == 0);
                    };

                    string sourceMethodShortName = string.Format("{0}.{1}", method.DeclaringType.Name, sourceMethod.Name);
                    functionLevelMessage = string.Format("Function '{0}' failed.", sourceMethodShortName);
                }
            }

            string errorHandlerFullName = string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name);

            ErrorHandlers.Add(errorHandlerFullName);

            // Create the TraceFilter instance
            TraceFilter traceFilter = null;

            if (attribute.FilterType != null)
            {
                if (methodFilter != null)
                {
                    TraceFilter innerTraceFilter = (TraceFilter)Activator.CreateInstance(attribute.FilterType);
                    traceFilter = new CompositeTraceFilter(innerTraceFilter, methodFilter, attribute.Message ?? functionLevelMessage);
                }
                else
                {
                    traceFilter = (TraceFilter)Activator.CreateInstance(attribute.FilterType);
                }
            }
            else if (!string.IsNullOrEmpty(attribute.Window))
            {
                TimeSpan window = TimeSpan.Parse(attribute.Window);
                traceFilter = new SlidingWindowTraceFilter(window, attribute.Threshold, methodFilter, attribute.Message);
            }
            else
            {
                traceFilter = TraceFilter.Create(methodFilter, attribute.Message ?? functionLevelMessage);
            }
            TraceMonitor traceMonitor = new TraceMonitor().Filter(traceFilter);

            // Apply any additional monitor options
            if (!string.IsNullOrEmpty(attribute.Throttle))
            {
                TimeSpan throttle = TimeSpan.Parse(attribute.Throttle);
                traceMonitor.Throttle(throttle);
            }

            // Subscribe the error handler function to the error stream
            traceMonitor.Subscribe(p =>
            {
                TriggeredFunctionData triggerData = new TriggeredFunctionData
                {
                    TriggerValue = p
                };
                Task <FunctionResult> task = executor.TryExecuteAsync(triggerData, CancellationToken.None);
                task.Wait();
            });

            return(traceMonitor);
        }
示例#25
0
        private async Task <string> ExecuteWithLogMessageAsync(IFunctionInstance instance, FunctionStartedMessage message,
                                                               IDictionary <string, ParameterLog> parameterLogCollector, CancellationToken cancellationToken)
        {
            string startedMessageId;

            // Create the console output writer
            IFunctionOutputDefinition outputDefinition = await _functionOutputLogger.CreateAsync(instance, cancellationToken);

            // Create a linked token source that will allow us to signal function cancellation
            // (e.g. Based on TimeoutAttribute, etc.)
            CancellationTokenSource functionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            using (IFunctionOutput outputLog = await outputDefinition.CreateOutputAsync(cancellationToken))
                using (ITaskSeriesTimer updateOutputLogTimer = StartOutputTimer(outputLog.UpdateCommand, _backgroundExceptionDispatcher))
                    using (functionCancellationTokenSource)
                    {
                        // We create a new composite trace writer that will also forward
                        // output to the function output log (in addition to console, user TraceWriter, etc.).
                        TraceWriter traceWriter = new CompositeTraceWriter(_trace, outputLog.Output);

                        FunctionBindingContext functionContext = new FunctionBindingContext(instance.Id, functionCancellationTokenSource.Token, traceWriter);

                        // Must bind before logging (bound invoke string is included in log message).
                        IReadOnlyDictionary <string, IValueProvider> parameters =
                            await instance.BindingSource.BindAsync(new ValueBindingContext(functionContext, cancellationToken));

                        ExceptionDispatchInfo exceptionInfo;
                        using (ValueProviderDisposable.Create(parameters))
                        {
                            startedMessageId = await LogFunctionStartedAsync(message, outputDefinition, parameters, cancellationToken);

                            try
                            {
                                await ExecuteWithOutputLogsAsync(instance, parameters, traceWriter, outputDefinition, parameterLogCollector, functionCancellationTokenSource);

                                exceptionInfo = null;
                            }
                            catch (OperationCanceledException exception)
                            {
                                exceptionInfo = ExceptionDispatchInfo.Capture(exception);
                            }
                            catch (Exception exception)
                            {
                                string errorMessage = string.Format("Exception while executing function: {0}", instance.FunctionDescriptor.ShortName);
                                FunctionInvocationException functionException = new FunctionInvocationException(errorMessage, instance.Id, instance.FunctionDescriptor.FullName, exception);
                                traceWriter.Error(errorMessage, functionException, TraceSource.Execution);
                                exceptionInfo = ExceptionDispatchInfo.Capture(functionException);
                            }
                        }

                        if (exceptionInfo == null && updateOutputLogTimer != null)
                        {
                            await updateOutputLogTimer.StopAsync(cancellationToken);
                        }

                        // after all execution is complete, flush the TraceWriter
                        traceWriter.Flush();

                        // We save the exception info rather than doing throw; above to ensure we always write console output,
                        // even if the function fails or was canceled.
                        await outputLog.SaveAndCloseAsync(cancellationToken);

                        if (exceptionInfo != null)
                        {
                            // release any held singleton lock immediately
                            SingletonLock singleton = null;
                            if (TryGetSingletonLock(parameters, out singleton) && singleton.IsHeld)
                            {
                                await singleton.ReleaseAsync(cancellationToken);
                            }

                            exceptionInfo.Throw();
                        }

                        return(startedMessageId);
                    }
        }
        private async Task <string> ExecuteWithLoggingAsync(IFunctionInstance instance, FunctionStartedMessage message,
                                                            FunctionInstanceLogEntry fastItem,
                                                            IDictionary <string, ParameterLog> parameterLogCollector, TraceLevel functionTraceLevel, CancellationToken cancellationToken)
        {
            IFunctionOutputDefinition outputDefinition     = null;
            IFunctionOutput           outputLog            = null;
            ITaskSeriesTimer          updateOutputLogTimer = null;
            TextWriter functionOutputTextWriter            = null;

            Func <Task> initializeOutputAsync = async() =>
            {
                outputDefinition = await _functionOutputLogger.CreateAsync(instance, cancellationToken);

                outputLog = outputDefinition.CreateOutput();
                functionOutputTextWriter = outputLog.Output;
                updateOutputLogTimer     = StartOutputTimer(outputLog.UpdateCommand, _exceptionHandler);
            };

            if (functionTraceLevel >= TraceLevel.Info)
            {
                await initializeOutputAsync();
            }

            try
            {
                // Create a linked token source that will allow us to signal function cancellation
                // (e.g. Based on TimeoutAttribute, etc.)
                CancellationTokenSource functionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                using (functionCancellationTokenSource)
                {
                    // We create a new composite trace writer that will also forward
                    // output to the function output log (in addition to console, user TraceWriter, etc.).
                    TraceWriter traceWriter = new CompositeTraceWriter(_trace, functionOutputTextWriter, functionTraceLevel);

                    // Must bind before logging (bound invoke string is included in log message).
                    FunctionBindingContext functionContext = new FunctionBindingContext(instance.Id, functionCancellationTokenSource.Token, traceWriter);
                    var valueBindingContext = new ValueBindingContext(functionContext, cancellationToken);
                    var parameters          = await instance.BindingSource.BindAsync(valueBindingContext);

                    Exception             invocationException = null;
                    ExceptionDispatchInfo exceptionInfo       = null;
                    string startedMessageId = null;
                    using (ValueProviderDisposable.Create(parameters))
                    {
                        if (functionTraceLevel >= TraceLevel.Info)
                        {
                            startedMessageId = await LogFunctionStartedAsync(message, outputDefinition, parameters, cancellationToken);
                        }

                        if (_fastLogger != null)
                        {
                            // Log started
                            fastItem.Arguments = message.Arguments;
                            await _fastLogger.AddAsync(fastItem);
                        }

                        try
                        {
                            await ExecuteWithLoggingAsync(instance, parameters, traceWriter, outputDefinition, parameterLogCollector, functionTraceLevel, functionCancellationTokenSource);
                        }
                        catch (Exception ex)
                        {
                            invocationException = ex;
                        }
                    }

                    if (invocationException != null)
                    {
                        if (outputDefinition == null)
                        {
                            // In error cases, even if logging is disabled for this function, we want to force
                            // log errors. So we must delay initialize logging here
                            await initializeOutputAsync();

                            startedMessageId = await LogFunctionStartedAsync(message, outputDefinition, parameters, cancellationToken);
                        }

                        // In the event of cancellation or timeout, we use the original exception without additional logging.
                        if (invocationException is OperationCanceledException || invocationException is FunctionTimeoutException)
                        {
                            exceptionInfo = ExceptionDispatchInfo.Capture(invocationException);
                        }
                        else
                        {
                            string errorMessage             = string.Format("Exception while executing function: {0}", instance.FunctionDescriptor.ShortName);
                            FunctionInvocationException fex = new FunctionInvocationException(errorMessage, instance.Id, instance.FunctionDescriptor.FullName, invocationException);
                            traceWriter.Error(errorMessage, fex, TraceSource.Execution);
                            exceptionInfo = ExceptionDispatchInfo.Capture(fex);
                        }
                    }

                    if (exceptionInfo == null && updateOutputLogTimer != null)
                    {
                        await updateOutputLogTimer.StopAsync(cancellationToken);
                    }

                    // after all execution is complete, flush the TraceWriter
                    traceWriter.Flush();

                    // We save the exception info above rather than throwing to ensure we always write
                    // console output even if the function fails or was canceled.
                    if (outputLog != null)
                    {
                        await outputLog.SaveAndCloseAsync(fastItem, cancellationToken);
                    }

                    if (exceptionInfo != null)
                    {
                        // release any held singleton lock immediately
                        SingletonLock singleton = null;
                        if (TryGetSingletonLock(parameters, out singleton) && singleton.IsHeld)
                        {
                            await singleton.ReleaseAsync(cancellationToken);
                        }

                        exceptionInfo.Throw();
                    }

                    return(startedMessageId);
                }
            }
            finally
            {
                if (outputLog != null)
                {
                    ((IDisposable)outputLog).Dispose();
                }
                if (updateOutputLogTimer != null)
                {
                    ((IDisposable)updateOutputLogTimer).Dispose();
                }
            }
        }