33

I'm looking for ideas on how to make RX more easily debuggable. It can be so very difficult to find the point of failure when a source is passed through combiners and throttles and publishes and such.

So far I've been doing similar things to what I do with complicated Enumerable chains - inserting Do() for tracing, adding a "name" field to an anonymous type part way through, grabbing stack traces sometimes.. But we have perhaps hundreds of producers and maybe thousands of consumers in our system and it's getting very hard to isolate problems.

What kinds of tricks do you have for debugging your RX usage?

scobi
  • 14,252
  • 13
  • 80
  • 114
  • 2
    Great question. I'm always struggling to debug my Rx code. – Tim Nov 22 '11 at 10:01
  • 8
    I strongly disagree with this question being casually closed by casperOne. I am looking for expertise in a very specific technology. This question will *not* likely result in "opinion, debate, arguments, polling, or extended discussion". – scobi Nov 29 '11 at 17:48
  • 2
    Not constructive? Maybe the question is not quite specific enough, but it's a highly valuable question. Oh well, back to the "why doesn't this code compile" questions. – Niall Connaughton Feb 03 '12 at 10:09
  • 1
    While this question may lead to some discussion, it seems like a reasonable question. The asker mentions specific debugging techniques that he tried and outlines why they aren't sufficient in specific circumstances. One would hope answers to this question would enumerate additional proven debugging techniques, wich would benefit readers of this site. Voting to re-open. @Scott Bilas: I think it would improve the question if you could clarify why in this your specific set of circumstances the techniques already tried make it "very hard to isolate problems". – njuffa Jun 20 '14 at 22:47
  • 2
    I am so glad that the voice of reason prevailed and the question was re-opened. – Tim Long May 30 '15 at 13:58

2 Answers2

13

I think a constructive discussion on this topic has been had on the Rx Forums in 2009.

Instead of adding adhoc Do operators into your queries, you would add a custom Log/Trace operator. This operator would capture the Subscription, Disposal, OnNext, OnError and OnCompleted events. Depending on your implementation it could just write to the console, use your favorite Logger library or even create ETW events for OS and Visual Studio integration.

public static class ObservableTrace
{
    public static IObservable<TSource> Trace<TSource>(this IObservable<TSource> source, string name)
    {
        int id = 0;
        return Observable.Create<TSource>(observer => 
        {
            int id1 = ++id;
            Action<string, object> trace = (m, v) => Debug.WriteLine("{0}{1}: {2}({3})", name, id1, m, v);
            trace("Subscribe", "");
            IDisposable disposable = source.Subscribe(
                v => { trace("OnNext", v); observer.OnNext(v); },
                e => { trace("OnError", ""); observer.OnError(e); },
                () => { trace("OnCompleted", ""); observer.OnCompleted(); });
            return () => { trace("Dispose", ""); disposable.Dispose(); };
        });
    }
}
Lee Campbell
  • 10,631
  • 1
  • 34
  • 29
  • Thank you so much for sharing this. Create is powerful. I want to trace an observable only for a specific subscription. My sample case would be that I have multiple IObservable properties in a class. Each of them uses another one and apply a Where condition. `IObservable<> A => root; IObservable<> B => A.Where(); IObservable<> C => B.Where();` Let's say I am trying to understand why my subscription on C is not hit as I expected. If I use Trace on A, it will be hit for other subscriptions as well, this would create too much noise. Would there be any solution for this? – Alperen Belgiç Nov 10 '20 at 20:49
  • 1
    I would just put the Trace right next to the subscription. e.g. myInstance.C.Trace("It is only me tracing C").Subscribe(o=>Console.Write("OnNext{0}"); – Lee Campbell Nov 16 '20 at 09:14
  • Thank you Lee (also thank you so much for your great book). This one only traces values that could pass through the filtering of A and B. If I expect a value to reach down to subscription, and it doesn't reach, it would be good to trace what those parent observables are filtering. I tried to find some solutions but each one of them requires some sort of state management. Then the elegance of chaining observables as declarations disappears. But what I want requires keeping state by nature I am afraid. I am just trying to make sure that I am not missing a point. – Alperen Belgiç Nov 21 '20 at 02:23
  • 1
    I am not entiriely sure what the problem here is, but I think you want to know when a filtered sequence is missing some data. First, I think this is probably best solved with a unit-test. However it you are going the "logging" or "tracing" route, then maybe ``` IObservable<> A => root; IObservable<> B => A.Where().Trace(...); IObservable<> C => B.Where().Trace(...); ``` But this will just show what _is_ coming from B and C, not what was filtered out. – Lee Campbell Nov 24 '20 at 08:34
12

One important trick for catching Rx bugs is to retry debugging with first-chance exceptions enabled, this makes it way more likely that you'll get a real exception message instead of a rethrown one:

Community
  • 1
  • 1
Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I'm always surprised when people don't run with exceptions set to catch first-chance. It's like flying blind. – scobi Nov 22 '11 at 19:20
  • Rx are often time-dependent so stepping through is often not an option. Traces are used in time-sensitive debugging. – Vadym Chekan Jan 08 '14 at 04:25
  • Clarifying, this screen is available in Debug > Exceptions (not in Options). https://msdn.microsoft.com/en-us/library/d14azbfh.aspx – Jeson Martajaya Jun 19 '15 at 23:16