exceptionless / Exceptionless.Net

Exceptionless clients for the .NET platform
https://exceptionless.com
Other
551 stars 142 forks source link

Problems with WinRT #312

Open Nuklon opened 7 months ago

Nuklon commented 7 months ago

This keeps getting logged, it's a problem with Exceptionless client when using WinRT (in .net core apps):

Exceptionless.Json.JsonSerializationException: Error getting value from 'GetReference_1' on 'ABI.WinRT.Interop.IRestrictedErrorInfo+Vftbl'. ---> InvalidOperationException: No coercion operator is defined between types 'System.Int32(System.IntPtr, System.IntPtr*)' and 'System.Object'.
at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(System.Linq.Expressions.ExpressionType coercionType, System.Linq.Expressions.Expression expression, System.Type convertToType) at offset 14
at Exceptionless.Json.Utilities.ExpressionReflectionDelegateFactory.EnsureCastExpression(System.Linq.Expressions.Expression expression, System.Type targetType, System.Boolean allowWidening) at offset 181
at Exceptionless.Json.Utilities.ExpressionReflectionDelegateFactory.CreateGet[T](System.Reflection.PropertyInfo propertyInfo) at offset 108
at Exceptionless.Json.Utilities.ReflectionDelegateFactory.CreateGet[T](System.Reflection.MemberInfo memberInfo) at offset 45
at Exceptionless.Json.Serialization.ExpressionValueProvider.GetValue(System.Object target) at offset 8
--- End of inner exception stack trace ---
at Exceptionless.Json.Serialization.ExpressionValueProvider.GetValue(System.Object target) at offset 84
at Exceptionless.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(Exceptionless.Json.JsonWriter writer, System.Object value, Exceptionless.Json.Serialization.JsonContainerContract contract, Exceptionless.Json.Serialization.JsonProperty member, Exceptionless.Json.Serialization.JsonProperty property, Exceptionless.Json.Serialization.JsonContract& memberContract, System.Object& memberValue) at offset 93
at Exceptionless.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(Exceptionless.Json.JsonWriter writer, System.Object value, Exceptionless.Json.Serialization.JsonObjectContract contract, Exceptionless.Json.Serialization.JsonProperty member, Exceptionless.Json.Serialization.JsonContainerContract collectionContract, Exceptionless.Json.Serialization.JsonProperty containerProperty) at offset 60

Another one:

InvalidOperationException: No coercion operator is defined between types 'System.Int32(System.IntPtr, System.IntPtr*)' and 'System.Object'.
at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(System.Linq.Expressions.ExpressionType coercionType, System.Linq.Expressions.Expression expression, System.Type convertToType) at offset 14
at Exceptionless.Json.Utilities.ExpressionReflectionDelegateFactory.EnsureCastExpression(System.Linq.Expressions.Expression expression, System.Type targetType, System.Boolean allowWidening) at offset 181
at Exceptionless.Json.Utilities.ExpressionReflectionDelegateFactory.CreateGet[T](System.Reflection.PropertyInfo propertyInfo) at offset 108
at Exceptionless.Json.Utilities.ReflectionDelegateFactory.CreateGet[T](System.Reflection.MemberInfo memberInfo) at offset 45
at Exceptionless.Json.Serialization.ExpressionValueProvider.GetValue(System.Object target) at offset 8
niemyjski commented 7 months ago

Are you submitting any custom data? When does this happen in the logs? We are just using JSON.NET via source import.

Nuklon commented 7 months ago

I do have some custom data, but it's not WinRT stuff. It's thrown when an exception is logged/submitted by Exceptionless related to WinRT (i.e., thrown by WinRT, typically a COMException).

   at Exceptionless.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
   at Exceptionless.Serializer.DefaultJsonSerializer.Serialize(Object model, String[] exclusions, Int32 maxDepth, Boolean continueOnSerializationError)
   at Exceptionless.Submission.DefaultSubmissionClient.PostEventsAsync(IEnumerable`1 events, ExceptionlessConfiguration config, IJsonSerializer serializer)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at Exceptionless.Submission.DefaultSubmissionClient.PostEventsAsync(IEnumerable`1 events, ExceptionlessConfiguration config, IJsonSerializer serializer)
   at Exceptionless.Queue.DefaultEventQueue.ProcessAsync()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at Exceptionless.Queue.DefaultEventQueue.ProcessAsync()
   at Exceptionless.Queue.DefaultEventQueue.OnProcessQueueAsync(Object state)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at Exceptionless.Queue.DefaultEventQueue.OnProcessQueueAsync(Object state)
