vlaaad / reveal

Read Eval Visualize Loop for Clojure

Home Page:https://vlaaad.github.io/reveal/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

log support

xificurC opened this issue · comments

Hi, thanks for this interesting library! Before diving into the code or looking for fixes I'd like to get an understanding of reveal's scope. I develop in emacs with cider so I'm using the nrepl middleware route to start up reveal. This leaves me with 2 places to look at output, the emacs nrepl buffer and reveal's window. Values show up in both just fine but the logs get only routed to the nrepl buffer. There was 1 time when the logs actually went into reveal as well, so I'm not quite sure what is considered "correct" behavior. I'm using timbre for logging and routing slf4j logs into it.

Is reveal's purpose to be a general REPL replacement? If so then I'd like to get the logs working. Otherwise I need to keep the nrepl buffer around as well. As for my personal preference - I'd like to have just one around, I don't type into the REPL anyway.

Hi!

You stumbled upon a hard problem, and I don't have a solution for it yet. I certainly would want to use only one output, and it's unfortunate that not every print statement is automatically routed into Reveal. The problem is — Clojure REPLs implement print output forking by re-binding *out* var during code evaluation, so every println/prn statement in the thread that performs evaluation will use a dynamically set printer. If the printing happens on the other thread, AND if thread-bound variables were not transferred by Clojure to that thread (which happens via bound-fn in future etc.), then the printed text will go into a root printer in *out*, which redirects to System.out. In addition to that, various logging systems (don't know about timbre) might just use System.out instead of *out*.

So, to make Reveal show those logs, it needs to:

  • fork System.out to also redirect to Reveal window;
  • (probably) rebind the root of *out* so it also redirects to Reveal window;

Unanswered questions:

  • What if some other program wants to re-bind System.out/*out* too? For example, we might want to run 2 Reveal REPLs in the same process;
  • Should it be opt-in or opt-out? When? This forking does not make sense if you are using remote-prepl, for example;
  • Should it fork System.err/*err* too? (.printStackTrace ex) prints there.

I'd like to solve it at some point, but currently other stuff has higher priority. If you are willing to spend some time thinking about it and propose a solution, I would love to hear it!

Btw I would guess as a workaround you can create a custom logging sink that does tap> on the log messages. That might be much more useful and inspectable: you will have log messages as objects, not strings.

You seem to have thought of every dark corner of this issue, which is nice! For starters I'd concentrate on the simpler parts though :) As you noted each logging framework should allow for defining appenders. So leaving a recipe in the readme/wiki on how to set these up would be a reasonable start. What do you think?

Regarding tap>ping objects - that's a nice bonus, although some might prefer logs to just be text (the window could get flooded a bit too much I fear). I guess both can be configured easily if you have a stream where prints can be sent?

The second basic thing to get working is println and friends, which seems to work in the cider/nrepl world.

You can combine tap with custom formatting (rx/as) to both show logs as text and retain references to log message objects.

I managed to tap> the timbre logs but can't figure out how to combine tap> with rx/as to get the behavior you're describing. Care to give a hint? The custom format in the reveal window would be done by (comp force :output_)

By combining I meant using (tap> (rx/stream-as-is (rx/as my-log-entry (rx/raw-string ...)))). There is a link in my previous comment that explains what as is. There is also documentation in code.

Thanks, I managed to persuade timbre to write some values out through tap> this way. Would you like to get this information documented somewhere? Readme or wiki? I'll gladly whip up a PR.

I read the documentation you linked but had a hard time understanding all the concepts. There's no explanation what a stream is, how does a streaming function look like or how do streams relate to reveal. At the very least a complete example like you just provided would help to get to working code. The identity-hash-code-sf example doesn't show how would it be actually used with reveal. I think this is an example of the curse of knowledge since otherwise the documentation is very thorough and easy to read!

Heh, I've been thinking about the curse of knowledge for some time (without knowing the term until today — thanks!) It would be nice if it was easier to transfer knowledge, but how can that knowledge transfer be enabled?.. I didn't want to go too much into the detail of what streaming functions are and how they are related to reducers/transducers in the readme, probably I'll write a blog post about them at one point. Yeah, the thing about identity-hash-code-sf is that it has to be used either inside formatter definition (i.e. defstream) or wrapped in streamed-as-is. I'm not super happy with it and I'd love it if I was able to avoid it, I'll think about that.

I think forwarding logging to Reveal would be a nice example in the examples folder.