When I see an exception reported, the type of the object says it's a PersistentArrayMap:
; eval (root-form): (type @user/p)
clojure.lang.PersistentArrayMap
Is there some way to get the actual exception here?Hmm, I kinda have it hooked up by just dumping all of that code into my project's user.clj, but it looks like unless I explicitly tap> something, it doesn't go to the "important" Portal window, but it does go to ** logging **. Is this the intended behaviour of your config?
Edit: re-read your blog post and saw:
> • one named *logging* that gets the output from the middleware (from regular evaluation of expressions) and the output from clojure.tools.logging (if present)
Which answers my question from above, if I understand correctly. Just to confirm, the only things that should be sent to the "important" Portal window are those which come from direct tap> calls in your code?
I also notice that the log message isn't showing up in the Portal viewer. Is this a bug in my setup code?
I haven't gone through the entire thread, but you can get access to the original exception from the datafied exception via (-> @user/p meta :obj)
> Just to confirm, the only things that should be sent to the "important" Portal window are those which come from direct tap> calls in your code?
Correct. Logging and regular (middleware) evaluation appear in the ** logging ** window and explicit tap>'s appear in the directory-labeled window.
> I also notice that the log message isn't showing up in the Portal viewer. Is this a bug in my setup code?
Hard to say. It looks like that c.t.l. debug call is in your user.clj file? Without seeing all your setup code, kinda hard to guess whether it is right...
@seancorfield yes, I basically just lifted that portion of your code directly into my user.clj. What's throwing me off is I except the log message (stdio) to appear as the "main text" (not sure if there's a technical term for this) of the viewer, rather than showing nil as it is in my screenshot there. Do you have that same behaviour, or do you see similar to "my error log" as in the top image here: https://cljdoc.org/d/djblue/portal/0.52.2/doc/guides/logging/timbre ?
Oh, I have no idea about Timbre. My code only deals with clojure.tools.logging. We use log4j2 under the hood.
And logging calls eval to nil so I would expect to see nil as the result.
Ah, yeah, that makes sense as the result. Do you not find it a little awkward to have to manually expand logging messages there to understand what's going on?
Not sure what you mean... The log messages show up in Portal much as they'd show up in the console or log file...
Here's a screenshot of my setup -- tap>'d values on top, logging/middleware output below:
The green log entries are INFO and they show the message and filename and line no.
That looks really good. Not what I'm seeing. I attached my user.clj above, in case anything jumps out at you. There's an RCF at the bottom that produces what you see in this screenshot.
And when that user.clj code runs, you see
Logging will be tap>'d...
Starting as the REPL... And you have two Portal windows open by that point too?
(and then you connect to that running REPL -- not jack-in -- right?)
Yep, it's in the other terminal window where I start everything up. Note that I do use Timbre, but I'm testing c.t.l and presuming there are no bad interactions with the rest of my stuff:
$ npx shadow-cljs server -A:dev:repl/conjure:shadow-cljs:test:dev.repl/logging:cards
shadow-cljs - config: /home/alex/repos/seqfind/shadow-cljs.edn
shadow-cljs - starting via "clojure"
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See for further details.
SLF4J: Class path contains SLF4J bindings targeting slf4j-api versions prior to 1.8.
SLF4J: Ignoring binding found at [jar:file:/home/alex/.m2/repository/com/fzakaria/slf4j-timbre/0.3.21/slf4j-timbre-0.3.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See for an explanation.
Logging will be tap>'d...
Starting nREPL Server with Portal, Notebook, CIDER as the REPL...
nREPL server started on port 36255 on host localhost -
> And you have two Portal windows open by that point too?
Yes, both Portal windows open after I run the above. They seem to work exactly as you would expect, but not the logging portion.
> (and then you connect to that running REPL -- not jack-in -- right?)
That's right. I connect to ; localhost:36255 (connected): .nrepl-port.I don't recognize that output -- how are you using Portal? I get actual exceptions reported in my setup?
@seancorfield the attached screenshot shows what I see. I expect that when I deref the Portal atom (`user/p`) and check out the type, it should be a PSQLException, but it shows me it's that PersistentArrayMap.
(type (Exception.))) ; java.lang.Exception
That ☝️ makes sense, and when I select it in Portal and deref the atom, I get java.lang.Exception as expected.Oh, because the exception has been run through Throwable->map by that point, so you'd need to navigate into the map to get at the :type keys
You could probably eval *e to get the original exception (depending on how your nREPL setup is configured). I have that bound to a hot key so I can easily see the raw exception.
Given that Portal is showing you the full details, why do you want to eval @user/p?
I want to try calling different methods of the exception instance.
Basically, Portal has datafy'd it so it can present the view you're seeing (and datafy on an exception calls Throwable->map).
Then *e is probably what you want...?
Yeah, that'll do it. I forgot that exists. Thank you for the reminder on that point. But out of curiosity, is it possible to reverse the datafying?
Not in general, no. Sometimes it's a lossy representation. Sometimes it can be reversed but it's specific to the transformation used to produce the data.
In this particular case, you probably couldn't reconstruct the original object because the PG-specific exception information is not present in the datafied result, only the general Exception stuff.
(and quite often the constructors you might need to rebuild the Java objects aren't public)
Isn't the exception information all in the second item of the :via vector in that screenshot?
Only the generic stuff. None of the PG-specific stuff.
I don't think it even includes the "generic" SQL-specific exception stuff. Just message/type/cause and stacktrace. Plus data if it's an ex-info.
Ah, I get what you mean. Like .getSQLState wouldn't work because that's pg-specific. That's what your talking about?
Well, .getSQLState() is "generic" SQL-specific I think but, yeah, that's not included.
You might get useful data from (bean *e) or use clojure.java.data's from-java on it to get more detail.
For example:
user=> (bean (ex-info "X" {}))
{:cause nil, :class clojure.lang.ExceptionInfo, :data {}, :localizedMessage "X", :message "X", :stackTrace #object["[Ljava.lang.StackTraceElement;" 0x3c818ac4 "[Ljava.lang.StackTraceElement;@3c818ac4"], :suppressed #object["[Ljava.lang.Throwable;" 0x5b69d40d "[Ljava.lang.Throwable;@5b69d40d"]}
user=> (map bean (:stackTrace *1))
({:classLoaderName nil, :fileName "NO_SOURCE_FILE", :moduleVersion nil, :nativeMethod false, :className "user$eval13", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 1, :methodName "invokeStatic"} {:classLoaderName nil, :fileName "NO_SOURCE_FILE", :moduleVersion nil, :nativeMethod false, :className "user$eval13", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 1, :methodName "invoke"} {:classLoaderName "app", :fileName "Compiler.java", :moduleVersion nil, :nativeMethod false, :className "clojure.lang.Compiler", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 7570, :methodName "eval"} {:classLoaderName "app", :fileName "Compiler.java", :moduleVersion nil, :nativeMethod false, :className "clojure.lang.Compiler", :moduleName nil, :cl...(you could use *e in that first call)
user=> (throw (ex-info "X" {}))
Execution error (ExceptionInfo) at user/eval17 (REPL:1).
X
user=> (bean *e)
{:cause nil, :class clojure.lang.ExceptionInfo, :data {}, :localizedMessage "X", :message "X", :stackTrace #object["[Ljava.lang.StackTraceElement;" 0xec50f54 "[Ljava.lang.StackTraceElement;@ec50f54"], :suppressed #object["[Ljava.lang.Throwable;" 0x5b69d40d "[Ljava.lang.Throwable;@5b69d40d"]}
user=> (map bean (:stackTrace *1))
({:classLoaderName nil, :fileName "NO_SOURCE_FILE", :moduleVersion nil, :nativeMethod false, :className "user$eval17", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 1, :methodName "invokeStatic"} {:classLoaderName nil, :fileName "NO_SOURCE_FILE", :moduleVersion nil, :nativeMethod false, :className "user$eval17", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 1, :methodName "invoke"} {:classLoaderName "app", :fileName "Compiler.java", :moduleVersion nil, :nativeMethod false, :className "clojure.lang.Compiler", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 7570, :methodName "eval"} {:classLoaderName "app", :fileName "Compiler.java", :moduleVersion nil, :nativeMethod false, :className "clojure.lang.Compiler", :moduleName nil, :class java.lang.StackTraceElement, :lineNumber 7525, :methodName "eval"} {:classLoaderName "app", :fileName "core.clj", :moduleVersion ...Good stuff, @seancorfield. Thank you for adding the bean usage examples. Last night I also read your blog post about the workflow you use. I'm going to borrow some of your hotkey ideas from dot-clojure and I'd really adapt the two-Portals solution you use for "important" + logging Portal windows. I think it's a little more involved for me to get that part going since I use nvim and not Calva, but your examples should suffice in figuring it all out when I get to it down the line.
Sure. If you change the two open calls in this code to not specify VS Code as the launcher, you will get two Portal browser windows: https://github.com/seancorfield/vscode-calva-setup/blob/develop/calva/config.edn#L46-L90
And this is the code that wires up clojure.tools.logging to Portal when starting a REPL (and also handles the middleware stuff, below): https://github.com/seancorfield/dot-clojure/blob/develop/src/org/corfield/dev/repl.clj#L87-L120