diff --git a/perf/Microsoft.Azure.WebJobs.Perf/app.config b/perf/Microsoft.Azure.WebJobs.Perf/app.config
index 30519e41..2e6d9844 100644
--- a/perf/Microsoft.Azure.WebJobs.Perf/app.config
+++ b/perf/Microsoft.Azure.WebJobs.Perf/app.config
@@ -1,23 +1,23 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
diff --git a/sample/SampleHost/App.config b/sample/SampleHost/App.config
index 66d2d478..63491736 100644
--- a/sample/SampleHost/App.config
+++ b/sample/SampleHost/App.config
@@ -1,25 +1,25 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/sample/SampleHost/Program.cs b/sample/SampleHost/Program.cs
index 7e990393..52cca4dd 100644
--- a/sample/SampleHost/Program.cs
+++ b/sample/SampleHost/Program.cs
@@ -2,7 +2,10 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
+using System.Diagnostics;
using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Extensions.Logging;
namespace SampleHost
{
@@ -19,6 +22,24 @@ namespace SampleHost
config.UseDevelopmentSettings();
}
+ config.Tracing.ConsoleLevel = TraceLevel.Off;
+
+ // Build up a LoggerFactory to log to App Insights, but only if this key exists.
+ string instrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
+ if (!string.IsNullOrEmpty(instrumentationKey))
+ {
+ // build up log filters
+ LogCategoryFilter logCategoryFilter = new LogCategoryFilter();
+ logCategoryFilter.DefaultLevel = LogLevel.Debug;
+ logCategoryFilter.CategoryLevels[LogCategories.Function] = LogLevel.Debug;
+ logCategoryFilter.CategoryLevels[LogCategories.Results] = LogLevel.Debug;
+ logCategoryFilter.CategoryLevels[LogCategories.Aggregator] = LogLevel.Debug;
+
+ config.LoggerFactory = new LoggerFactory()
+ .AddApplicationInsights(instrumentationKey, logCategoryFilter.Filter)
+ .AddConsole(logCategoryFilter.Filter);
+ }
+
config.CreateMetadataProvider().DebugDumpGraph(Console.Out);
var host = new JobHost(config);
diff --git a/sample/SampleHost/SampleHost.csproj b/sample/SampleHost/SampleHost.csproj
index 2f74be3c..26883425 100644
--- a/sample/SampleHost/SampleHost.csproj
+++ b/sample/SampleHost/SampleHost.csproj
@@ -34,18 +34,116 @@
4
+
+ ..\..\packages\Microsoft.Extensions.Configuration.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Console.1.1.1\lib\net451\Microsoft.Extensions.Logging.Console.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Primitives.1.1.0\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll
+ True
+
+
+ ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
+ True
+
..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
True
+
+ ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
+ True
+
+
+ ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+ True
+
+
+
+ ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
+
+ ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
+ True
+
+
+
+ ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll
+ True
+
+
+ ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
+ True
+
-
+
+ ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
+ True
+
diff --git a/sample/SampleHost/packages.config b/sample/SampleHost/packages.config
index 9d64bf36..7cb141c5 100644
--- a/sample/SampleHost/packages.config
+++ b/sample/SampleHost/packages.config
@@ -1,4 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.Azure.WebJobs.Host/ApplicationInsights.config b/src/Microsoft.Azure.WebJobs.Host/ApplicationInsights.config
new file mode 100644
index 00000000..75e12df9
--- /dev/null
+++ b/src/Microsoft.Azure.WebJobs.Host/ApplicationInsights.config
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/Microsoft.Azure.WebJobs.Host/Bindings/Logger/LoggerBinding.cs b/src/Microsoft.Azure.WebJobs.Host/Bindings/Logger/LoggerBinding.cs
new file mode 100644
index 00000000..f2dc9f66
--- /dev/null
+++ b/src/Microsoft.Azure.WebJobs.Host/Bindings/Logger/LoggerBinding.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Azure.WebJobs.Host.Protocols;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.Azure.WebJobs.Host.Bindings
+{
+ internal class LoggerBinding : IBinding
+ {
+ private readonly ParameterInfo _parameter;
+ private readonly ILoggerFactory _loggerFactory;
+
+ public LoggerBinding(ParameterInfo parameter, ILoggerFactory loggerFactory)
+ {
+ _parameter = parameter;
+ _loggerFactory = loggerFactory;
+ }
+
+ public bool FromAttribute => false;
+
+ public Task BindAsync(object value, ValueBindingContext context)
+ {
+ if (value == null || !_parameter.ParameterType.IsAssignableFrom(value.GetType()))
+ {
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to convert value to {0}.", _parameter.ParameterType));
+ }
+
+ IValueProvider valueProvider = new ValueBinder(value, _parameter.ParameterType);
+ return Task.FromResult(valueProvider);
+ }
+
+ public Task BindAsync(BindingContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ ILogger logger = _loggerFactory.CreateLogger(LogCategories.Function);
+ return BindAsync(logger, context.ValueContext);
+ }
+
+ public ParameterDescriptor ToParameterDescriptor()
+ {
+ return new ParameterDescriptor
+ {
+ Name = _parameter.Name
+ };
+ }
+
+ private sealed class ValueBinder : IValueBinder
+ {
+ private readonly object _tracer;
+ private readonly Type _type;
+
+ public ValueBinder(object tracer, Type type)
+ {
+ _tracer = tracer;
+ _type = type;
+ }
+
+ public Type Type => _type;
+
+ public Task
diff --git a/src/Microsoft.Azure.WebJobs.Host/packages.config b/src/Microsoft.Azure.WebJobs.Host/packages.config
index ed1b799f..04e1dfb2 100644
--- a/src/Microsoft.Azure.WebJobs.Host/packages.config
+++ b/src/Microsoft.Azure.WebJobs.Host/packages.config
@@ -1,17 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.Azure.WebJobs.NuGet/WebJobs.nuspec b/src/Microsoft.Azure.WebJobs.NuGet/WebJobs.nuspec
index 60dc2434..4117acdd 100644
--- a/src/Microsoft.Azure.WebJobs.NuGet/WebJobs.nuspec
+++ b/src/Microsoft.Azure.WebJobs.NuGet/WebJobs.nuspec
@@ -17,6 +17,8 @@
+
+
\ No newline at end of file
diff --git a/src/Microsoft.Azure.WebJobs.ServiceBus/app.config b/src/Microsoft.Azure.WebJobs.ServiceBus/app.config
index c997897f..377e21fe 100644
--- a/src/Microsoft.Azure.WebJobs.ServiceBus/app.config
+++ b/src/Microsoft.Azure.WebJobs.ServiceBus/app.config
@@ -1,30 +1,30 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -32,30 +32,30 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
+
diff --git a/test/Dashboard.UnitTests/Dashboard.UnitTests.csproj b/test/Dashboard.UnitTests/Dashboard.UnitTests.csproj
index 4f7de255..cc0f7312 100644
--- a/test/Dashboard.UnitTests/Dashboard.UnitTests.csproj
+++ b/test/Dashboard.UnitTests/Dashboard.UnitTests.csproj
@@ -119,7 +119,10 @@
-
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
True
diff --git a/test/Dashboard.UnitTests/app.config b/test/Dashboard.UnitTests/app.config
index 3c685cb8..3258445c 100644
--- a/test/Dashboard.UnitTests/app.config
+++ b/test/Dashboard.UnitTests/app.config
@@ -1,52 +1,56 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
-
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/App.config b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/App.config
index 6465544f..5ff20847 100644
--- a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/App.config
+++ b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/App.config
@@ -1,49 +1,53 @@
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
@@ -51,26 +55,26 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AsyncChainEndToEndTests.cs b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AsyncChainEndToEndTests.cs
index 62a95d2e..ef160b0c 100644
--- a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AsyncChainEndToEndTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AsyncChainEndToEndTests.cs
@@ -12,10 +12,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
+using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue;
@@ -51,6 +53,8 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
private readonly CloudQueue _testQueue;
private readonly TestFixture _fixture;
+ private readonly TestLoggerProvider _loggerProvider = new TestLoggerProvider();
+
public AsyncChainEndToEndTests(TestFixture fixture)
{
_fixture = fixture;
@@ -66,6 +70,11 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
_storageAccount = fixture.StorageAccount;
_timeoutJobDelay = TimeSpan.FromMinutes(5);
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ loggerFactory.AddProvider(_loggerProvider);
+ _hostConfig.LoggerFactory = loggerFactory;
+ _hostConfig.Aggregator.IsEnabled = false; // makes validation easier
+
CloudQueueClient queueClient = _storageAccount.CreateCloudQueueClient();
string queueName = _resolver.ResolveInString(TestQueueName);
_testQueue = queueClient.GetQueueReference(queueName);
@@ -86,6 +95,8 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
await AsyncChainEndToEndInternal();
+ Console.SetOut(hold);
+
string firstQueueName = _resolver.ResolveInString(Queue1Name);
string secondQueueName = _resolver.ResolveInString(Queue2Name);
string blobContainerName = _resolver.ResolveInString(ContainerName);
@@ -124,15 +135,23 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}.OrderBy(p => p).ToArray();
bool hasError = consoleOutputLines.Any(p => p.Contains("Function had errors"));
- if (!hasError)
+ Assert.False(hasError);
+
+ // Validate console output
+ for (int i = 0; i < expectedOutputLines.Length; i++)
{
- for (int i = 0; i < expectedOutputLines.Length; i++)
- {
- Assert.StartsWith(expectedOutputLines[i], consoleOutputLines[i]);
- }
+ Assert.StartsWith(expectedOutputLines[i], consoleOutputLines[i]);
}
- Console.SetOut(hold);
+ // Validate Logger output
+ var allLogs = _loggerProvider.CreatedLoggers.SelectMany(l => l.LogMessages.SelectMany(m => m.FormattedMessage.Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.None))).OrderBy(p => p).ToArray();
+ // Logger doesn't log the 'Executing' messages
+ var loggerExpected = expectedOutputLines.Where(l => !l.StartsWith("Executing '")).ToArray();
+
+ for (int i = 0; i < loggerExpected.Length; i++)
+ {
+ Assert.StartsWith(loggerExpected[i], allLogs[i]);
+ }
}
}
@@ -188,23 +207,131 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
await host.StopAsync();
bool hasError = string.Join(Environment.NewLine, trace.Traces.Where(p => p.Message.Contains("Error"))).Any();
- if (!hasError)
- {
- Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("User TraceWriter log")));
- Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("User TextWriter log (TestParam)")));
- Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("Another User TextWriter log")));
- ValidateTraceProperties(trace);
+ Assert.False(hasError);
- string[] consoleOutputLines = consoleOutput.ToString().Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
- Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("User TraceWriter log")));
- Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("User TextWriter log (TestParam)")));
- Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("Another User TextWriter log")));
- }
+ Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("User TraceWriter log")));
+ Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("User TextWriter log (TestParam)")));
+ Assert.NotNull(trace.Traces.SingleOrDefault(p => p.Message.Contains("Another User TextWriter log")));
+ ValidateTraceProperties(trace);
+
+ string[] consoleOutputLines = consoleOutput.ToString().Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
+ Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("User TraceWriter log")));
+ Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("User TextWriter log (TestParam)")));
+ Assert.NotNull(consoleOutputLines.SingleOrDefault(p => p.Contains("Another User TextWriter log")));
+
+ // Validate Logger
+ var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Function).Single();
+ Assert.Equal(3, logger.LogMessages.Count);
+ Assert.NotNull(logger.LogMessages.SingleOrDefault(p => p.FormattedMessage.Contains("User TraceWriter log")));
+ Assert.NotNull(logger.LogMessages.SingleOrDefault(p => p.FormattedMessage.Contains("User TextWriter log (TestParam)")));
+ Assert.NotNull(logger.LogMessages.SingleOrDefault(p => p.FormattedMessage.Contains("Another User TextWriter log")));
}
Console.SetOut(hold);
}
+ [Fact]
+ public async Task AggregatorAndEventCollector()
+ {
+ using (_functionCompletedEvent = new ManualResetEvent(initialState: false))
+ {
+ _hostConfig.Tracing.ConsoleLevel = TraceLevel.Off;
+
+ // enable the aggregator
+ _hostConfig.Aggregator.IsEnabled = true;
+ _hostConfig.Aggregator.BatchSize = 1;
+
+ // add a FunctionEventCollector
+ var eventCollector = new TestFunctionEventCollector();
+ _hostConfig.AddService>(eventCollector);
+
+ JobHost host = new JobHost(_hostConfig);
+
+ await host.StartAsync();
+ await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue"));
+
+ _functionCompletedEvent.WaitOne();
+
+ // ensure all logs have had a chance to flush
+ await Task.Delay(3000);
+
+ await host.StopAsync();
+
+ // Make sure the aggregator was logged to
+ var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).Single();
+ Assert.Equal(4, logger.LogMessages.Count);
+
+ // Make sure the eventCollector was logged to
+ // The aggregator ignores 'start' evetns, so this will be double
+ Assert.Equal(8, eventCollector.LogCount);
+ }
+ }
+
+ [Fact]
+ public async Task AggregatorOnly()
+ {
+ using (_functionCompletedEvent = new ManualResetEvent(initialState: false))
+ {
+ _hostConfig.Tracing.ConsoleLevel = TraceLevel.Off;
+
+ // enable the aggregator
+ _hostConfig.Aggregator.IsEnabled = true;
+ _hostConfig.Aggregator.BatchSize = 1;
+
+ JobHost host = new JobHost(_hostConfig);
+
+ await host.StartAsync();
+ await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue"));
+
+ _functionCompletedEvent.WaitOne();
+
+ // ensure all logs have had a chance to flush
+ await Task.Delay(3000);
+
+ await host.StopAsync();
+
+ // Make sure the aggregator was logged to
+ var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).Single();
+ Assert.Equal(4, logger.LogMessages.Count);
+ }
+ }
+
+ [Fact]
+ public async Task EventCollectorOnly()
+ {
+ using (_functionCompletedEvent = new ManualResetEvent(initialState: false))
+ {
+ _hostConfig.Tracing.ConsoleLevel = TraceLevel.Off;
+
+ // disable the aggregator
+ _hostConfig.Aggregator.IsEnabled = false;
+
+ // add a FunctionEventCollector
+ var eventCollector = new TestFunctionEventCollector();
+ _hostConfig.AddService>(eventCollector);
+
+ JobHost host = new JobHost(_hostConfig);
+
+ await host.StartAsync();
+ await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue"));
+
+ _functionCompletedEvent.WaitOne();
+
+ // ensure all logs have had a chance to flush
+ await Task.Delay(3000);
+
+ await host.StopAsync();
+
+ // Make sure the aggregator was logged to
+ var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).SingleOrDefault();
+ Assert.Null(logger);
+
+ // Make sure the eventCollector was logged to
+ // The aggregator ignores 'start' evetns, so this will be double
+ Assert.Equal(8, eventCollector.LogCount);
+ }
+ }
+
private void ValidateTraceProperties(TestTraceWriter trace)
{
foreach (var traceEvent in trace.Traces)
@@ -251,17 +378,28 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
}
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
+ // 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.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);
}
[Fact]
@@ -349,12 +487,24 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
host.Stop();
}
+ string expectedExceptionMessage = $"Timeout value of 00:00:01 exceeded by function 'AsyncChainEndToEndTests.{functionName}'";
+ string expectedResultMessage = $"Executed 'AsyncChainEndToEndTests.{functionName}' (Failed, Id=";
+
+ // Validate TraceWriter
// We expect 3 error messages total
TraceEvent[] traceErrors = trace.Traces.Where(p => p.Level == TraceLevel.Error).ToArray();
Assert.Equal(3, traceErrors.Length);
- Assert.True(traceErrors[0].Message.StartsWith(string.Format("Timeout value of 00:00:01 exceeded by function 'AsyncChainEndToEndTests.{0}'", functionName)));
- Assert.True(traceErrors[1].Message.StartsWith(string.Format("Executed 'AsyncChainEndToEndTests.{0}' (Failed, Id=", functionName)));
- Assert.True(traceErrors[2].Message.Trim().StartsWith("Function had errors. See Azure WebJobs SDK dashboard for details."));
+ Assert.StartsWith(expectedExceptionMessage, traceErrors[0].Message);
+ Assert.StartsWith(expectedResultMessage, traceErrors[1].Message);
+ Assert.StartsWith("Function had errors. See Azure WebJobs SDK dashboard for details.", traceErrors[2].Message.Trim());
+
+ // Validate Logger
+ // One error is logged by the Executor and one as a Result.
+ var resultLogger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Results).Single();
+ var executorLogger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Executor).Single();
+ Assert.NotNull(resultLogger.LogMessages.Single().Exception);
+ Assert.StartsWith(expectedResultMessage, resultLogger.LogMessages.Single().FormattedMessage);
+ Assert.StartsWith(expectedExceptionMessage, executorLogger.LogMessages.Single().FormattedMessage);
}
[Fact]
@@ -368,8 +518,13 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
MethodInfo methodInfo = GetType().GetMethod("TimeoutJob");
await host.CallAsync(methodInfo);
+ // Validate TraceWriter
TraceEvent[] traceErrors = trace.Traces.Where(p => p.Level == TraceLevel.Error).ToArray();
Assert.Equal(0, traceErrors.Length);
+
+ // Validate Logger
+ LogMessage[] logErrors = _loggerProvider.GetAllLogMessages().Where(l => l.Level == Extensions.Logging.LogLevel.Error).ToArray();
+ Assert.Equal(0, logErrors.Length);
}
[Fact]
@@ -781,5 +936,21 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
return Task.FromResult(0);
}
}
+
+ private class TestFunctionEventCollector : IAsyncCollector
+ {
+ public int LogCount = 0;
+
+ public Task AddAsync(FunctionInstanceLogEntry item, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ LogCount++;
+ return Task.CompletedTask;
+ }
+
+ public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return Task.CompletedTask;
+ }
+ }
}
}
diff --git a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AzureStorageEndToEndTests.cs b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AzureStorageEndToEndTests.cs
index 048b7914..cea4f96e 100644
--- a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AzureStorageEndToEndTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/AzureStorageEndToEndTests.cs
@@ -9,6 +9,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue;
@@ -335,6 +336,11 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
var tracer = new TestTraceWriter(TraceLevel.Verbose);
hostConfig.Tracing.Tracers.Add(tracer);
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ TestLoggerProvider loggerProvider = new TestLoggerProvider();
+ loggerFactory.AddProvider(loggerProvider);
+ hostConfig.LoggerFactory = loggerFactory;
+
// The jobs host is started
JobHost host = new JobHost(hostConfig);
host.Start();
@@ -385,6 +391,10 @@ namespace Microsoft.Azure.WebJobs.Host.EndToEndTests
// make sure the exception is being properly logged
var errors = tracer.Traces.Where(t => t.Level == TraceLevel.Error);
Assert.True(errors.All(t => t.Exception.InnerException.InnerException is FormatException));
+
+ // Validate Logger
+ var loggerErrors = loggerProvider.GetAllLogMessages().Where(l => l.Level == Extensions.Logging.LogLevel.Error);
+ Assert.True(loggerErrors.All(t => t.Exception.InnerException.InnerException is FormatException));
}
private void UploadTestObject()
diff --git a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/WebJobs.Host.EndToEndTests.csproj b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/WebJobs.Host.EndToEndTests.csproj
index 77da6f8f..0f49bcf4 100644
--- a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/WebJobs.Host.EndToEndTests.csproj
+++ b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/WebJobs.Host.EndToEndTests.csproj
@@ -14,6 +14,9 @@
512
+ ..\test.ruleset
+ true
+ false
@@ -54,10 +57,26 @@
..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
+
+ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
+ True
+
..\..\packages\WindowsAzure.ServiceBus.3.4.5\lib\net45-full\Microsoft.ServiceBus.dll
True
+
+ ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
+ True
+
..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll
True
@@ -71,9 +90,72 @@
True
+
+ ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
+ True
+
+
+ ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+ True
+
+
+
+ ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
+
+ ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
+ True
+
+
+
+ ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
+ True
+
..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
@@ -81,6 +163,10 @@
+
+ ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
+ True
+
..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
True
diff --git a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/packages.config b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/packages.config
index 1cacafd5..f9eb6e68 100644
--- a/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/packages.config
+++ b/test/Microsoft.Azure.WebJobs.Host.EndToEndTests/packages.config
@@ -4,13 +4,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/ILoggerTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/ILoggerTests.cs
new file mode 100644
index 00000000..5ef7d088
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/ILoggerTests.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Azure.WebJobs.Host.Executors;
+using Microsoft.Azure.WebJobs.Host.FunctionalTests.TestDoubles;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
+{
+ public class ILoggerTests
+ {
+ TestTraceWriter _trace = new TestTraceWriter(TraceLevel.Info);
+ TestLoggerProvider _loggerProvider = new TestLoggerProvider();
+
+ [Fact]
+ public void ILogger_Succeeds()
+ {
+ using (JobHost host = new JobHost(CreateConfig()))
+ {
+ var method = typeof(ILoggerFunctions).GetMethod(nameof(ILoggerFunctions.ILogger));
+ host.Call(method);
+ }
+
+ // Five loggers are the startup, singleton, executor, results, and function loggers
+ Assert.Equal(5, _loggerProvider.CreatedLoggers.Count);
+
+ var functionLogger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Function).Single();
+ var resultsLogger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Results).Single();
+
+ Assert.Equal(2, functionLogger.LogMessages.Count);
+ var infoMessage = functionLogger.LogMessages[0];
+ var errorMessage = functionLogger.LogMessages[1];
+ // These get the {OriginalFormat} property as well as the 3 from TraceWriter
+ Assert.Equal(3, infoMessage.State.Count());
+ Assert.Equal(3, errorMessage.State.Count());
+
+ Assert.Equal(1, resultsLogger.LogMessages.Count);
+ //TODO: beef these verifications up
+ }
+
+ [Fact]
+ public void TraceWriter_ForwardsTo_ILogger()
+ {
+ using (JobHost host = new JobHost(CreateConfig()))
+ {
+ var method = typeof(ILoggerFunctions).GetMethod(nameof(ILoggerFunctions.TraceWriterWithILoggerFactory));
+ host.Call(method);
+ }
+
+ Assert.Equal(5, _trace.Traces.Count);
+ // The third and fourth traces are from our function
+ var infoLog = _trace.Traces[2];
+ var errorLog = _trace.Traces[3];
+
+ Assert.Equal("This should go to the ILogger", infoLog.Message);
+ Assert.Null(infoLog.Exception);
+ Assert.Equal(3, infoLog.Properties.Count);
+
+ Assert.Equal("This should go to the ILogger with an Exception!", errorLog.Message);
+ Assert.IsType(errorLog.Exception);
+ Assert.Equal(3, errorLog.Properties.Count);
+
+ // Five loggers are the startup, singleton, executor, results, and function loggers
+ Assert.Equal(5, _loggerProvider.CreatedLoggers.Count);
+ var functionLogger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Function).Single();
+ Assert.Equal(2, functionLogger.LogMessages.Count);
+ var infoMessage = functionLogger.LogMessages[0];
+ var errorMessage = functionLogger.LogMessages[1];
+ // These get the {OriginalFormat} property as well as the 3 from TraceWriter
+ Assert.Equal(4, infoMessage.State.Count());
+ Assert.Equal(4, errorMessage.State.Count());
+ //TODO: beef these verifications up
+ }
+
+ [Fact]
+ public void Aggregator_Runs_WhenEnabled_AndFlushes_OnStop()
+ {
+ int addCalls = 0;
+ int flushCalls = 0;
+
+ var config = CreateConfig();
+
+ var mockAggregator = new Mock>(MockBehavior.Strict);
+ mockAggregator
+ .Setup(a => a.AddAsync(It.IsAny(), It.IsAny()))
+ .Callback((l, t) => addCalls++)
+ .Returns(Task.CompletedTask);
+ mockAggregator
+ .Setup(a => a.FlushAsync(It.IsAny()))
+ .Callback(t => flushCalls++)
+ .Returns(Task.CompletedTask);
+
+ var mockFactory = new Mock(MockBehavior.Strict);
+ mockFactory
+ .Setup(f => f.Create(5, TimeSpan.FromSeconds(1), It.IsAny()))
+ .Returns(mockAggregator.Object);
+
+ config.AddService(mockFactory.Object);
+
+ config.Aggregator.IsEnabled = true;
+ config.Aggregator.BatchSize = 5;
+ config.Aggregator.FlushTimeout = TimeSpan.FromSeconds(1);
+
+ using (JobHost host = new JobHost(config))
+ {
+ host.Start();
+
+ var method = typeof(ILoggerFunctions).GetMethod(nameof(ILoggerFunctions.TraceWriterWithILoggerFactory));
+
+ for (int i = 0; i < 5; i++)
+ {
+ host.Call(method);
+ }
+
+ host.Stop();
+ }
+
+ // Add will be called 5 times. The default aggregator will ingore the
+ // 'Function started' calls.
+ Assert.Equal(10, addCalls);
+
+ // Flush is called on host stop
+ Assert.Equal(1, flushCalls);
+ }
+
+ [Fact]
+ public void NoILoggerFactory_NoAggregator()
+ {
+ var config = CreateConfig(addFactory: false);
+
+ // Ensure the aggregator is never configured by registering an
+ // AggregatorFactory that with a strict, unconfigured mock.
+ var mockFactory = new Mock(MockBehavior.Strict);
+ config.AddService(mockFactory.Object);
+
+ using (JobHost host = new JobHost(config))
+ {
+ var method = typeof(ILoggerFunctions).GetMethod(nameof(ILoggerFunctions.TraceWriterWithILoggerFactory));
+ host.Call(method);
+ }
+ }
+
+ [Fact]
+ public void DisabledAggregator_NoAggregator()
+ {
+ // Add the loggerfactory but disable the aggregator
+ var config = CreateConfig();
+ config.Aggregator.IsEnabled = false;
+
+ // Ensure the aggregator is never configured by registering an
+ // AggregatorFactory that with a strict, unconfigured mock.
+ var mockFactory = new Mock(MockBehavior.Strict);
+ config.AddService(mockFactory.Object);
+
+ using (JobHost host = new JobHost(config))
+ {
+ // also start and stop the host to ensure nothing throws due to the
+ // null aggregator
+ host.Start();
+
+ var method = typeof(ILoggerFunctions).GetMethod(nameof(ILoggerFunctions.TraceWriterWithILoggerFactory));
+ host.Call(method);
+
+ host.Stop();
+ }
+ }
+
+ private JobHostConfiguration CreateConfig(bool addFactory = true)
+ {
+ IStorageAccountProvider accountProvider = new FakeStorageAccountProvider()
+ {
+ StorageAccount = new FakeStorageAccount()
+ };
+
+ ILoggerFactory factory = new LoggerFactory();
+ factory.AddProvider(_loggerProvider);
+
+ var config = new JobHostConfiguration();
+ config.AddService(accountProvider);
+ config.TypeLocator = new FakeTypeLocator(new[] { typeof(ILoggerFunctions) });
+ config.Tracing.Tracers.Add(_trace);
+ config.AddService(factory);
+ config.Aggregator.IsEnabled = false; // disable aggregator
+
+ return config;
+ }
+
+ private class ILoggerFunctions
+ {
+ [NoAutomaticTrigger]
+ public void ILogger(ILogger log)
+ {
+ log.LogInformation("Log {some} keys and {values}", "1", "2");
+
+ var ex = new InvalidOperationException("Failure.");
+ log.LogError(0, ex, "Log {other} keys {and} values", "3", "4");
+ }
+
+ [NoAutomaticTrigger]
+ public void TraceWriterWithILoggerFactory(TraceWriter log)
+ {
+ log.Info("This should go to the ILogger");
+
+ var ex = new InvalidOperationException("Failure.");
+ log.Error("This should go to the ILogger with an Exception!", ex);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Queues/QueueProcessorTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Queues/QueueProcessorTests.cs
index 7b71bf6a..d9bdc56b 100644
--- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Queues/QueueProcessorTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Queues/QueueProcessorTests.cs
@@ -9,6 +9,7 @@ using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.Storage.Queue;
using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Queue;
using Moq;
using Xunit;
@@ -31,7 +32,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
_poisonQueue = fixture.PoisonQueue;
_queuesConfig = new JobHostQueuesConfiguration();
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, _queuesConfig);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, null, _queuesConfig);
_processor = new QueueProcessor(context);
}
@@ -46,7 +47,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
VisibilityTimeout = TimeSpan.FromSeconds(30),
MaxPollingInterval = TimeSpan.FromSeconds(15)
};
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, config);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, null, config);
QueueProcessor localProcessor = new QueueProcessor(context);
Assert.Equal(config.BatchSize, localProcessor.BatchSize);
@@ -94,7 +95,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
[Fact]
public async Task CompleteProcessingMessageAsync_MaxDequeueCountExceeded_MovesMessageToPoisonQueue()
{
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, _queuesConfig, _poisonQueue);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, null, _queuesConfig, _poisonQueue);
QueueProcessor localProcessor = new QueueProcessor(context);
bool poisonMessageHandlerCalled = false;
@@ -134,7 +135,7 @@ namespace Microsoft.Azure.WebJobs.Host.FunctionalTests
// configure a non-zero visibility timeout
VisibilityTimeout = TimeSpan.FromMinutes(5)
};
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, queuesConfig, _poisonQueue);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(_queue, _trace, null, queuesConfig, _poisonQueue);
QueueProcessor localProcessor = new QueueProcessor(context);
string messageContent = Guid.NewGuid().ToString();
diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/WebJobs.Host.FunctionalTests.csproj b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/WebJobs.Host.FunctionalTests.csproj
index 6a6f5a94..e76c4614 100644
--- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/WebJobs.Host.FunctionalTests.csproj
+++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/WebJobs.Host.FunctionalTests.csproj
@@ -59,6 +59,7 @@
+
@@ -145,6 +146,22 @@
..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
+
+ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
+ True
+
..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll
True
@@ -162,14 +179,81 @@
True
+
+ ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
+ True
+
+
+ ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+ True
+
+
+
+ ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
+
+ ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
+ True
+
+
+
+ ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
+ True
+
..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
+
+ ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
+ True
+
..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
True
diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/app.config b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/app.config
index badb94a2..b6767ed9 100644
--- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/app.config
+++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/app.config
@@ -1,25 +1,29 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
-
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/packages.config b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/packages.config
index 3de92605..3507e653 100644
--- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/packages.config
+++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/packages.config
@@ -5,14 +5,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/JobHostFactory.cs b/test/Microsoft.Azure.WebJobs.Host.TestCommon/JobHostFactory.cs
index 02586456..2483f8ec 100644
--- a/test/Microsoft.Azure.WebJobs.Host.TestCommon/JobHostFactory.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/JobHostFactory.cs
@@ -1,15 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
-using System.Diagnostics;
-using System.Threading;
-using Microsoft.Azure.WebJobs.Host.Blobs;
using Microsoft.Azure.WebJobs.Host.Executors;
-using Microsoft.Azure.WebJobs.Host.Indexers;
-using Microsoft.Azure.WebJobs.Host.Listeners;
-using Microsoft.Azure.WebJobs.Host.Loggers;
-using Microsoft.Azure.WebJobs.Host.Queues;
-using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.WindowsAzure.Storage;
namespace Microsoft.Azure.WebJobs.Host.TestCommon
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/LogMessage.cs b/test/Microsoft.Azure.WebJobs.Host.TestCommon/LogMessage.cs
new file mode 100644
index 00000000..b4db6742
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/LogMessage.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.Azure.WebJobs.Host.TestCommon
+{
+ public class LogMessage
+ {
+ public LogLevel Level { get; set; }
+ public EventId EventId { get; set; }
+ public IEnumerable> State { get; set; }
+ public Exception Exception { get; set; }
+ public string FormattedMessage { get; set; }
+
+ public string Category { get; set; }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/NullFunctionOutputLoggerProvider.cs b/test/Microsoft.Azure.WebJobs.Host.TestCommon/NullFunctionOutputLoggerProvider.cs
index 342a4aed..7af33956 100644
--- a/test/Microsoft.Azure.WebJobs.Host.TestCommon/NullFunctionOutputLoggerProvider.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/NullFunctionOutputLoggerProvider.cs
@@ -10,6 +10,7 @@ using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Timers;
+using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.WebJobs.Host.TestCommon
{
@@ -47,7 +48,7 @@ namespace Microsoft.Azure.WebJobs.Host.TestCommon
return new NullFunctionOutput();
}
- public IRecurrentCommand CreateParameterLogUpdateCommand(IReadOnlyDictionary watches, TraceWriter trace)
+ public IRecurrentCommand CreateParameterLogUpdateCommand(IReadOnlyDictionary watches, TraceWriter trace, ILogger logger)
{
return null;
}
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLogger.cs b/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLogger.cs
new file mode 100644
index 00000000..245e3bb1
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLogger.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.Azure.WebJobs.Host.TestCommon
+{
+ public class TestLogger : ILogger
+ {
+ private readonly Func _filter;
+
+ public string Category { get; private set; }
+
+ public IList LogMessages = new List();
+
+ public TestLogger(string category, Func filter = null)
+ {
+ Category = category;
+ _filter = filter;
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return null;
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return _filter?.Invoke(Category, logLevel) ?? true;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ {
+ return;
+ }
+
+ LogMessages.Add(new LogMessage
+ {
+ Level = logLevel,
+ EventId = eventId,
+ State = state as IEnumerable>,
+ Exception = exception,
+ FormattedMessage = formatter(state, exception),
+ Category = Category
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLoggerProvider.cs b/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLoggerProvider.cs
new file mode 100644
index 00000000..4a677977
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/TestLoggerProvider.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.Azure.WebJobs.Host.TestCommon
+{
+ public class TestLoggerProvider : ILoggerProvider
+ {
+ private readonly Func _filter;
+
+ public IList CreatedLoggers = new List();
+
+ public TestLoggerProvider(Func filter = null)
+ {
+ _filter = filter ?? new LogCategoryFilter().Filter;
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ var logger = new TestLogger(categoryName, _filter);
+ CreatedLoggers.Add(logger);
+ return logger;
+ }
+
+ public IEnumerable GetAllLogMessages()
+ {
+ return CreatedLoggers.SelectMany(l => l.LogMessages);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/WebJobs.Host.TestCommon.csproj b/test/Microsoft.Azure.WebJobs.Host.TestCommon/WebJobs.Host.TestCommon.csproj
index 0e60866d..56461a08 100644
--- a/test/Microsoft.Azure.WebJobs.Host.TestCommon/WebJobs.Host.TestCommon.csproj
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/WebJobs.Host.TestCommon.csproj
@@ -66,6 +66,22 @@
..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
+
+ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
+ True
+
..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll
True
@@ -79,7 +95,70 @@
True
+
+ ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
+ True
+
+
+ ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+ True
+
+
+
+ ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
+
+ ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
+ True
+
+
+
+ ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
+ True
+
..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
@@ -89,6 +168,10 @@
+
+ ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
+ True
+
..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
True
@@ -116,6 +199,7 @@
+
@@ -123,6 +207,8 @@
+
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/app.config b/test/Microsoft.Azure.WebJobs.Host.TestCommon/app.config
index 1ad795fa..7609ce58 100644
--- a/test/Microsoft.Azure.WebJobs.Host.TestCommon/app.config
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/app.config
@@ -1,23 +1,27 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
-
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.TestCommon/packages.config b/test/Microsoft.Azure.WebJobs.Host.TestCommon/packages.config
index eb90baf1..28e7c1cf 100644
--- a/test/Microsoft.Azure.WebJobs.Host.TestCommon/packages.config
+++ b/test/Microsoft.Azure.WebJobs.Host.TestCommon/packages.config
@@ -4,13 +4,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionExecutorTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionExecutorTests.cs
index f5d300de..69f8ce28 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionExecutorTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionExecutorTests.cs
@@ -3,12 +3,14 @@
using System;
using System.Diagnostics;
+using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -44,7 +46,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
TimeoutAttribute attribute = method.GetCustomAttribute();
- System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter);
+ System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter, null);
Assert.True(timer.Enabled);
Assert.Equal(attribute.Timeout.TotalMilliseconds, timer.Interval);
@@ -64,7 +66,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
TimeoutAttribute attribute = typeof(Functions).GetCustomAttribute();
- System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter);
+ System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter, null);
Assert.True(timer.Enabled);
Assert.Equal(attribute.Timeout.TotalMilliseconds, timer.Interval);
@@ -76,7 +78,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
public void StartFunctionTimeout_NoTimeout_ReturnsNull()
{
TimeoutAttribute timeoutAttribute = null;
- System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(null, timeoutAttribute, _cancellationTokenSource, _traceWriter);
+ System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(null, timeoutAttribute, _cancellationTokenSource, _traceWriter, null);
Assert.Null(timer);
}
@@ -90,7 +92,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
TimeoutAttribute attribute = typeof(Functions).GetCustomAttribute();
attribute.ThrowOnTimeout = false;
- System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter);
+ System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter, null);
Assert.Null(timer);
@@ -109,7 +111,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
TimeoutAttribute attribute = typeof(Functions).GetCustomAttribute();
- System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter);
+ System.Timers.Timer timer = FunctionExecutor.StartFunctionTimeout(_mockFunctionInstance.Object, attribute, _cancellationTokenSource, _traceWriter, null);
Assert.True(timer.Enabled);
Assert.Equal(attribute.Timeout.TotalMilliseconds, timer.Interval);
@@ -317,16 +319,26 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors
TimeoutAttribute attribute = method.GetCustomAttribute();
Guid instanceId = Guid.Parse("B2D1DD72-80E2-412B-A22E-3B4558F378B4");
bool timeoutWhileDebugging = false;
- FunctionExecutor.OnFunctionTimeout(timer, method, instanceId, attribute.Timeout, timeoutWhileDebugging, _traceWriter, _cancellationTokenSource, () => isDebugging);
+
+ TestLogger logger = new TestLogger("Tests.FunctionExecutor");
+
+ FunctionExecutor.OnFunctionTimeout(timer, method, instanceId, attribute.Timeout, timeoutWhileDebugging, _traceWriter, logger, _cancellationTokenSource, () => isDebugging);
Assert.False(timer.Enabled);
Assert.NotEqual(isDebugging, _cancellationTokenSource.IsCancellationRequested);
+ string message = string.Format("Timeout value of 00:01:00 exceeded by function 'Functions.MethodLevel' (Id: 'b2d1dd72-80e2-412b-a22e-3b4558f378b4'). {0}", expectedMessage);
+
+ // verify TraceWriter
TraceEvent trace = _traceWriter.Traces[0];
Assert.Equal(TraceLevel.Error, trace.Level);
Assert.Equal(TraceSource.Execution, trace.Source);
- string message = string.Format("Timeout value of 00:01:00 exceeded by function 'Functions.MethodLevel' (Id: 'b2d1dd72-80e2-412b-a22e-3b4558f378b4'). {0}", expectedMessage);
Assert.Equal(message, trace.Message);
+
+ // verify ILogger
+ LogMessage log = logger.LogMessages.Single();
+ Assert.Equal(LogLevel.Error, log.Level);
+ Assert.Equal(message, log.FormattedMessage);
}
public static void GlobalLevel(CancellationToken cancellationToken)
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/FunctionIndexerFactory.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/FunctionIndexerFactory.cs
index 4fafa899..97f0a4a1 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/FunctionIndexerFactory.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/FunctionIndexerFactory.cs
@@ -5,16 +5,14 @@ using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.Azure.WebJobs.Host.Bindings;
-using Microsoft.Azure.WebJobs.Host.Blobs;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
-using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Loggers;
-using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
using Microsoft.Azure.WebJobs.Host.Triggers;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Moq;
@@ -22,7 +20,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
{
internal static class FunctionIndexerFactory
{
- public static FunctionIndexer Create(CloudStorageAccount account = null, INameResolver nameResolver = null, IExtensionRegistry extensionRegistry = null, TraceWriter traceWriter = null)
+ public static FunctionIndexer Create(CloudStorageAccount account = null, INameResolver nameResolver = null,
+ IExtensionRegistry extensionRegistry = null, TraceWriter traceWriter = null, ILoggerFactory loggerFactory = null)
{
IStorageAccountProvider storageAccountProvider = GetStorageAccountProvider(account);
@@ -42,7 +41,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
IFunctionExecutor executor = new FunctionExecutor(new NullFunctionInstanceLogger(), outputLogger, exceptionHandler, logger);
- return new FunctionIndexer(triggerBindingProvider, bindingProvider, DefaultJobActivator.Instance, executor, extensionRegistry, singletonManager, logger);
+ return new FunctionIndexer(triggerBindingProvider, bindingProvider, DefaultJobActivator.Instance, executor,
+ extensionRegistry, singletonManager, logger, loggerFactory);
}
private static IStorageAccountProvider GetStorageAccountProvider(CloudStorageAccount account)
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerIntegrationErrorTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerIntegrationErrorTests.cs
index fcc745e5..5e9bef70 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerIntegrationErrorTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerIntegrationErrorTests.cs
@@ -11,9 +11,9 @@ using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
-using Microsoft.Azure.WebJobs.Host.Storage.Blob;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Triggers;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -35,6 +35,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
Mock executorMock = new Mock(MockBehavior.Strict);
IFunctionIndexCollector stubIndex = new Mock().Object;
+
FunctionIndexer indexer = new FunctionIndexer(
new Mock(MockBehavior.Strict).Object,
new Mock(MockBehavior.Strict).Object,
@@ -42,7 +43,8 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
executorMock.Object,
extensionsMock.Object,
new SingletonManager(),
- new TestTraceWriter(TraceLevel.Verbose));
+ new TestTraceWriter(TraceLevel.Verbose),
+ null);
Assert.Throws(() => indexer.IndexMethodAsync(method, stubIndex, CancellationToken.None).GetAwaiter().GetResult());
}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerTests.cs
index 757c208c..48c310d2 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Indexers/FunctionIndexerTests.cs
@@ -11,9 +11,11 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Indexers;
+using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Triggers;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -151,16 +153,28 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
public async Task IndexMethod_IfMethodReturnsAsyncVoid_Throws()
{
var traceWriter = new TestTraceWriter(TraceLevel.Verbose);
+ var loggerFactory = new LoggerFactory();
+ var loggerProvider = new TestLoggerProvider();
+ loggerFactory.AddProvider(loggerProvider);
// Arrange
IFunctionIndexCollector index = CreateStubFunctionIndex();
- FunctionIndexer product = CreateProductUnderTest(traceWriter: traceWriter);
+ FunctionIndexer product = CreateProductUnderTest(traceWriter: traceWriter, loggerFactory: loggerFactory);
// Act & Assert
await product.IndexMethodAsync(typeof(FunctionIndexerTests).GetMethod("ReturnAsyncVoid"), index, CancellationToken.None);
- var warning = traceWriter.Traces.First(p => p.Level == TraceLevel.Warning);
- Assert.Equal("Function 'ReturnAsyncVoid' is async but does not return a Task. Your function may not run correctly.", warning.Message);
+ string expectedMessage = "Function 'ReturnAsyncVoid' is async but does not return a Task. Your function may not run correctly.";
+
+ // Validate TraceWriter
+ var traceWarning = traceWriter.Traces.First(p => p.Level == TraceLevel.Warning);
+ Assert.Equal(expectedMessage, traceWarning.Message);
+
+ // Validate Logger
+ var logger = loggerProvider.CreatedLoggers.Single(l => l.Category == LogCategories.Startup);
+ var loggerWarning = logger.LogMessages.Single();
+ Assert.Equal(LogLevel.Warning, loggerWarning.Level);
+ Assert.Equal(expectedMessage, loggerWarning.FormattedMessage);
}
[Fact]
@@ -317,9 +331,9 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Indexers
return new Mock(MockBehavior.Strict).Object;
}
- private static FunctionIndexer CreateProductUnderTest(TraceWriter traceWriter = null)
+ private static FunctionIndexer CreateProductUnderTest(TraceWriter traceWriter = null, ILoggerFactory loggerFactory = null)
{
- return FunctionIndexerFactory.Create(traceWriter: traceWriter);
+ return FunctionIndexerFactory.Create(traceWriter: traceWriter, loggerFactory: loggerFactory);
}
private static IFunctionIndexCollector CreateStubFunctionIndex()
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/FunctionListenerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/FunctionListenerTests.cs
index 34891779..48cb1a68 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/FunctionListenerTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/FunctionListenerTests.cs
@@ -2,17 +2,17 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
+using System.Collections.ObjectModel;
using System.Diagnostics;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
-using Microsoft.Azure.WebJobs.Host.Triggers;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
-using System.Collections.ObjectModel;
namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
{
@@ -25,6 +25,16 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
ShortName = "testfunc"
};
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly TestLoggerProvider _loggerProvider;
+
+ public FunctionListenerTests()
+ {
+ _loggerFactory = new LoggerFactory();
+ _loggerProvider = new TestLoggerProvider();
+ _loggerFactory.AddProvider(_loggerProvider);
+ }
+
[Fact]
public async Task FunctionListener_Throws_IfUnhandledListenerExceptionOnStartAsync()
{
@@ -32,12 +42,20 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
Mock badListener = new Mock(MockBehavior.Strict);
badListener.Setup(bl => bl.StartAsync(It.IsAny()))
.Throws(new Exception("listener"));
- var listener = new FunctionListener(badListener.Object, fd, trace);
+ var listener = new FunctionListener(badListener.Object, fd, trace, _loggerFactory);
var e = await Assert.ThrowsAsync(async () => await listener.StartAsync(ct));
- var exc = trace.Traces[0].Exception as FunctionException;
- Assert.Equal("testfunc", exc.MethodName);
- Assert.False(exc.Handled);
+
+ // Validate TraceWriter
+ var traceEx = trace.Traces[0].Exception as FunctionException;
+ Assert.Equal("testfunc", traceEx.MethodName);
+ Assert.False(traceEx.Handled);
+
+ // Validate Logger
+ var loggerEx = _loggerProvider.CreatedLoggers.Single().LogMessages.Single().Exception as FunctionException;
+ Assert.Equal("testfunc", loggerEx.MethodName);
+ Assert.False(loggerEx.Handled);
+
badListener.VerifyAll();
}
@@ -48,14 +66,25 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
Mock badListener = new Mock(MockBehavior.Strict);
badListener.Setup(bl => bl.StartAsync(It.IsAny()))
.Throws(new Exception("listener"));
- var listener = new FunctionListener(badListener.Object, fd, trace);
+ var listener = new FunctionListener(badListener.Object, fd, trace, _loggerFactory);
await listener.StartAsync(ct);
- Assert.Equal("The listener for function 'testfunc' was unable to start.", trace.Traces[0].Message);
- var exc = trace.Traces[0].Exception as FunctionException;
- Assert.Equal("testfunc", exc.MethodName);
- Assert.True(exc.Handled);
+ string expectedMessage = "The listener for function 'testfunc' was unable to start.";
+
+ // Validate TraceWriter
+ Assert.Equal(expectedMessage, trace.Traces[0].Message);
+ var traceEx = trace.Traces[0].Exception as FunctionException;
+ Assert.Equal("testfunc", traceEx.MethodName);
+ Assert.True(traceEx.Handled);
+
+ // Validate Logger
+ var logMessage = _loggerProvider.CreatedLoggers.Single().LogMessages.Single();
+ Assert.Equal(expectedMessage, logMessage.FormattedMessage);
+ var loggerEx = logMessage.Exception as FunctionException;
+ Assert.Equal("testfunc", loggerEx.MethodName);
+ Assert.True(loggerEx.Handled);
+
badListener.VerifyAll();
}
@@ -66,7 +95,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
Mock badListener = new Mock(MockBehavior.Strict);
badListener.Setup(bl => bl.StartAsync(It.IsAny()))
.Throws(new Exception("listener"));
- var listener = new FunctionListener(badListener.Object, fd, trace);
+ var listener = new FunctionListener(badListener.Object, fd, trace, _loggerFactory);
await listener.StartAsync(ct);
// these should do nothing, as function listener had an exception on start
@@ -87,12 +116,13 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
.Returns(Task.FromResult(false));
goodListener.Setup(bl => bl.StopAsync(It.IsAny()))
.Returns(Task.FromResult(false));
- var listener = new FunctionListener(goodListener.Object, fd, trace);
+ var listener = new FunctionListener(goodListener.Object, fd, trace, _loggerFactory);
await listener.StartAsync(ct);
await listener.StopAsync(ct);
Assert.Empty(trace.Traces);
+ Assert.Empty(_loggerProvider.CreatedLoggers.Single().LogMessages);
goodListener.VerifyAll();
}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/HostListenerFactoryTests.cs
index e13da200..6f80a921 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/HostListenerFactoryTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Listeners/HostListenerFactoryTests.cs
@@ -4,14 +4,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Listeners;
+using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@@ -43,6 +46,10 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
SingletonManager singletonManager = new SingletonManager();
TestTraceWriter traceWriter = new TestTraceWriter(TraceLevel.Verbose);
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ TestLoggerProvider loggerProvider = new TestLoggerProvider();
+ loggerFactory.AddProvider(loggerProvider);
+
// create a bunch of function definitions that are disabled
List functions = new List();
FunctionDescriptor descriptor = new FunctionDescriptor
@@ -55,13 +62,22 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Listeners
// Create the composite listener - this will fail if any of the
// function definitions indicate that they are not disabled
- HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, DefaultJobActivator.Instance, null, traceWriter);
+ HostListenerFactory factory = new HostListenerFactory(functions, singletonManager, DefaultJobActivator.Instance, null, traceWriter, loggerFactory);
IListener listener = await factory.CreateAsync(CancellationToken.None);
+ string expectedMessage = $"Function '{descriptor.ShortName}' is disabled";
+
+ // Validate TraceWriter
Assert.Equal(1, traceWriter.Traces.Count);
Assert.Equal(TraceLevel.Info, traceWriter.Traces[0].Level);
Assert.Equal(TraceSource.Host, traceWriter.Traces[0].Source);
- Assert.Equal(string.Format("Function '{0}' is disabled", descriptor.ShortName), traceWriter.Traces[0].Message);
+ Assert.Equal(expectedMessage, traceWriter.Traces[0].Message);
+
+ // Validate Logger
+ var logMessage = loggerProvider.CreatedLoggers.Single().LogMessages.Single();
+ Assert.Equal(LogLevel.Information, logMessage.Level);
+ Assert.Equal(LogCategories.Startup, logMessage.Category);
+ Assert.Equal(expectedMessage, logMessage.FormattedMessage);
Environment.SetEnvironmentVariable("EnvironmentSettingTrue", null);
}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/ApplicationInsightsLoggerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/ApplicationInsightsLoggerTests.cs
new file mode 100644
index 00000000..e17a0a73
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/ApplicationInsightsLoggerTests.cs
@@ -0,0 +1,481 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.Web;
+using Microsoft.ApplicationInsights;
+using Microsoft.ApplicationInsights.DataContracts;
+using Microsoft.ApplicationInsights.Extensibility;
+using Microsoft.Azure.WebJobs.Host.Executors;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Azure.WebJobs.Host.Protocols;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class ApplicationInsightsLoggerTests
+ {
+ private readonly Guid _invocationId = Guid.NewGuid();
+ private readonly DateTime _startTime = DateTime.UtcNow;
+ private readonly DateTime _endTime;
+ private readonly string _triggerReason = "new queue message";
+ private readonly string _functionFullName = "Functions.TestFunction";
+ private readonly string _functionShortName = "TestFunction";
+ private readonly IDictionary _arguments;
+ private readonly TestTelemetryChannel _channel = new TestTelemetryChannel();
+ private readonly string defaultIp = "0.0.0.0";
+ private readonly TelemetryClient _client;
+ private readonly int durationMs = 450;
+
+ public ApplicationInsightsLoggerTests()
+ {
+ _endTime = _startTime.AddMilliseconds(durationMs);
+ _arguments = new Dictionary
+ {
+ ["queueMessage"] = "my message",
+ ["anotherParam"] = "some value"
+ };
+
+ TelemetryConfiguration config = new TelemetryConfiguration
+ {
+ TelemetryChannel = _channel,
+ InstrumentationKey = "some key"
+ };
+
+ // Add the same initializers that we use in the product code
+ DefaultTelemetryClientFactory.AddInitializers(config);
+
+ _client = new TelemetryClient(config);
+ }
+
+ [Fact]
+ public void LogFunctionResult_Succeeded_SendsCorrectTelemetry()
+ {
+ var result = CreateDefaultInstanceLogEntry();
+ ILogger logger = CreateLogger(LogCategories.Results);
+
+ using (logger.BeginFunctionScope(CreateFunctionInstance(_invocationId)))
+ {
+ logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(durationMs));
+ }
+
+ RequestTelemetry telemetry = _channel.Telemetries.Single() as RequestTelemetry;
+
+ Assert.Equal(_invocationId.ToString(), telemetry.Id);
+ Assert.Equal(_invocationId.ToString(), telemetry.Context.Operation.Id);
+ Assert.Equal(_functionShortName, telemetry.Name);
+ Assert.Equal(_functionShortName, telemetry.Context.Operation.Name);
+ Assert.Equal(defaultIp, telemetry.Context.Location.Ip);
+ // TODO: Beef up validation to include properties
+ }
+
+ [Fact]
+ 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().Single();
+ ExceptionTelemetry exceptionTelemetry = _channel.Telemetries.OfType().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
+ }
+
+ [Fact]
+ public void LogFunctionAggregate_SendsCorrectTelemetry()
+ {
+ DateTime now = DateTime.UtcNow;
+ var resultAggregate = new FunctionResultAggregate
+ {
+ Name = _functionFullName,
+ Failures = 4,
+ Successes = 116,
+ MinDuration = TimeSpan.FromMilliseconds(200),
+ MaxDuration = TimeSpan.FromMilliseconds(2180),
+ AverageDuration = TimeSpan.FromMilliseconds(340),
+ Timestamp = now
+ };
+
+ ILogger logger = CreateLogger(LogCategories.Aggregator);
+ logger.LogFunctionResultAggregate(resultAggregate);
+
+ IEnumerable metrics = _channel.Telemetries.Cast();
+ // turn them into a dictionary so we can easily validate
+ IDictionary metricDict = metrics.ToDictionary(m => m.Name, m => m.Value);
+
+ Assert.Equal(7, metricDict.Count);
+ Assert.Equal(4, metricDict[$"{_functionFullName} {LoggingKeys.Failures}"]);
+ Assert.Equal(116, metricDict[$"{_functionFullName} {LoggingKeys.Successes}"]);
+ Assert.Equal(200, metricDict[$"{_functionFullName} {LoggingKeys.MinDuration}"]);
+ Assert.Equal(2180, metricDict[$"{_functionFullName} {LoggingKeys.MaxDuration}"]);
+ Assert.Equal(340, metricDict[$"{_functionFullName} {LoggingKeys.AvgDuration}"]);
+ Assert.Equal(96.67, metricDict[$"{_functionFullName} {LoggingKeys.SuccessRate}"]);
+ Assert.Equal(120, metricDict[$"{_functionFullName} {LoggingKeys.Count}"]);
+ }
+
+ [Fact]
+ public void LogFunctionResult_HttpRequest_SendsCorrectTelemetry()
+ {
+ // If the scope has an HttpRequestMessage, we'll use the proper values
+ // for the RequestTelemetry
+ DateTime now = DateTime.UtcNow;
+ var result = CreateDefaultInstanceLogEntry();
+
+ var request = new HttpRequestMessage(HttpMethod.Post, "http://someuri/api/path");
+ request.Headers.Add("User-Agent", "my custom user agent");
+ var response = new HttpResponseMessage();
+ request.Properties[ScopeKeys.FunctionsHttpResponse] = response;
+
+ MockIpAddress(request, "1.2.3.4");
+
+ ILogger logger = CreateLogger(LogCategories.Results);
+ var scopeProps = CreateScopeDictionary(_invocationId, _functionShortName);
+ scopeProps[ScopeKeys.HttpRequest] = request;
+
+ using (logger.BeginScope(scopeProps))
+ {
+ logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(durationMs));
+ }
+
+ RequestTelemetry telemetry = _channel.Telemetries.Single() as RequestTelemetry;
+
+ Assert.Equal(_invocationId.ToString(), telemetry.Id);
+ Assert.Equal(_invocationId.ToString(), telemetry.Context.Operation.Id);
+ Assert.Equal(_functionShortName, telemetry.Name);
+ Assert.Equal(_functionShortName, telemetry.Context.Operation.Name);
+ Assert.Equal("1.2.3.4", telemetry.Context.Location.Ip);
+ Assert.Equal("POST", telemetry.Properties[LoggingKeys.HttpMethod]);
+ Assert.Equal(new Uri("http://someuri/api/path"), telemetry.Url);
+ Assert.Equal("my custom user agent", telemetry.Context.User.UserAgent);
+ Assert.Equal("200", telemetry.ResponseCode);
+ // TODO: Beef up validation to include properties
+ }
+
+ [Fact]
+ public void LogFunctionResult_HttpRequest_WithException_SendsCorrectTelemetry()
+ {
+ // If the scope has an HttpRequestMessage, we'll use the proper values
+ // for the RequestTelemetry
+ DateTime now = DateTime.UtcNow;
+ var result = CreateDefaultInstanceLogEntry();
+
+ var request = new HttpRequestMessage(HttpMethod.Post, "http://someuri/api/path");
+ request.Headers.Add("User-Agent", "my custom user agent");
+
+ // In the case of an exception being thrown, no response is attached
+
+ MockIpAddress(request, "1.2.3.4");
+
+ ILogger logger = CreateLogger(LogCategories.Results);
+ var scopeProps = CreateScopeDictionary(_invocationId, _functionShortName);
+ scopeProps[ScopeKeys.HttpRequest] = request;
+
+ using (logger.BeginScope(scopeProps))
+ {
+ logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(durationMs), new Exception("Boom"));
+ }
+
+ // one Exception, one Request
+ Assert.Equal(2, _channel.Telemetries.Count);
+
+ RequestTelemetry requestTelemetry = _channel.Telemetries.Where(t => t is RequestTelemetry).Single() as RequestTelemetry;
+ ExceptionTelemetry exceptionTelemetry = _channel.Telemetries.Where(t => t is ExceptionTelemetry).Single() as ExceptionTelemetry;
+
+ 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("1.2.3.4", requestTelemetry.Context.Location.Ip);
+ Assert.Equal("POST", requestTelemetry.Properties[LoggingKeys.HttpMethod]);
+ Assert.Equal(new Uri("http://someuri/api/path"), requestTelemetry.Url);
+ Assert.Equal("my custom user agent", requestTelemetry.Context.User.UserAgent);
+ Assert.Equal("500", requestTelemetry.ResponseCode);
+ // TODO: Beef up validation to include properties
+ }
+
+ [Fact]
+ public void Log_NoProperties_CreatesTraceAndCorrelates()
+ {
+ Guid scopeGuid = Guid.NewGuid();
+
+ ILogger logger = CreateLogger(LogCategories.Function);
+ using (logger.BeginFunctionScope(CreateFunctionInstance(scopeGuid)))
+ {
+ logger.LogInformation("Information");
+ logger.LogCritical("Critical");
+ logger.LogDebug("Debug");
+ logger.LogError("Error");
+ logger.LogTrace("Trace");
+ logger.LogWarning("Warning");
+ }
+
+ Assert.Equal(6, _channel.Telemetries.Count);
+ Assert.Equal(6, _channel.Telemetries.OfType().Count());
+ foreach (var telemetry in _channel.Telemetries.Cast())
+ {
+ SeverityLevel expectedLevel;
+ if (telemetry.Message == "Trace" || telemetry.Message == "Debug")
+ {
+ expectedLevel = SeverityLevel.Verbose;
+ }
+ else
+ {
+ Assert.True(Enum.TryParse(telemetry.Message, out expectedLevel));
+ }
+ Assert.Equal(expectedLevel, telemetry.SeverityLevel);
+
+ Assert.Equal(LogCategories.Function, telemetry.Properties[LoggingKeys.CategoryName]);
+ Assert.Equal(telemetry.Message, telemetry.Properties[LoggingKeys.CustomPropertyPrefix + LoggingKeys.OriginalFormat]);
+ Assert.Equal(scopeGuid.ToString(), telemetry.Context.Operation.Id);
+ Assert.Equal(_functionShortName, telemetry.Context.Operation.Name);
+ }
+ }
+
+ [Fact]
+ public void Log_WithProperties_IncludesProps()
+ {
+ ILogger logger = CreateLogger(LogCategories.Function);
+ logger.LogInformation("Using {some} custom {properties}. {Test}.", "1", 2, "3");
+
+ var telemetry = _channel.Telemetries.Single() as TraceTelemetry;
+
+ Assert.Equal(SeverityLevel.Information, telemetry.SeverityLevel);
+
+ Assert.Equal(LogCategories.Function, telemetry.Properties[LoggingKeys.CategoryName]);
+ Assert.Equal("Using {some} custom {properties}. {Test}.",
+ telemetry.Properties[LoggingKeys.CustomPropertyPrefix + LoggingKeys.OriginalFormat]);
+ Assert.Equal("Using 1 custom 2. 3.", telemetry.Message);
+ Assert.Equal("1", telemetry.Properties[LoggingKeys.CustomPropertyPrefix + "some"]);
+ Assert.Equal("2", telemetry.Properties[LoggingKeys.CustomPropertyPrefix + "properties"]);
+ Assert.Equal("3", telemetry.Properties[LoggingKeys.CustomPropertyPrefix + "Test"]);
+ }
+
+ [Fact]
+ public void Log_WithException_CreatesExceptionAndCorrelates()
+ {
+ var ex = new InvalidOperationException("Failure");
+ Guid scopeGuid = Guid.NewGuid();
+ ILogger logger = CreateLogger(LogCategories.Function);
+
+ using (logger.BeginFunctionScope(CreateFunctionInstance(scopeGuid)))
+ {
+ logger.LogError(0, ex, "Error with customer: {customer}.", "John Doe");
+ }
+
+ var telemetry = _channel.Telemetries.Single() as ExceptionTelemetry;
+
+ Assert.Equal(SeverityLevel.Error, telemetry.SeverityLevel);
+
+ Assert.Equal(LogCategories.Function, telemetry.Properties[LoggingKeys.CategoryName]);
+ Assert.Equal("Error with customer: {customer}.",
+ telemetry.Properties[LoggingKeys.CustomPropertyPrefix + LoggingKeys.OriginalFormat]);
+ Assert.Equal("Error with customer: John Doe.", telemetry.Message);
+ Assert.Equal("John Doe", telemetry.Properties[LoggingKeys.CustomPropertyPrefix + "customer"]);
+ Assert.Same(ex, telemetry.Exception);
+ Assert.Equal(scopeGuid.ToString(), telemetry.Context.Operation.Id);
+ Assert.Equal(_functionShortName, telemetry.Context.Operation.Name);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4:5")]
+ [InlineData("1.2.3.4")]
+ public void GetIpAddress_ChecksHeaderFirst(string headerIp)
+ {
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.Headers.Add(ScopeKeys.ForwardedForHeaderName, headerIp);
+ MockIpAddress(request, "5.6.7.8");
+
+ string ip = ApplicationInsightsLogger.GetIpAddress(request);
+
+ Assert.Equal("1.2.3.4", ip);
+ }
+
+ [Fact]
+ public void GetIpAddress_ChecksContextSecond()
+ {
+ HttpRequestMessage request = new HttpRequestMessage();
+ MockIpAddress(request, "5.6.7.8");
+
+ string ip = ApplicationInsightsLogger.GetIpAddress(request);
+
+ Assert.Equal("5.6.7.8", ip);
+ }
+
+ [Fact]
+ public async Task BeginScope()
+ {
+ List tasks = new List();
+ for (int i = 0; i < 20; i++)
+ {
+ tasks.Add(Level1(Guid.NewGuid()));
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ private async Task Level1(Guid asyncLocalSetting)
+ {
+ // Push and pop values onto the dictionary at various levels. Make sure they
+ // maintain their AsyncLocal state
+ var level1 = new Dictionary
+ {
+ ["AsyncLocal"] = asyncLocalSetting,
+ ["1"] = 1
+ };
+
+ ILogger logger = CreateLogger(LogCategories.Function);
+ using (logger.BeginScope(level1))
+ {
+ ValidateScope(level1);
+
+ await Level2(asyncLocalSetting);
+
+ ValidateScope(level1);
+ }
+ }
+
+ private async Task Level2(Guid asyncLocalSetting)
+ {
+ await Task.Delay(1);
+
+ var level2 = new Dictionary
+ {
+ ["2"] = 2
+ };
+
+ var expectedLevel2 = new Dictionary
+ {
+ ["1"] = 1,
+ ["2"] = 2,
+ ["AsyncLocal"] = asyncLocalSetting
+ };
+
+ ILogger logger2 = CreateLogger(LogCategories.Function);
+ using (logger2.BeginScope(level2))
+ {
+ ValidateScope(expectedLevel2);
+
+ await Level3(asyncLocalSetting);
+
+ ValidateScope(expectedLevel2);
+ }
+ }
+
+ private async Task Level3(Guid asyncLocalSetting)
+ {
+ await Task.Delay(1);
+
+ // also overwrite value 1, we expect this to win here
+ var level3 = new Dictionary
+ {
+ ["1"] = 11,
+ ["3"] = 3
+ };
+
+ var expectedLevel3 = new Dictionary
+ {
+ ["1"] = 11,
+ ["2"] = 2,
+ ["3"] = 3,
+ ["AsyncLocal"] = asyncLocalSetting
+ };
+
+ ILogger logger3 = CreateLogger(LogCategories.Function);
+ using (logger3.BeginScope(level3))
+ {
+ ValidateScope(expectedLevel3);
+ }
+ }
+
+ private static void MockIpAddress(HttpRequestMessage request, string ipAddress)
+ {
+ Mock mockContext = new Mock(MockBehavior.Strict);
+ Mock mockRequest = new Mock(MockBehavior.Strict);
+ mockRequest.Setup(r => r.UserHostAddress).Returns(ipAddress);
+ mockContext.Setup(c => c.Request).Returns(mockRequest.Object);
+ request.Properties[ScopeKeys.HttpContext] = mockContext.Object;
+ }
+
+ private ILogger CreateLogger(string category)
+ {
+ return new ApplicationInsightsLogger(_client, category, null);
+ }
+
+ private static void ValidateScope(IDictionary expected)
+ {
+ var scopeDict = DictionaryLoggerScope.GetMergedStateDictionary();
+ Assert.Equal(expected.Count, scopeDict.Count);
+ foreach (var entry in expected)
+ {
+ Assert.Equal(entry.Value, scopeDict[entry.Key]);
+ }
+ }
+
+ private IFunctionInstance CreateFunctionInstance(Guid id)
+ {
+ var descriptor = new FunctionDescriptor
+ {
+ Method = GetType().GetMethod(nameof(TestFunction), BindingFlags.NonPublic | BindingFlags.Static)
+ };
+
+ return new FunctionInstance(id, null, new ExecutionReason(), null, null, descriptor);
+ }
+
+ private static IDictionary CreateScopeDictionary(Guid invocationId, string functionName)
+ {
+ return new Dictionary
+ {
+ [ScopeKeys.FunctionInvocationId] = invocationId,
+ [ScopeKeys.FunctionName] = functionName
+ };
+ }
+
+ private static void TestFunction()
+ {
+ // used for a FunctionDescriptor
+ }
+
+ private FunctionInstanceLogEntry CreateDefaultInstanceLogEntry()
+ {
+ return new FunctionInstanceLogEntry
+ {
+ FunctionName = _functionFullName,
+ FunctionInstanceId = _invocationId,
+ StartTime = _startTime,
+ EndTime = _endTime,
+ LogOutput = "a bunch of output that we will not forward", // not used here -- this is all Traced
+ TriggerReason = _triggerReason,
+ ParentId = Guid.NewGuid(), // we do not track this
+ ErrorDetails = null, // we do not use this -- we pass the exception in separately
+ Arguments = _arguments
+ };
+ }
+
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FormattedLogValuesCollectionTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FormattedLogValuesCollectionTests.cs
new file mode 100644
index 00000000..b802f8f3
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FormattedLogValuesCollectionTests.cs
@@ -0,0 +1,164 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class FormattedLogValuesCollectionTests
+ {
+ [Fact]
+ public void Verify_ToString_AndCollection()
+ {
+ var additionalPayload = new Dictionary()
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123
+ };
+
+ var payload = new FormattedLogValuesCollection("This {string} has some {data}",
+ new object[] { "xyz", 789 }, new ReadOnlyDictionary(additionalPayload));
+
+ // Verify string behavior
+ Assert.Equal("This xyz has some 789", payload.ToString());
+
+ // Verify the collection
+ var expectedPayload = new Dictionary
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123,
+ ["string"] = "xyz",
+ ["data"] = 789,
+
+ // FormattedLogValues adds this automatically to capture the string template
+ ["{OriginalFormat}"] = "This {string} has some {data}"
+ };
+
+ ValidateCollection(expectedPayload, payload);
+ }
+
+ [Fact]
+ public void NoAdditionalPayload()
+ {
+ var payload = new FormattedLogValuesCollection("This {string} has some {data}",
+ new object[] { "xyz", 789 }, null);
+
+ // Verify string behavior
+ Assert.Equal("This xyz has some 789", payload.ToString());
+
+ // Verify the collection
+ var expectedPayload = new Dictionary
+ {
+ ["string"] = "xyz",
+ ["data"] = 789,
+
+ // FormattedLogValues adds this automatically to capture the string template
+ ["{OriginalFormat}"] = "This {string} has some {data}"
+ };
+
+ ValidateCollection(expectedPayload, payload);
+ }
+
+ [Fact]
+ public void NoStringPayload()
+ {
+ var additionalPayload = new Dictionary()
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123
+ };
+
+ var payload = new FormattedLogValuesCollection("This string has no data",
+ null, new ReadOnlyDictionary(additionalPayload));
+
+ // Verify string behavior
+ Assert.Equal("This string has no data", payload.ToString());
+
+ // Verify the collection
+ var expectedPayload = new Dictionary
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123,
+
+ // FormattedLogValues adds this automatically to capture the string template
+ ["{OriginalFormat}"] = "This string has no data"
+ };
+
+ ValidateCollection(expectedPayload, payload);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void NoString(string message)
+ {
+ var additionalPayload = new Dictionary()
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123
+ };
+
+ var payload = new FormattedLogValuesCollection(message,
+ null, new ReadOnlyDictionary(additionalPayload));
+
+ // Verify string behavior
+ // FormattedLogValues changes nulls to [null]
+ var expectedMessage = message ?? "[null]";
+ Assert.Equal(expectedMessage, payload.ToString());
+
+ // Verify the collection
+ var expectedPayload = new Dictionary
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123,
+
+ // FormattedLogValues adds this automatically to capture the string template
+ ["{OriginalFormat}"] = expectedMessage
+ };
+
+ ValidateCollection(expectedPayload, payload);
+ }
+
+ [Fact]
+ public void NullValues_WithCurlyBraces()
+ {
+ var additionalPayload = new Dictionary()
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123
+ };
+
+ var message = "{this} {should} {work} {";
+ var payload = new FormattedLogValuesCollection(message,
+ null, new ReadOnlyDictionary(additionalPayload));
+
+ // Verify string behavior
+ Assert.Equal(message, payload.ToString());
+
+ // Verify the collection
+ var expectedPayload = new Dictionary
+ {
+ ["additional"] = "abc",
+ ["payload"] = 123,
+
+ // FormattedLogValues adds this automatically to capture the string template
+ ["{OriginalFormat}"] = message
+ };
+
+ ValidateCollection(expectedPayload, payload);
+ }
+
+ private static void ValidateCollection(IDictionary expectedPayload,
+ IReadOnlyList> actualPayload)
+ {
+ Assert.Equal(expectedPayload.Count, actualPayload.Count);
+ foreach (var kvp in actualPayload)
+ {
+ Assert.Equal(expectedPayload[kvp.Key], kvp.Value);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorConfigurationTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorConfigurationTests.cs
new file mode 100644
index 00000000..b7945cde
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorConfigurationTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+
+using System;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class FunctionResultAggregatorConfigurationTests
+ {
+ [Fact]
+ public void DefaultValues()
+ {
+ var config = new FunctionResultAggregatorConfiguration();
+
+ Assert.Equal(TimeSpan.FromSeconds(30), config.FlushTimeout);
+ Assert.Equal(1000, config.BatchSize);
+ Assert.Equal(true, config.IsEnabled);
+ }
+
+ [Theory]
+ [InlineData(0, true)]
+ [InlineData(10001, true)]
+ [InlineData(1, false)]
+ [InlineData(10000, false)]
+ public void BatchSize_Limits(int batchSize, bool throws)
+ {
+ var config = new FunctionResultAggregatorConfiguration();
+ ArgumentOutOfRangeException caughtEx = null;
+
+ try
+ {
+ config.BatchSize = batchSize;
+ }
+ catch (ArgumentOutOfRangeException ex)
+ {
+ caughtEx = ex;
+ }
+
+ Assert.Equal(throws, caughtEx != null);
+ }
+
+ public static object[][] TimeSpanData = new object[][] {
+ new object[] { TimeSpan.FromSeconds(300), false },
+ new object[] { TimeSpan.FromSeconds(0), true },
+ new object[] { TimeSpan.FromSeconds(301), true },
+ new object[] { TimeSpan.FromSeconds(1), false },
+ };
+
+ [Theory]
+ [MemberData(nameof(TimeSpanData))]
+ public void FlushTimeout_Limits(TimeSpan flushTimeout, bool throws)
+ {
+ var config = new FunctionResultAggregatorConfiguration();
+ ArgumentOutOfRangeException caughtEx = null;
+
+ try
+ {
+ config.FlushTimeout = flushTimeout;
+ }
+ catch (ArgumentOutOfRangeException ex)
+ {
+ caughtEx = ex;
+ }
+
+ Assert.Equal(throws, caughtEx != null);
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorTests.cs
new file mode 100644
index 00000000..94c0df2b
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/FunctionResultAggregatorTests.cs
@@ -0,0 +1,209 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class FunctionResultAggregatorTests
+ {
+ [Theory]
+ [InlineData(1)]
+ [InlineData(1000)]
+ public async Task Aggregator_Flushes_WhenBatchIsFull(int batchSize)
+ {
+ int publishCalls = 0;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ Assert.Equal(batchSize, props[LoggingKeys.Successes]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromMinutes(1), factory);
+
+ await AddSuccessResults(aggregator, batchSize * 5);
+
+ // allow a flush
+ await Task.Delay(100);
+
+ Assert.Equal((batchSize * 5) / batchSize, publishCalls);
+ }
+
+ [Fact]
+ public async Task Aggregator_Flushes_WhenTimerFires()
+ {
+ int batchSize = 1000;
+ int numberToInsert = 10;
+ int publishCalls = 0;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ Assert.Equal(numberToInsert, props[LoggingKeys.Successes]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromSeconds(1), factory);
+
+ await AddSuccessResults(aggregator, numberToInsert);
+
+ // allow the timer to fire
+ await Task.Delay(1100);
+
+ Assert.Equal(1, publishCalls);
+ }
+
+ [Fact]
+ public async Task Aggregator_AlternatesTimerAndBatch()
+ {
+ int publishCalls = 0;
+ int totalSuccesses = 0;
+ int batchSize = 100;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ totalSuccesses += Convert.ToInt32(props[LoggingKeys.Successes]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromSeconds(1), factory);
+
+ // do this loop twice to ensure it continues working
+ for (int i = 0; i < 2; i++)
+ {
+ // insert 225. Expect 2 calls to publish, then a flush for 25.
+ await AddSuccessResults(aggregator, 225);
+
+ await Task.Delay(1100);
+ Assert.Equal(3 * (i + 1), publishCalls);
+ }
+
+ Assert.Equal(6, publishCalls);
+ Assert.Equal(450, totalSuccesses);
+ }
+
+ [Fact]
+ public async Task Aggregator_FlushesOnCancelation()
+ {
+ int batchSize = 100;
+ int publishCalls = 0;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ Assert.Equal(10, props[LoggingKeys.Successes]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromSeconds(1), factory);
+
+ await AddSuccessResults(aggregator, 10);
+ }
+
+ [Fact]
+ public async Task Aggregator_Trims_DefaultClassName()
+ {
+ int batchSize = 10;
+ int publishCalls = 0;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ Assert.Equal(10, props[LoggingKeys.Successes]);
+ Assert.Equal("SomeTest", props[LoggingKeys.Name]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromSeconds(1), factory);
+
+ await AddSuccessResults(aggregator, 10, "Functions.SomeTest");
+ }
+
+ [Fact]
+ public async Task Aggregator_DoesNotTrim_NonDefaultClassName()
+ {
+ int batchSize = 10;
+ int publishCalls = 0;
+
+ ILoggerFactory factory = CreateMockLoggerFactory((props) =>
+ {
+ publishCalls++;
+ Assert.Equal(10, props[LoggingKeys.Successes]);
+ Assert.Equal("AnotherClass.SomeTest", props[LoggingKeys.Name]);
+ });
+
+ var aggregator = new FunctionResultAggregator(batchSize, TimeSpan.FromSeconds(1), factory);
+
+ await AddSuccessResults(aggregator, 10, "AnotherClass.SomeTest");
+ }
+
+
+ private static async Task AddSuccessResults(IAsyncCollector aggregator, int count, string functionName = null)
+ {
+ // Simulate the real executor, where a "function start" and "function end" are both added.
+ // The aggregator will filter out any with EndTime == null
+ for (int i = 0; i < count; i++)
+ {
+ await aggregator.AddAsync(new FunctionInstanceLogEntry
+ {
+ FunctionName = functionName,
+ ErrorDetails = null,
+ EndTime = null
+ });
+
+ await aggregator.AddAsync(new FunctionInstanceLogEntry
+ {
+ FunctionName = functionName,
+ ErrorDetails = null,
+ EndTime = DateTime.Now
+ });
+ }
+ }
+
+ private static ILoggerFactory CreateMockLoggerFactory(Action> logAction)
+ {
+ ILogger logger = new MockLogger(logAction);
+
+ Mock mockFactory = new Mock();
+ mockFactory
+ .Setup(lf => lf.CreateLogger(It.IsAny()))
+ .Returns(logger);
+
+ return mockFactory.Object;
+ }
+
+ private class MockLogger : ILogger
+ {
+ private Action> _logAction;
+
+ public MockLogger(Action> logAction)
+ {
+ _logAction = logAction;
+ }
+ public IDisposable BeginScope(TState state)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ // in these tests, we will only ever see Information;
+ Assert.Equal(LogLevel.Information, logLevel);
+ return true;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ IEnumerable> props = state as IEnumerable>;
+ Assert.NotNull(props);
+
+ _logAction(props.ToDictionary(k => k.Key, k => k.Value));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LogCategoryFilterTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LogCategoryFilterTests.cs
new file mode 100644
index 00000000..d949c182
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LogCategoryFilterTests.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Extensions.Logging;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class LogCategoryFilterTests
+ {
+ [Fact]
+ public void Filter_MatchesLongestCategory()
+ {
+ var filter = new LogCategoryFilter();
+ filter.DefaultLevel = LogLevel.Error;
+ filter.CategoryLevels.Add("Microsoft", LogLevel.Critical);
+ filter.CategoryLevels.Add("Microsoft.Azure", LogLevel.Error);
+ filter.CategoryLevels.Add("Microsoft.Azure.WebJobs", LogLevel.Information);
+ filter.CategoryLevels.Add("Microsoft.Azure.WebJobs.Host", LogLevel.Trace);
+
+ Assert.False(filter.Filter("Microsoft", LogLevel.Information));
+ Assert.False(filter.Filter("Microsoft.Azure", LogLevel.Information));
+ Assert.False(filter.Filter("Microsoft.Azure.WebJob", LogLevel.Information));
+ Assert.False(filter.Filter("NoMatch", LogLevel.Information));
+
+ Assert.True(filter.Filter("Microsoft", LogLevel.Critical));
+ Assert.True(filter.Filter("Microsoft.Azure", LogLevel.Critical));
+ Assert.True(filter.Filter("Microsoft.Azure.WebJobs.Extensions", LogLevel.Information));
+ Assert.True(filter.Filter("Microsoft.Azure.WebJobs.Host", LogLevel.Debug));
+ Assert.True(filter.Filter("NoMatch", LogLevel.Error));
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LoggerExtensionsTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LoggerExtensionsTests.cs
new file mode 100644
index 00000000..d8e6b35a
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/LoggerExtensionsTests.cs
@@ -0,0 +1,191 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Azure.WebJobs.Host.Loggers;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ public class LoggerExtensionsTests
+ {
+ private Guid _invocationId = Guid.NewGuid();
+ private DateTime _startTime = DateTime.Now;
+ private DateTime _endTime;
+ private string _triggerReason = "new queue message";
+ private string _functionShortName = "TestFunction";
+ private string _functionFullName = "Functions.TestFunction";
+ private IDictionary _arguments;
+
+ public LoggerExtensionsTests()
+ {
+ _endTime = _startTime.AddMilliseconds(450);
+ _arguments = new Dictionary
+ {
+ ["queueMessage"] = "my message",
+ ["anotherParam"] = "some value"
+ };
+ }
+
+ [Fact]
+ public void LogFunctionResult_Succeeded_CreatesCorrectState()
+ {
+ int logCount = 0;
+ ILogger logger = CreateMockLogger((l, e, o, ex, f) =>
+ {
+ logCount++;
+ Assert.Equal(LogLevel.Information, l);
+ Assert.Equal(0, e);
+ Assert.Null(ex);
+ Assert.Equal($"Executed '{_functionFullName}' (Succeeded, Id={_invocationId})", f(o, ex));
+
+ var payload = VerifyResultDefaultsAndConvert(o);
+ Assert.True((bool)payload[LoggingKeys.Succeeded]);
+ Assert.Equal("Executed '{FullName}' (Succeeded, Id={InvocationId})", payload[LoggingKeys.OriginalFormat]);
+ });
+
+ var result = CreateDefaultInstanceLogEntry();
+
+ logger.LogFunctionResult(_functionShortName, result, TimeSpan.FromMilliseconds(450));
+
+ Assert.Equal(1, logCount);
+ }
+
+ [Fact]
+ public void LogFunctionResult_Failed_CreatesCorrectState()
+ {
+ int logCount = 0;
+ ILogger logger = CreateMockLogger((l, e, o, ex, f) =>
+ {
+ logCount++;
+ Assert.Equal(LogLevel.Error, l);
+ Assert.Equal(0, e);
+ Assert.NotNull(ex);
+ Assert.IsType(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);
+ }
+
+ [Fact]
+ public void LogFunctionResultAggregate_CreatesCorrectState()
+ {
+ DateTimeOffset now = DateTimeOffset.Now;
+ int logCount = 0;
+ ILogger logger = CreateMockLogger((l, e, o, ex, f) =>
+ {
+ logCount++;
+ Assert.Equal(LogLevel.Information, l);
+ Assert.Equal(0, e);
+ Assert.Null(ex);
+
+ // nothing logged
+ Assert.Equal(string.Empty, f(o, ex));
+
+ // convert to dictionary
+ var payload = o.ToDictionary(k => k.Key, v => v.Value);
+
+ Assert.Equal(10, payload.Count);
+ Assert.Equal(_functionShortName, payload[LoggingKeys.Name]);
+ Assert.Equal(4, payload[LoggingKeys.Failures]);
+ Assert.Equal(116, payload[LoggingKeys.Successes]);
+ Assert.Equal(TimeSpan.FromMilliseconds(200), (TimeSpan)payload[LoggingKeys.MinDuration]);
+ Assert.Equal(TimeSpan.FromMilliseconds(2180), (TimeSpan)payload[LoggingKeys.MaxDuration]);
+ Assert.Equal(TimeSpan.FromMilliseconds(340), (TimeSpan)payload[LoggingKeys.AvgDuration]);
+ Assert.Equal(now, payload[LoggingKeys.Timestamp]);
+ Assert.Equal(120, payload[LoggingKeys.Count]);
+ Assert.Equal(96.67, payload[LoggingKeys.SuccessRate]);
+
+ // {OriginalFormat} is still added, even though it is empty
+ Assert.Equal(string.Empty, payload[LoggingKeys.OriginalFormat]);
+ });
+
+ var resultAggregate = new FunctionResultAggregate
+ {
+ Name = _functionShortName,
+ Failures = 4,
+ Successes = 116,
+ MinDuration = TimeSpan.FromMilliseconds(200),
+ MaxDuration = TimeSpan.FromMilliseconds(2180),
+ AverageDuration = TimeSpan.FromMilliseconds(340),
+ Timestamp = now
+ };
+
+ logger.LogFunctionResultAggregate(resultAggregate);
+
+ Assert.Equal(1, logCount);
+ }
+
+ private FunctionInstanceLogEntry CreateDefaultInstanceLogEntry()
+ {
+ return new FunctionInstanceLogEntry
+ {
+ FunctionName = _functionFullName,
+ FunctionInstanceId = _invocationId,
+ StartTime = _startTime,
+ EndTime = _endTime,
+ LogOutput = "a bunch of output that we will not forward", // not used here -- this is all Traced
+ TriggerReason = _triggerReason,
+ ParentId = Guid.NewGuid(), // we do not track this
+ ErrorDetails = null, // we do not use this -- we pass the exception in separately
+ Arguments = _arguments
+ };
+ }
+
+ private IDictionary VerifyResultDefaultsAndConvert(TState state)
+ {
+ var enumerable = state as IEnumerable>;
+ Assert.NotNull(enumerable);
+
+ var payload = enumerable.ToDictionary(k => k.Key, v => v.Value);
+
+ Assert.Equal(11, payload.Count);
+ Assert.Equal(_functionFullName, payload[LoggingKeys.FullName]);
+ Assert.Equal(_functionShortName, payload[LoggingKeys.Name]);
+ Assert.Equal(_invocationId, payload[LoggingKeys.InvocationId]);
+ Assert.Equal(_startTime, payload[LoggingKeys.StartTime]);
+ Assert.Equal(_endTime, payload[LoggingKeys.EndTime]);
+ Assert.Equal(TimeSpan.FromMilliseconds(450), payload[LoggingKeys.Duration]);
+ Assert.Equal(_triggerReason, payload[LoggingKeys.TriggerReason]);
+
+ // verify default arguments were passed with prefix
+ var args = payload.Where(kvp => kvp.Value is string && kvp.Key.ToString().StartsWith(LoggingKeys.ParameterPrefix));
+ Assert.Equal(_arguments.Count, args.Count());
+ foreach (var arg in _arguments)
+ {
+ var payloadKey = LoggingKeys.ParameterPrefix + arg.Key;
+ Assert.Equal(arg.Value, args.Single(kvp => kvp.Key == payloadKey).Value.ToString());
+ }
+
+ return payload;
+ }
+
+ private static ILogger CreateMockLogger(Action> callback)
+ {
+ var mockLogger = new Mock(MockBehavior.Strict);
+ mockLogger
+ .Setup(l => l.Log(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()))
+ .Callback>((l, e, o, ex, f) =>
+ {
+ callback(l, e, o, ex, f);
+ });
+
+ return mockLogger.Object;
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/TestTelemetryChannel.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/TestTelemetryChannel.cs
new file mode 100644
index 00000000..bf34be7d
--- /dev/null
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Loggers/TestTelemetryChannel.cs
@@ -0,0 +1,31 @@
+
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System.Collections.Concurrent;
+using Microsoft.ApplicationInsights.Channel;
+
+namespace Microsoft.Azure.WebJobs.Host.UnitTests.Loggers
+{
+ internal class TestTelemetryChannel : ITelemetryChannel
+ {
+ public ConcurrentBag Telemetries = new ConcurrentBag();
+
+ public bool? DeveloperMode { get; set; }
+
+ public string EndpointAddress { get; set; }
+
+ public void Dispose()
+ {
+ }
+
+ public void Flush()
+ {
+ }
+
+ public void Send(ITelemetry item)
+ {
+ Telemetries.Add(item);
+ }
+ }
+}
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs
index eb1c4412..9b41d585 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs
@@ -226,7 +226,13 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests
"RecoverableException",
"FunctionException",
"FunctionListenerException",
- "ExceptionFormatter"
+ "ExceptionFormatter",
+ "FunctionResultAggregatorConfiguration",
+ "ApplicationInsightsLoggerExtensions",
+ "LogCategoryFilter",
+ "LogCategories",
+ "ITelemetryClientFactory",
+ "DefaultTelemetryClientFactory"
};
AssertPublicTypes(expected, assembly);
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueListenerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueListenerTests.cs
index 8501c4a4..91459019 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueListenerTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueListenerTests.cs
@@ -3,7 +3,6 @@
using System;
using System.Diagnostics;
-using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
@@ -13,6 +12,7 @@ using Microsoft.Azure.WebJobs.Host.Queues.Listeners;
using Microsoft.Azure.WebJobs.Host.Storage.Queue;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Queue;
using Moq;
using Xunit;
@@ -25,6 +25,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
private Mock _mockQueueProcessor;
private Mock> _mockTriggerExecutor;
private StorageQueueMessage _storageMessage;
+ private ILoggerFactory _loggerFactory;
public QueueListenerTests()
{
@@ -34,10 +35,12 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
_mockTriggerExecutor = new Mock>(MockBehavior.Strict);
Mock mockExceptionDispatcher = new Mock(MockBehavior.Strict);
- TestTraceWriter log = new TestTraceWriter(TraceLevel.Verbose);
+ TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose);
+ _loggerFactory = new LoggerFactory();
+ _loggerFactory.AddProvider(new TestLoggerProvider());
Mock mockQueueProcessorFactory = new Mock(MockBehavior.Strict);
JobHostQueuesConfiguration queuesConfig = new JobHostQueuesConfiguration();
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(queue, log, queuesConfig);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(queue, trace, _loggerFactory, queuesConfig);
_mockQueueProcessor = new Mock(MockBehavior.Strict, context);
JobHostQueuesConfiguration queueConfig = new JobHostQueuesConfiguration
@@ -48,7 +51,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
mockQueueProcessorFactory.Setup(p => p.Create(It.IsAny())).Returns(_mockQueueProcessor.Object);
- _listener = new QueueListener(mockQueue.Object, null, _mockTriggerExecutor.Object, mockExceptionDispatcher.Object, log, null, queueConfig);
+ _listener = new QueueListener(mockQueue.Object, null, _mockTriggerExecutor.Object, mockExceptionDispatcher.Object, trace, _loggerFactory, null, queueConfig);
CloudQueueMessage cloudMessage = new CloudQueueMessage("TestMessage");
_storageMessage = new StorageQueueMessage(cloudMessage);
@@ -58,7 +61,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
public void CreateQueueProcessor_CreatesProcessorCorrectly()
{
CloudQueue poisonQueue = null;
- TestTraceWriter log = new TestTraceWriter(TraceLevel.Verbose);
+ TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose);
bool poisonMessageHandlerInvoked = false;
EventHandler poisonMessageEventHandler = (sender, e) => { poisonMessageHandlerInvoked = true; };
Mock mockQueueProcessorFactory = new Mock(MockBehavior.Strict);
@@ -72,7 +75,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
// create for a host queue - don't expect custom factory to be invoked
CloudQueue queue = new CloudQueue(new Uri(string.Format("https://test.queue.core.windows.net/{0}", HostQueueNames.GetHostQueueName("12345"))));
- QueueProcessor queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, log, queueConfig, poisonMessageEventHandler);
+ QueueProcessor queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, trace, _loggerFactory, queueConfig, poisonMessageEventHandler);
Assert.False(processorFactoryInvoked);
Assert.NotSame(expectedQueueProcessor, queueProcessor);
queueProcessor.OnMessageAddedToPoisonQueue(new PoisonMessageEventArgs(null, poisonQueue));
@@ -87,11 +90,12 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
Assert.Same(queue, mockProcessorContext.Queue);
Assert.Same(poisonQueue, mockProcessorContext.PoisonQueue);
Assert.Equal(queueConfig.MaxDequeueCount, mockProcessorContext.MaxDequeueCount);
- Assert.Same(log, mockProcessorContext.Trace);
+ Assert.Same(trace, mockProcessorContext.Trace);
+ Assert.NotNull(mockProcessorContext.Logger);
processorFactoryContext = mockProcessorContext;
})
- .Returns(() =>
+ .Returns(() =>
{
expectedQueueProcessor = new QueueProcessor(processorFactoryContext);
return expectedQueueProcessor;
@@ -101,7 +105,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
// host queues (this enables local test mocking)
processorFactoryInvoked = false;
queue = new CloudQueue(new Uri(string.Format("https://localhost/{0}", HostQueueNames.GetHostQueueName("12345"))));
- queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, log, queueConfig, poisonMessageEventHandler);
+ queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, trace, _loggerFactory, queueConfig, poisonMessageEventHandler);
Assert.True(processorFactoryInvoked);
Assert.Same(expectedQueueProcessor, queueProcessor);
@@ -109,7 +113,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
poisonMessageHandlerInvoked = false;
processorFactoryInvoked = false;
queue = new CloudQueue(new Uri("https://test.queue.core.windows.net/testqueue"));
- queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, log, queueConfig, poisonMessageEventHandler);
+ queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, trace, _loggerFactory, queueConfig, poisonMessageEventHandler);
Assert.True(processorFactoryInvoked);
Assert.Same(expectedQueueProcessor, queueProcessor);
queueProcessor.OnMessageAddedToPoisonQueue(new PoisonMessageEventArgs(null, poisonQueue));
@@ -118,7 +122,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
// if poison message watcher not specified, event not subscribed to
poisonMessageHandlerInvoked = false;
processorFactoryInvoked = false;
- queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, log, queueConfig, null);
+ queueProcessor = QueueListener.CreateQueueProcessor(queue, poisonQueue, trace, _loggerFactory, queueConfig, null);
Assert.True(processorFactoryInvoked);
Assert.Same(expectedQueueProcessor, queueProcessor);
queueProcessor.OnMessageAddedToPoisonQueue(new PoisonMessageEventArgs(null, poisonQueue));
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueProcessorFactoryContextTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueProcessorFactoryContextTests.cs
index f80b2bc2..eac59a1f 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueProcessorFactoryContextTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueProcessorFactoryContextTests.cs
@@ -3,9 +3,10 @@
using System;
using System.Diagnostics;
-using System.IO;
+using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Queues;
using Microsoft.Azure.WebJobs.Host.TestCommon;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Queue;
using Xunit;
@@ -18,14 +19,17 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
{
CloudQueue queue = new CloudQueue(new Uri("https://test.queue.core.windows.net/testqueue"));
CloudQueue poisonQueue = new CloudQueue(new Uri("https://test.queue.core.windows.net/poisonqueue"));
- TestTraceWriter log = new TestTraceWriter(TraceLevel.Verbose);
+ TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose);
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ loggerFactory.AddProvider(new TestLoggerProvider());
JobHostQueuesConfiguration queuesConfig = new JobHostQueuesConfiguration();
- QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(queue, log, queuesConfig, poisonQueue);
+ QueueProcessorFactoryContext context = new QueueProcessorFactoryContext(queue, trace, loggerFactory, queuesConfig, poisonQueue);
Assert.Same(queue, context.Queue);
- Assert.Same(log, context.Trace);
+ Assert.Same(trace, context.Trace);
Assert.Same(poisonQueue, context.PoisonQueue);
+ Assert.NotNull(context.Logger);
Assert.Equal(queuesConfig.BatchSize, context.BatchSize);
Assert.Equal(queuesConfig.NewBatchThreshold, context.NewBatchThreshold);
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueTriggerBindingIntegrationTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueTriggerBindingIntegrationTests.cs
index 95bbb548..f3d18dc5 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueTriggerBindingIntegrationTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Queues/QueueTriggerBindingIntegrationTests.cs
@@ -34,7 +34,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Queues
_binding = new QueueTriggerBinding("parameterName", queue, argumentBinding,
new Mock(MockBehavior.Strict).Object, exceptionHandler,
new Mock>(MockBehavior.Strict).Object,
- new SharedContextProvider(), new TestTraceWriter(TraceLevel.Verbose));
+ new SharedContextProvider(), new TestTraceWriter(TraceLevel.Verbose), null);
}
[Theory]
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonListenerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonListenerTests.cs
index be02dea0..ac7b66d0 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonListenerTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonListenerTests.cs
@@ -31,10 +31,12 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{
LockPeriod = TimeSpan.FromSeconds(20)
};
- _mockSingletonManager = new Mock(MockBehavior.Strict, null, null, null, null, new FixedHostIdProvider(TestHostId), null);
+ _mockSingletonManager = new Mock(MockBehavior.Strict, null, null, null, null, null, new FixedHostIdProvider(TestHostId), null);
_mockSingletonManager.SetupGet(p => p.Config).Returns(_config);
_mockInnerListener = new Mock(MockBehavior.Strict);
- _listener = new SingletonListener(methodInfo, _attribute, _mockSingletonManager.Object, _mockInnerListener.Object, new TestTraceWriter(System.Diagnostics.TraceLevel.Verbose));
+
+ _listener = new SingletonListener(methodInfo, _attribute, _mockSingletonManager.Object, _mockInnerListener.Object,
+ new TestTraceWriter(System.Diagnostics.TraceLevel.Verbose), null);
_lockId = SingletonManager.FormatLockId(methodInfo, SingletonScope.Function, TestHostId, _attribute.ScopeId) + ".Listener";
}
@@ -59,7 +61,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
public async Task StartAsync_DoesNotStartListener_WhenLockCannotBeAcquired()
{
CancellationToken cancellationToken = new CancellationToken();
- _mockSingletonManager.Setup(p => p.TryLockAsync(_lockId, null, _attribute, cancellationToken, false))
+ _mockSingletonManager.Setup(p => p.TryLockAsync(_lockId, null, _attribute, cancellationToken, false))
.ReturnsAsync((object)null);
await _listener.StartAsync(cancellationToken);
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonManagerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonManagerTests.cs
index 0e11cd2f..f3d8c04a 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonManagerTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonManagerTests.cs
@@ -9,10 +9,12 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
+using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Storage;
using Microsoft.Azure.WebJobs.Host.Storage.Blob;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Azure.WebJobs.Host.Timers;
+using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Moq;
@@ -39,6 +41,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
private Mock _mockExceptionDispatcher;
private Mock _mockStorageBlob;
private TestTraceWriter _trace = new TestTraceWriter(TraceLevel.Verbose);
+ private TestLoggerProvider _loggerProvider;
private Dictionary _mockBlobMetadata;
private TestNameResolver _nameResolver;
@@ -79,7 +82,14 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
_singletonConfig.LockAcquisitionTimeout = TimeSpan.FromMilliseconds(200);
_nameResolver = new TestNameResolver();
- _singletonManager = new SingletonManager(_mockAccountProvider.Object, _mockExceptionDispatcher.Object, _singletonConfig, _trace, new FixedHostIdProvider(TestHostId), _nameResolver);
+
+ ILoggerFactory loggerFactory = new LoggerFactory();
+ // We want to see all logs, so set the default level to Trace.
+ LogCategoryFilter filter = new LogCategoryFilter { DefaultLevel = Extensions.Logging.LogLevel.Trace };
+ _loggerProvider = new TestLoggerProvider(filter.Filter);
+ loggerFactory.AddProvider(_loggerProvider);
+
+ _singletonManager = new SingletonManager(_mockAccountProvider.Object, _mockExceptionDispatcher.Object, _singletonConfig, _trace, loggerFactory, new FixedHostIdProvider(TestHostId), _nameResolver);
_singletonManager.MinimumLeaseRenewalInterval = TimeSpan.FromMilliseconds(250);
}
@@ -170,6 +180,13 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
Assert.Equal(1, _trace.Traces.Count(p => p.ToString().Contains("Verbose Singleton lock acquired (testid)")));
Assert.Equal(1, _trace.Traces.Count(p => p.ToString().Contains("Verbose Singleton lock released (testid)")));
+ // verify the logger
+ TestLogger logger = _loggerProvider.CreatedLoggers.Single() as TestLogger;
+ Assert.Equal(LogCategories.Singleton, logger.Category);
+ Assert.Equal(2, logger.LogMessages.Count);
+ Assert.NotNull(logger.LogMessages.Single(m => m.Level == Extensions.Logging.LogLevel.Debug && m.FormattedMessage == "Singleton lock acquired (testid)"));
+ Assert.NotNull(logger.LogMessages.Single(m => m.Level == Extensions.Logging.LogLevel.Debug && m.FormattedMessage == "Singleton lock released (testid)"));
+
renewCount = 0;
await Task.Delay(1000);
@@ -334,7 +351,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
{
Mock mockHostIdProvider = new Mock(MockBehavior.Strict);
mockHostIdProvider.Setup(p => p.GetHostIdAsync(CancellationToken.None)).ReturnsAsync(TestHostId);
- SingletonManager singletonManager = new SingletonManager(null, null, null, null, mockHostIdProvider.Object);
+ SingletonManager singletonManager = new SingletonManager(null, null, null, null, null, mockHostIdProvider.Object);
Assert.Equal(TestHostId, singletonManager.HostId);
Assert.Equal(TestHostId, singletonManager.HostId);
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonValueProviderTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonValueProviderTests.cs
index 40a02e65..8e1a8e16 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonValueProviderTests.cs
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Singleton/SingletonValueProviderTests.cs
@@ -24,7 +24,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
public SingletonValueProviderTests()
{
_attribute = new SingletonAttribute("TestScope");
- SingletonManager singletonManager = new SingletonManager(null, null, null, null, new FixedHostIdProvider(TestHostId));
+ SingletonManager singletonManager = new SingletonManager(null, null, null, null, null, new FixedHostIdProvider(TestHostId));
_method = GetType().GetMethod("TestJob", BindingFlags.Static | BindingFlags.Public);
_lockId = SingletonManager.FormatLockId(_method, SingletonScope.Function, TestHostId, _attribute.ScopeId);
_valueProvider = new SingletonValueProvider(_method, "TestScope", TestInstanceId, _attribute, singletonManager);
@@ -54,7 +54,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
[Fact]
public async Task ToInvokeString_ReturnsExpectedValue()
{
- SingletonManager singletonManager = new SingletonManager(null, null, null, null, new FixedHostIdProvider(TestHostId));
+ SingletonManager singletonManager = new SingletonManager(null, null, null, null, null, new FixedHostIdProvider(TestHostId));
SingletonAttribute attribute = new SingletonAttribute();
SingletonValueProvider localValueProvider = new SingletonValueProvider(_method, attribute.ScopeId, TestInstanceId, attribute, singletonManager);
SingletonLock singletonLock = (SingletonLock)(await localValueProvider.GetValueAsync());
@@ -69,7 +69,7 @@ namespace Microsoft.Azure.WebJobs.Host.UnitTests.Singleton
[Fact]
public async Task SingletonWatcher_GetStatus_ReturnsExpectedValue()
{
- Mock mockSingletonManager = new Mock(MockBehavior.Strict, null, null, null, null, new FixedHostIdProvider(TestHostId), null);
+ Mock mockSingletonManager = new Mock(MockBehavior.Strict, null, null, null, null, null, new FixedHostIdProvider(TestHostId), null);
mockSingletonManager.Setup(p => p.GetLockOwnerAsync(_attribute, _lockId, CancellationToken.None)).ReturnsAsync("someotherguy");
SingletonValueProvider localValueProvider = new SingletonValueProvider(_method, _attribute.ScopeId, TestInstanceId, _attribute, mockSingletonManager.Object);
SingletonLock localSingletonLock = (SingletonLock)(await localValueProvider.GetValueAsync());
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/WebJobs.Host.UnitTests.csproj b/test/Microsoft.Azure.WebJobs.Host.UnitTests/WebJobs.Host.UnitTests.csproj
index 720b325f..075b3d3a 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/WebJobs.Host.UnitTests.csproj
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/WebJobs.Host.UnitTests.csproj
@@ -54,6 +54,10 @@
..\..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll
True
+
+ ..\..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll
+ True
+
..\..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll
True
@@ -70,6 +74,22 @@
..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
+
+ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
+ True
+
+
+ ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
+ True
+
+
+ ..\..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
+ True
+
..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll
True
@@ -87,17 +107,84 @@
True
+
+ ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll
+ True
+
3.5
+
+ ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
+ True
+
+
+ ..\..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
+ True
+
+
+ ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+ True
+
+
+
+ ..\..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Net.Http.4.3.1\lib\net46\System.Net.Http.dll
+ True
+
+
+ ..\..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
+ True
+
+
+ ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll
+ True
+
..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
+
+
+ ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
+ True
+
..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
True
@@ -155,6 +242,7 @@
+
@@ -197,6 +285,12 @@
+
+
+
+
+
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/app.config b/test/Microsoft.Azure.WebJobs.Host.UnitTests/app.config
index a7388136..edadf40e 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/app.config
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/app.config
@@ -1,37 +1,45 @@
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/packages.config b/test/Microsoft.Azure.WebJobs.Host.UnitTests/packages.config
index 3de92605..11198f2e 100644
--- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/packages.config
+++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/packages.config
@@ -1,18 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.Azure.WebJobs.ServiceBus.UnitTests/app.config b/test/Microsoft.Azure.WebJobs.ServiceBus.UnitTests/app.config
index 001d0e4e..1c956112 100644
--- a/test/Microsoft.Azure.WebJobs.ServiceBus.UnitTests/app.config
+++ b/test/Microsoft.Azure.WebJobs.ServiceBus.UnitTests/app.config
@@ -1,37 +1,41 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
@@ -39,26 +43,26 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+