Browse Source

Tweaked logging of binding errors.

1. Fixed some tests to expect `BindingNotification`s to be returned on a broken binding chain.
2. Changed logging of `BindingNotification`s - log at `Warning` level (instead of `Error`) except when the binding chain is broken at the root, in which case log at `Information` level. Do this to prevent flooding the output window when initializing.
Steven Kirk 8 years ago
parent
commit
3eb5e0e200

+ 1 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFramework>netstandard1.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <RootNamespace>Avalonia</RootNamespace>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DebugSymbols>true</DebugSymbols>

+ 1 - 10
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)
                 {

+ 1 - 0
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
 {

+ 53 - 0
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);
+        }
+    }
+}

+ 1 - 8
src/Avalonia.Base/PriorityValue.cs

@@ -189,14 +189,7 @@ namespace Avalonia
         /// <param name="error">The binding error.</param>
         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);
         }
 
         /// <summary>

+ 0 - 23
src/Avalonia.Base/Utilities/ExceptionUtilities.cs

@@ -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;
-        }
-    }
-}

+ 6 - 4
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;
         }
     }

+ 1 - 1
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 &&

+ 32 - 9
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);
 

+ 1 - 1
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 &&