Tracy debugger bar in AJAX-enabled sites

4 years ago

danik
Member | 56
+
+2
-

Hi,

as all of web is AJAX these days (or at least it should be!), it would be kind of nice to support updating the Tracy Debugger bar on AJAX requests, as well as full page loads. I've been thinking about the options of doing this and came up with a solution akin to FireLogger's way of channeling arbitrary data through custom HTTP headers. You can get the code over here. This is just to share an idea, it is in no way a complete project; there are some things that could be done better and ideally this should be built into Tracy itself (the way it is now I have to do a couple of ugly “hacks” to force Tracy into thinking it's a regular request and that it should render the bar – I could render it myself, but I would then need to perform the same ugly hacks in reverse to disable rendering the bar a second time during regular requests).

If I get around to doing it, I might rewrite this as a pull request for Tracy, I just wanted to demonstrate that it is simple enough to do.

4 years ago

Vojtěch Dobeš
Member | 1317
+
+2
-

You might consider adding following snippet of code to your repository, as it would make adoption of this easier and more unified by nette.ajax.js users:

$.nette.ext('tracy-bar-ajax', {
    success: function(payload, textStatus, xhr) {
        updateDebugger(xhr.getAllResponseHeaders());
    }
});

But I get you may want to keep this more as Proof-of-Concept :).

4 years ago

enumag
Member | 2128
+
0
-

Wow that's some nasty hacking of Tracy! :-D

Maybe you should first send a PR that would simplify the integration for you? Anyway good work!

4 years ago

danik
Member | 56
+
0
-

@vojtech.dobes maybe, thanks.. on the other hand, as I wrote it, you can adopt it for any framework you use, both on the server side and the client side (I'm currently using this in a Zend Framework project) (yes, I know I will go to hell, but believe me, after having to work in Zend Framework, somehow I'm not scared of hell anymore..). Plus, I'm still hoping that one day I'll finish my own JS framework specifically designed for Nette projects, so why support the competition, right? ;-)

@enumag yes, I was feeling a bit sick myself, writing stuff to $_SERVER etc.. but in a way, this was more or less just a proof-of-concept as Vojtech noticed; like I said, I think this not only could, but should be built into Tracy, and I'll probably get around doing it myself some time after Christmas – but it never hurts to share ideas before actually implementing them and see if anyone has anything to add :-)

4 years ago

enumag
Member | 2128
+
0
-

@danik There is not much to add since it is this hacky. But I think you can expect some discussion over the pull request.

4 years ago

danik
Member | 56
+
0
-

@enumag of course it couldn't be based on hacks – if, or more precisely, when I get to rewriting this the right way, it will require some changes to Tracy\Debugger and Tracy\Bar that will enable this feature without the ugly hacks and then there won't be any need to hack stuff ;-) I was thinking more about what the community has to say about the feature itself. For instance, as it is now, each request replaces the existing bar entirely, but I'm thinking a nicer way to do it would be to add to the existing bar, so that you still have access to data from the previous request, much as you do when performing a redirect. It would have to be done in a way that doesn't expand the bar indefinitely, because it'd easily get in the way then if it grew too large. But I think with some basic JS we can do this.

Another thing I'm considering is that maybe we don't need to transfer the whole source code for the bar as we do now; we could surely omit the JS wrapping since it's been already loaded by the time you get to do any AJAX requests, and maybe we can think of other ways to save bandwidth – not because of bandwidth costs, but because the amount of data transfered in HTTP headers as it is now might pose some compatibility issues with browsers that we don't yet know about. Another thing is that it would be nice to have a means of enabling / disabling this feature from the client side as well as the server side, for instance by including or not including an arbitrary header in the request – again, much the same as FireLogger does.

Yet another nice addition would be a way to support iframes. There are two use cases I now know about: one is iframe-based file uploads (for example the JS framework I mentioned uses a hidden iframe for uploads when AJAX uploads are not supported by the browser) and another is loading something in a lightbox. It'd be nice to have just one bar per browser window, which would give us access to all the relevant data.

