diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 95be67c98c..e82af4c834 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -2,6 +2,7 @@ netstandard1.1 false + Avalonia true diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1bdf1eb5e3..7b8da28f53 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -664,16 +664,7 @@ namespace Avalonia if (notification != null) { - if (notification.ErrorType == BindingErrorType.Error) - { - Logger.Error( - LogArea.Binding, - this, - "Error in binding to {Target}.{Property}: {Message}", - this, - property, - ExceptionUtilities.GetMessage(notification.Error)); - } + notification.LogIfError(this, property); if (notification.HasValue) { diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index ecaf59e174..3394bc4f1a 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.Logging; namespace Avalonia.Data { diff --git a/src/Avalonia.Base/Logging/LoggerExtensions.cs b/src/Avalonia.Base/Logging/LoggerExtensions.cs new file mode 100644 index 0000000000..24e44bf9de --- /dev/null +++ b/src/Avalonia.Base/Logging/LoggerExtensions.cs @@ -0,0 +1,53 @@ +using System; +using Avalonia.Data; + +namespace Avalonia.Logging +{ + internal static class LoggerExtensions + { + public static void LogIfError( + this BindingNotification notification, + object source, + AvaloniaProperty property) + { + if (notification.ErrorType == BindingErrorType.Error) + { + if (notification.Error is AggregateException aggregate) + { + foreach (var inner in aggregate.InnerExceptions) + { + LogError(source, property, inner); + } + } + else + { + LogError(source, property, notification.Error); + } + } + } + + private static void LogError(object source, AvaloniaProperty property, Exception e) + { + var level = LogEventLevel.Warning; + + if (e is BindingChainException b && + !string.IsNullOrEmpty(b.Expression) && + string.IsNullOrEmpty(b.ExpressionErrorPoint)) + { + // The error occurred at the root of the binding chain: it's possible that the + // DataContext isn't set up yet, so log at Information level instead of Warning + // to prevent spewing hundreds of errors. + level = LogEventLevel.Information; + } + + Logger.Log( + level, + LogArea.Binding, + source, + "Error in binding to {Target}.{Property}: {Message}", + source, + property, + e.Message); + } + } +} diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index c33d50ff0e..3726fb7ae5 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -189,14 +189,7 @@ namespace Avalonia /// The binding error. public void LevelError(PriorityLevel level, BindingNotification error) { - Logger.Log( - LogEventLevel.Error, - LogArea.Binding, - Owner, - "Error in binding to {Target}.{Property}: {Message}", - Owner, - Property, - error.Error.Message); + error.LogIfError(Owner, Property); } /// diff --git a/src/Avalonia.Base/Utilities/ExceptionUtilities.cs b/src/Avalonia.Base/Utilities/ExceptionUtilities.cs deleted file mode 100644 index fa8c5be788..0000000000 --- a/src/Avalonia.Base/Utilities/ExceptionUtilities.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Utilities -{ - internal static class ExceptionUtilities - { - public static string GetMessage(Exception e) - { - var aggregate = e as AggregateException; - - if (aggregate != null) - { - return string.Join(" | ", aggregate.InnerExceptions.Select(x => x.Message)); - } - - return e.Message; - } - } -} diff --git a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs b/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs index 51afe1ffbf..ddfcf531eb 100644 --- a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs +++ b/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs @@ -32,10 +32,12 @@ namespace Avalonia.Markup.Data public void Commit(string expression) { Expression = expression; - ExpressionErrorPoint = string.Join(".", _nodes.Reverse()) - .Replace(".!", "!") - .Replace(".[", "[") - .Replace(".^", "^"); + ExpressionErrorPoint = _nodes != null ? + string.Join(".", _nodes.Reverse()) + .Replace(".!", "!") + .Replace(".[", "[") + .Replace(".^", "^") : + string.Empty; _nodes = null; } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index ecb555252d..6f6a675b0e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -389,7 +389,7 @@ namespace Avalonia.Base.UnitTests LogCallback checkLogMessage = (level, area, src, mt, pv) => { - if (level == LogEventLevel.Error && + if (level == LogEventLevel.Warning && area == LogArea.Binding && mt == "Error in binding to {Target}.{Property}: {Message}" && pv.Length == 3 && diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index bdcd39d997..959dce7181 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -58,43 +58,63 @@ namespace Avalonia.Markup.UnitTests.Data } [Fact] - public async void Should_Return_UnsetValue_For_Root_Null() + public async void Should_Return_BindingNotification_Error_For_Root_Null() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(default(object), "Foo"); var result = await target.Take(1); - Assert.Equal(AvaloniaProperty.UnsetValue, result); + Assert.Equal( + new BindingNotification( + new MarkupBindingChainException("Null value", "Foo", string.Empty), + BindingErrorType.Error, + AvaloniaProperty.UnsetValue), + result); } [Fact] - public async void Should_Return_UnsetValue_For_Root_UnsetValue() + public async void Should_Return_BindingNotification_Error_For_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo"); var result = await target.Take(1); - Assert.Equal(AvaloniaProperty.UnsetValue, result); + Assert.Equal( + new BindingNotification( + new MarkupBindingChainException("Null value", "Foo", string.Empty), + BindingErrorType.Error, + AvaloniaProperty.UnsetValue), + result); } [Fact] - public async void Should_Return_UnsetValue_For_Observable_Root_Null() + public async void Should_Return_BindingNotification_Error_For_Observable_Root_Null() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(Observable.Return(default(object)), "Foo"); var result = await target.Take(1); - Assert.Equal(AvaloniaProperty.UnsetValue, result); + Assert.Equal( + new BindingNotification( + new MarkupBindingChainException("Null value", "Foo", string.Empty), + BindingErrorType.Error, + AvaloniaProperty.UnsetValue), + result); } [Fact] - public async void Should_Return_UnsetValue_For_Observable_Root_UnsetValue() + public async void Should_Return_BindingNotification_Error_For_Observable_Root_UnsetValue() { var data = new Class3 { Foo = "foo" }; var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo"); var result = await target.Take(1); - Assert.Equal(AvaloniaProperty.UnsetValue, result); + Assert.Equal( + new BindingNotification( + new MarkupBindingChainException("Null value", "Foo", string.Empty), + BindingErrorType.Error, + AvaloniaProperty.UnsetValue), + result); } [Fact] @@ -492,7 +512,10 @@ namespace Avalonia.Markup.UnitTests.Data { "foo", "bar", - AvaloniaProperty.UnsetValue, + new BindingNotification( + new MarkupBindingChainException("Null value", "Foo", string.Empty), + BindingErrorType.Error, + AvaloniaProperty.UnsetValue) }, result); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs index 0c2151850f..a508c21747 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs @@ -37,7 +37,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml LogCallback checkLogMessage = (level, area, src, mt, pv) => { - if (level == LogEventLevel.Error && + if (level == LogEventLevel.Warning && area == LogArea.Binding && mt == "Error in binding to {Target}.{Property}: {Message}" && pv.Length == 3 &&