niemyjski commented 7 months ago

Any chance you could put together a small sample project in a pr (in our samples folder) that can reproduce this. I can then debug this and make sure it's not broken going forward.

Nuklon commented 7 months ago

Maybe, the trick is getting a WinRT method to throw.

niemyjski commented 7 months ago

Were you able to find a method that throws?

Nuklon commented 7 months ago

Were you able to find a method that throws?

Maybe, need a bit more time.

niemyjski commented 6 months ago

Were you able to come up with an easy repo sample?

Nuklon commented 6 months ago

Took some time, but here it is. Create a WPF project in VS and target net8.0-windows10.0.22000.0 (necessary to get UWP types). Some of the code in ExceptionlessService is probably not necessary.


internal class ExceptionlessService(ExceptionlessClient exceptionlessClient)
{
    private bool _isDisposed;
    private long _queuedFirstChanceExceptions;

    public void Initialize()
    {
        try
        {
            ExceptionlessConfiguration configuration = exceptionlessClient.Configuration;
            configuration.ServerUrl = "YOUR_URL";
            configuration.ApiKey = "YOUR_API_KEY";

            configuration.Enabled = true;
            configuration.IncludePrivateInformation = true;
            configuration.IncludeIpAddress = false;
            configuration.UseSessions();

            AppDomain.CurrentDomain.UnhandledException += (_, args) => OnUnhandledException(args.ExceptionObject as Exception, "AppDomainUnhandledException");
            TaskScheduler.UnobservedTaskException += (_, args) => OnUnhandledException(args.Exception, "TaskSchedulerUnobservedTaskException");
            App.Current.DispatcherUnhandledException += (_, args) => OnUnhandledException(args.Exception, "ApplicationDispatcherUnhandledException");

            AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;

            if (configuration.SessionsEnabled)
                exceptionlessClient.SubmitSessionStart();

            exceptionlessClient.SubmittingEvent += OnSubmittingEvent;
        }
        catch
        { }
    }

    public void Dispose()
    {
        if (exceptionlessClient == null || _isDisposed)
            return;

        _isDisposed = true;

        // Wait until all first chance exceptions have been submitted.
        while (Interlocked.Read(ref _queuedFirstChanceExceptions) > 0)
            Thread.Sleep(1);

        exceptionlessClient.ProcessQueueAsync().GetAwaiter().GetResult();

        if (exceptionlessClient.Configuration.SessionsEnabled)
            exceptionlessClient.SubmitSessionEndAsync().GetAwaiter().GetResult();
    }

    private static void OnSubmittingEvent(object sender, EventSubmittingEventArgs eventArgs)
    {
        try
        {
            if (eventArgs is { Event: not null, IsUnhandledError: true })
                eventArgs.Event.Tags.Add("Unhandled");
        }
        catch
        { }
    }

    private static void OnUnhandledException(Exception exception, string submissionMethod)
    {
        if (exception == null)
            return;

        var contextData = new ContextData();

        contextData.MarkAsUnhandledError();
        contextData.SetSubmissionMethod(submissionMethod);

        EventBuilder eventBuilder = exception.ToExceptionless(contextData);
        eventBuilder.AddTags("Unhandled");
        eventBuilder.Submit();
    }

    private void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs eventArgs)
    {
        Exception exception = eventArgs.Exception;
        string stackTrace = new StackTrace(true).ToString();

        Interlocked.Increment(ref _queuedFirstChanceExceptions);
        Task.Run(() => SubmitFirstChanceException(exception, stackTrace));
    }

    private void SubmitFirstChanceException(Exception exception, string stackTrace)
    {
        try
        {
            EventBuilder eventBuilder = exception.ToExceptionless();

            eventBuilder.AddTags("First Chance");
            eventBuilder.AddObject(stackTrace, "Stack Trace");

            eventBuilder.Submit();
        }
        finally
        {
            Interlocked.Decrement(ref _queuedFirstChanceExceptions);
        }
    }
}

