Permalink
Browse files

Logging compilation errors in invocation path

  • Loading branch information...
fabiocav committed Dec 10, 2016
1 parent 6cf25e0 commit e00d42c40d093ef93edee775c1c6396e780f6ce0
@@ -129,10 +129,10 @@ private void ReloadScript()
}
catch (CompilationErrorException exc)
{
compilationResult = compilationResult.AddRange(exc.Diagnostics);
compilationResult = AddFunctionDiagnostics(compilationResult.AddRange(exc.Diagnostics));
}
TraceCompilationDiagnostics(compilationResult);
TraceCompilationDiagnostics(compilationResult, LogTargets.User);
bool compilationSucceeded = !compilationResult.Any(d => d.Severity == DiagnosticSeverity.Error);
@@ -150,7 +150,19 @@ private void ReloadScript()
}
}
internal Task<MethodInfo> GetFunctionTargetAsync() => _functionLoader.GetFunctionTargetAsync();
internal async Task<MethodInfo> GetFunctionTargetAsync()
{
try
{
return await _functionLoader.GetFunctionTargetAsync().ConfigureAwait(false);
}
catch (CompilationErrorException exc)
{
TraceOnPrimaryHost("Function compilation error", TraceLevel.Error);
TraceCompilationDiagnostics(exc.Diagnostics, LogTargets.User);
throw;
}
}
private void RestorePackages()
{
@@ -186,7 +198,8 @@ protected override async Task InvokeCore(object[] parameters, FunctionInvocation
{
// Separate system parameters from the actual method parameters
object[] originalParameters = parameters;
MethodInfo function = await _functionLoader.GetFunctionTargetAsync();
MethodInfo function = await GetFunctionTargetAsync();
int actualParameterCount = function.GetParameters().Length;
object[] systemParameters = parameters.Skip(actualParameterCount).ToArray();
parameters = parameters.Take(actualParameterCount).ToArray();
@@ -261,11 +274,14 @@ private async Task<MethodInfo> CreateFunctionTarget(CancellationToken cancellati
return _functionEntryPointResolver.GetFunctionEntryPoint(scriptType.DeclaredMethods.ToList());
}
}
catch (CompilationErrorException ex)
catch (CompilationErrorException exc)
{
TraceOnPrimaryHost("Function compilation error", TraceLevel.Error);
TraceCompilationDiagnostics(ex.Diagnostics);
throw;
ImmutableArray<Diagnostic> diagnostics = AddFunctionDiagnostics(exc.Diagnostics);
// Here we only need to trace to system logs
TraceCompilationDiagnostics(diagnostics, LogTargets.System);
throw new CompilationErrorException(exc.Message, diagnostics);
}
}
@@ -290,19 +306,48 @@ private async Task VerifyPackageReferencesAsync()
}
}
private void TraceCompilationDiagnostics(ImmutableArray<Diagnostic> diagnostics)
internal void TraceCompilationDiagnostics(ImmutableArray<Diagnostic> diagnostics, LogTargets logTarget = LogTargets.All)
{
if (logTarget == LogTargets.None)
{
return;
}
TraceWriter traceWriter = TraceWriter;
IDictionary<string, object> properties = PrimaryHostTraceProperties;
if (!logTarget.HasFlag(LogTargets.User))
{
traceWriter = Host.TraceWriter;
properties = PrimaryHostSystemTraceProperties;
}
else if (!logTarget.HasFlag(LogTargets.System))
{
properties = PrimaryHostUserTraceProperties;
}
foreach (var diagnostic in diagnostics.Where(d => !d.IsSuppressed))
{
TraceWriter.Trace(diagnostic.ToString(), diagnostic.Severity.ToTraceLevel(), PrimaryHostTraceProperties);
traceWriter.Trace(diagnostic.ToString(), diagnostic.Severity.ToTraceLevel(), properties);
}
}
ImmutableArray<Diagnostic> scriptDiagnostics = GetFunctionDiagnostics(diagnostic);
private ImmutableArray<Diagnostic> AddFunctionDiagnostics(ImmutableArray<Diagnostic> diagnostics)
{
var result = diagnostics.Aggregate(new List<Diagnostic>(), (a, d) =>
{
a.Add(d);
ImmutableArray<Diagnostic> functionsDiagnostics = GetFunctionDiagnostics(d);
if (!scriptDiagnostics.IsEmpty)
if (!functionsDiagnostics.IsEmpty)
{
TraceCompilationDiagnostics(scriptDiagnostics);
a.AddRange(functionsDiagnostics);
}
}
return a;
});
return ImmutableArray.CreateRange(result);
}
private ImmutableArray<Diagnostic> GetFunctionDiagnostics(Diagnostic diagnostic)
@@ -434,5 +479,14 @@ protected override void Dispose(bool disposing)
return processor ?? ((_, __, ___, ____) => { /*noop*/ });
}
[Flags]
internal enum LogTargets
{
None = 0,
System = 1,
User = 2,
All = System | User
}
}
}
@@ -45,14 +45,16 @@ internal FunctionInvokerBase(ScriptHost host, FunctionMetadata functionMetadata,
{ ScriptConstants.TracePropertyFunctionNameKey, Metadata.Name }
};
TraceWriter = TraceWriter.Apply(functionTraceProperties);
PrimaryHostTraceProperties = new Dictionary<string, object>
{
{ ScriptConstants.TracePropertyPrimaryHostKey, true }
};
}
protected IDictionary<string, object> PrimaryHostTraceProperties { get; }
protected static IDictionary<string, object> PrimaryHostTraceProperties { get; }
= new ReadOnlyDictionary<string, object>(new Dictionary<string, object> { { ScriptConstants.TracePropertyPrimaryHostKey, true } });
protected static IDictionary<string, object> PrimaryHostUserTraceProperties { get; }
= new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(PrimaryHostTraceProperties) { { ScriptConstants.TracePropertyIsUserTraceKey, true } });
protected static IDictionary<string, object> PrimaryHostSystemTraceProperties { get; }
= new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(PrimaryHostTraceProperties) { { ScriptConstants.TracePropertyIsSystemTraceKey, true } });
public ScriptHost Host { get; }
@@ -14,7 +14,7 @@ internal sealed class FunctionLoader<T> : IDisposable
private readonly ReaderWriterLockSlim _functionValueLoaderLock = new ReaderWriterLockSlim();
private readonly Func<CancellationToken, Task<T>> _valueFactory;
private FunctionValueLoader<T> _functionValueLoader;
private bool disposed = false;
private bool _disposed = false;
public FunctionLoader(Func<CancellationToken, Task<T>> valueFactory)
{
@@ -69,10 +69,10 @@ public async Task<T> GetFunctionTargetAsync(int attemptCount = 0)
void Dispose(bool disposing)
{
if (!disposed && disposing)
if (!_disposed && disposing)
{
_functionValueLoader.Dispose();
disposed = true;
_disposed = true;
}
}
@@ -54,6 +54,7 @@ protected ScriptHost(ScriptHostConfiguration scriptConfig)
ScriptConfig = scriptConfig;
FunctionErrors = new Dictionary<string, Collection<string>>(StringComparer.OrdinalIgnoreCase);
NodeFunctionInvoker.UnhandledException += OnUnhandledException;
TraceWriter = ScriptConfig.TraceWriter;
}
public static readonly string Version = GetAssemblyFileVersion(typeof(ScriptHost).Assembly);
@@ -236,7 +237,6 @@ protected virtual void Initialize()
.Subscribe(HandleHostError);
ScriptConfig.HostConfig.Tracing.Tracers.Add(traceMonitor);
TraceWriter = ScriptConfig.TraceWriter;
TraceLevel hostTraceLevel = ScriptConfig.HostConfig.Tracing.ConsoleLevel;
if (ScriptConfig.FileLoggingMode != FileLoggingMode.Never)
{
@@ -26,7 +26,7 @@ public class DotNetFunctionInvokerTests
public async Task ReloadScript_WithInvalidCompilationAndMissingMethod_ReportsResults()
{
// Create the compilation exception we expect to throw during the reload
var descriptor = new DiagnosticDescriptor(DotNetConstants.MissingFunctionEntryPointCompilationCode,
var descriptor = new DiagnosticDescriptor(DotNetConstants.MissingFunctionEntryPointCompilationCode,
"Test compilation exception", "Test compilation error", "AzureFunctions", DiagnosticSeverity.Error, true);
var exception = new CompilationErrorException("Test compilation exception", ImmutableArray.Create(Diagnostic.Create(descriptor, Location.None)));
@@ -52,17 +52,25 @@ public async Task ReloadScript_WithInvalidCompilationAndMissingMethod_ReportsRes
metadata.Bindings.Add(new BindingMetadata() { Name = "Test", Type = "ManualTrigger" });
var invoker = new DotNetFunctionInvoker(dependencies.Host.Object, metadata, new Collection<Script.Binding.FunctionBinding>(),
new Collection<Script.Binding.FunctionBinding>(), dependencies.EntrypointResolver.Object, new FunctionAssemblyLoader(string.Empty),
new Collection<FunctionBinding>(), dependencies.EntrypointResolver.Object, new FunctionAssemblyLoader(string.Empty),
dependencies.CompilationServiceFactory.Object, dependencies.TraceWriterFactory.Object);
// Update the file to trigger a reload
File.WriteAllText(filePath, string.Empty);
await TestHelpers.Await(() =>
{
return dependencies.TraceWriter.Traces.Any(t => t.Message.Contains("Compilation failed.")) &&
dependencies.TraceWriter.Traces.Any(t => t.Message.Contains(DotNetConstants.MissingFunctionEntryPointCompilationCode));
});
dependencies.TraceWriter.Traces.Clear();
CompilationErrorException resultException = await Assert.ThrowsAsync<CompilationErrorException>(() => invoker.GetFunctionTargetAsync());
await TestHelpers.Await(() =>
{
return dependencies.TraceWriter.Traces.Any(t => t.Message.Contains("Compilation failed.")) &&
return dependencies.TraceWriter.Traces.Any(t => t.Message.Contains("Function compilation error")) &&
dependencies.TraceWriter.Traces.Any(t => t.Message.Contains(DotNetConstants.MissingFunctionEntryPointCompilationCode));
});
}
@@ -73,10 +81,10 @@ public async Task Compilation_WithMissingBindingArguments_LogsAF004Warning()
// Create the compilation exception we expect to throw during the reload
string rootFunctionsFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(rootFunctionsFolder);
// Create the invoker dependencies and setup the appropriate method to throw the exception
RunDependencies dependencies = CreateDependencies();
// Create a dummy file to represent our function
string filePath = Path.Combine(rootFunctionsFolder, Guid.NewGuid().ToString() + ".csx");
File.WriteAllText(filePath, Resources.TestFunctionWithMissingBindingArgumentsCode);
@@ -91,7 +99,7 @@ public async Task Compilation_WithMissingBindingArguments_LogsAF004Warning()
metadata.Bindings.Add(new BindingMetadata() { Name = "myQueueItem", Type = "ManualTrigger" });
var testBinding = new Mock<FunctionBinding>(null, new BindingMetadata() { Name = "TestBinding", Type = "blob" }, FileAccess.Write);
var invoker = new DotNetFunctionInvoker(dependencies.Host.Object, metadata, new Collection<FunctionBinding>(),
new Collection<FunctionBinding> { testBinding.Object }, new FunctionEntryPointResolver(), new FunctionAssemblyLoader(string.Empty),
new DotNetCompilationServiceFactory(), dependencies.TraceWriterFactory.Object);
@@ -111,7 +119,7 @@ public async Task Compilation_OnSecondaryHost_SuppressesLogs()
// Create the invoker dependencies and setup the appropriate method to throw the exception
RunDependencies dependencies = CreateDependencies();
// Set the host to secondary
dependencies.Host.SetupGet(h => h.IsPrimary).Returns(false);
@@ -178,6 +186,7 @@ private RunDependencies CreateDependencies(TraceLevel traceLevel = TraceLevel.In
{
var dependencies = new RunDependencies();
var functionTraceWriter = new TestTraceWriter(System.Diagnostics.TraceLevel.Verbose);
var traceWriter = new TestTraceWriter(System.Diagnostics.TraceLevel.Verbose);
var scriptHostConfiguration = new ScriptHostConfiguration
{
@@ -210,7 +219,7 @@ private RunDependencies CreateDependencies(TraceLevel traceLevel = TraceLevel.In
var traceWriterFactory = new Mock<ITraceWriterFactory>();
traceWriterFactory.Setup(f => f.Create())
.Returns(traceWriter);
.Returns(functionTraceWriter);
var metricsLogger = new MetricsLogger();
scriptHostConfiguration.HostConfig.AddService<IMetricsLogger>(metricsLogger);
@@ -223,7 +232,7 @@ private RunDependencies CreateDependencies(TraceLevel traceLevel = TraceLevel.In
CompilationService = compilationService,
CompilationServiceFactory = compilationServiceFactory,
TraceWriterFactory = traceWriterFactory,
TraceWriter = traceWriter
TraceWriter = functionTraceWriter
};
}

0 comments on commit e00d42c

Please sign in to comment.