And what I was hoping to trigger by posting this crude example is more discussion on features like this :-) the server-side implementation is going to be pretty straightforward anyway ;-)

4 years ago

danik
Member | 56
+
0
-

Last, but not least, I'm trying to get someone else interested in this enough to consider helping out a bit – I'm not very good with writing tests for stuff, so if I manage to make this into a PR, it'd be great if somebody would pop in to write the tests for me ;-)

4 years ago

enumag
Member | 2128
+
0
-

@danik There is an old proof of concept commit here: https://github.com/…04f2e6676aad It adds the ajax bars to the regular one similar way as bars of redirect requests are currently handled.

4 years ago

danik
Member | 56
+
0
-

There you go: https://github.com/…tracy/pull/1

.. this is my first PR ever, so please bear with me if I forgot something I should've done / done something I shouldn't have / left some trailing whitespace behind or whatever ;-)

4 years ago

enumag
Member | 2128
+
0
-

@danik You sent the PR to your own repository, not to official tracy repository.

I wasn't able to get this working yet. In my case there are no Tracy headers in ajax response. The reason is that headers were already sent when the shotdown handler is called. It would probably be better to store the debug bar content on the server and then load it with a second ajax request or as <script src="...">. Actually that's how the old implementation from @DavidGrudl works.

4 years ago

danik
Member | 56
+
0
-

@enumag ooops, too bad.. like I said, it's my first ever pull request ;-)

I don't much like the idea of storing the bar's output in a file and then loading it with a separate request. At the very least it would have to include some kind of garbage-collection, because we can never be sure the separate Debugger request will actually arrive. Plus it doesn't seem semantically correct from my point of view: the debugger relates to the current request, but we would need another request to actually do any debugging. It is a route well worth considering though, since it would overcome one issue I've had with the header-based approach, and that is browser-imposed limits on browser length. As these things do, these limits vary with each browser and they do vary quite wildly (see here). And if the headers are too big for the browser, it discards the whole response. Happens easily enough with Webkit based browsers, for instance.

For the time being though, I've updated my original PR a bit. Try it out and check out the example – I've updated the API a bit and added a Bar->enable() method which adds an additional output buffering level that should solve the issue you are seeing.

4 years ago

enumag
Member | 2128
+
0
-

I didn't know about the headers limits until now. Well with that limitation I think your implementation would never get merged into tracy. Or at least I would be against it.

Of course you're right about the garbage collection, but you can make a proof of concept PR without it first and then discuss with others on GH how to do the garbage collection or it it's actually needed at all. Another thing to consider is storing the data in session (that might solve the issue).

If you decide to try the approach I suggested, you might wanna look to this RFC as well.

EDIT: I have something to say about the output buffering as well. It might not always be save to use – the application might accidently call ob_end_*()/ob_flush() or something and end your output buffer by accident. Also it would cause a situation where all ajax requests would be buffered in development mode but not in production mode. If the application were to manipulate with headers when something already was sent to the output, it would work in development mode (well maybe with some warning but I don't wanna rely on that) but fail on production. Such differenct in dev/production behaviour might prove fatal.

Last edited by enumag (2015-01-03 23:04)

4 years ago

danik
Member | 56
+
0
-

That RFC looks really great! If this is formalised and made into a generic transport mechanism between the client-side instance of Tracy and the server-side code, it could be used for arbitrary async widgets as well as updating Tracy on regular AJAX requests. I'll look into it. Thanks!

EDIT: As it is implemented now, if you explicitly call Bar->enable(), output buffering will be started both in development mode and in production, whereas if you don't call Bar->enable(), no additional output buffering takes place and the Bar will just silently do nothing on AJAX requests if headers have already been sent by the time it is supposed to render. But I see your point of course.. Thanks for pointing this out!

Last edited by danik (2015-01-03 23:53)