public partial class MainWindow : Window
{
        public WiFiDirectAdvertisementPublisher a;

        public MainWindow()
        {
            ExceptionlessService aa = new ExceptionlessService(ExceptionlessClient.Default);
            aa.Initialize();

            InitializeComponent();

            try
            {
                a = new WiFiDirectAdvertisementPublisher();
                a.Advertisement.LegacySettings.Ssid = "test";
                a.Advertisement.LegacySettings.Passphrase = new PasswordCredential("a", null, null); // Throws!
                a.Start();
            }
            catch (Exception e)
            {
                e.ToExceptionless().Submit();
                aa.Dispose();
            }
        }
}

Now, within Exceptionless, you'll see two exceptions logged. One is "The parameter is incorrect. Cannot create credential". This is the correct one. But you'll also get a second exception: "Error getting value from 'GetReference_1' on 'ABI.WinRT.Interop.IRestrictedErrorInfo+Vftbl'." which obviously doesn't make sense.

If the other exception doesn't show up on first start, run it a couple of times, it'll get there eventually. I've had better success by letting it run a few seconds.

On a side note, you might also see this (only rarely here, but it happens):

InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.Collections.NodeKeyValueCollection.NodeKeyValueEnumerator.MoveNext() at offset 29
at Exceptionless.ToErrorModelExtensions.ToErrorModelInternal(System.Exception exception, Exceptionless.ExceptionlessClient client, System.Boolean isInner) at offset 397
niemyjski commented 6 months ago

Thanks for the sample! I'll try and take a look into this hopefully today or tomorrow.

niemyjski commented 5 months ago

Sorry for the delay. This is expected as you are wiring up FirstChanceExceptions which catch everything that's already handled. I do think that maybe we should update the default error exclusions to filter out any error data property prefixed with __Restricted or Restricted (cc @ejsmith ) image image image image

Looking at your sample I see a few things that I wanted to call out.

  1. There is no reason I'm aware of that you couldn't call ExceptionlessClient.Default.Register() (register also wires up to RegisterApplicationDispatcherUnhandledExceptionHandler) to wire up to all the wpf handlers and cover WinRT scenarios. If that doesn't work which it did for me I'd still call ExceptionlessClient.Default.Startup() which handles session management for you. which will wire up to
    
    client.RegisterAppDomainUnhandledExceptionHandler();

// make sure that queued events are sent when the app exits client.RegisterOnProcessExitHandler(); client.RegisterTaskSchedulerUnobservedTaskExceptionHandler();


2.Instead of your dispose method call ExceptionlessClient.Default.ShutdownAsync() which does everything and more safely.
3. Be very careful of FirstChanceExceptions it can be misleading as it catches things that are already safely handled.

I'll leave this open until we get a response on ` __Restricted`... Or if you have any follow up
Nuklon commented 5 months ago
  1. I'm doing manual registering of events as I want to run some code when an unhandled exception occurs (I removed that from the above example). I previously checked IsUnhandledError in OnSubmittingEvent, but found this to be too unreliable/slow sometimes.
  2. Good idea, wasn't aware of that.
  3. Right, but it provides some useful information sometimes, I've hidden useless exceptions in Exceptionless dashboard ☺️
niemyjski commented 5 months ago
  1. If this is slow or unexpected please report with more information as we'd like to get it fixed. You can always do custom logic on unhandled in a plugin :)
Nuklon commented 5 months ago

It's been some time since I've written this around Exceptionless, perhaps I'll give the more default code another try someday ☺️

niemyjski commented 3 months ago

@Nuklon would you mind submitting a pr to update the default exclusions to filter out any error data property prefixed with __Restricted or Restricted (cc @ejsmith )

Nuklon commented 3 months ago

@Nuklon would you mind submitting a pr to update the default exclusions to filter out any error data property prefixed with __Restricted or Restricted (cc @ejsmith )

I have no idea where this is located, so you can probably make this change yourself faster :)