Fork me on GitHub
#portal
<
2024-03-02
>
sheluchin21:03:08

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?

seancorfield23:03:41

I don't recognize that output -- how are you using Portal? I get actual exceptions reported in my setup?

sheluchin23:03:06

@U04V70XH6 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.

seancorfield23:03:28

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

seancorfield23:03:02

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.

👍 1
seancorfield23:03:34

Given that Portal is showing you the full details, why do you want to eval @user/p?

sheluchin23:03:14

I want to try calling different methods of the exception instance.

seancorfield23:03:15

Basically, Portal has datafy'd it so it can present the view you're seeing (and datafy on an exception calls Throwable->map).

seancorfield23:03:41

Then *e is probably what you want...?

sheluchin23:03:56

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?

seancorfield23:03:25

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.

seancorfield23:03:37

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.

seancorfield23:03:09

(and quite often the constructors you might need to rebuild the Java objects aren't public)

sheluchin23:03:09

Isn't the exception information all in the second item of the :via vector in that screenshot?

seancorfield23:03:09

Only the generic stuff. None of the PG-specific stuff.

seancorfield23:03:15

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.

sheluchin23:03:08

Ah, I get what you mean. Like .getSQLState wouldn't work because that's pg-specific. That's what your talking about?

seancorfield23:03:14

Well, .getSQLState() is "generic" SQL-specific I think but, yeah, that's not included.

seancorfield23:03:43

You might get useful data from (bean *e) or use clojure.java.data's from-java on it to get more detail.

seancorfield00:03:58

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...

seancorfield00:03:37

(you could use *e in that first call)

seancorfield00:03:03

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 ...

sheluchin17:03:53

Good stuff, @U04V70XH6. 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.

seancorfield17:03:14

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

sheluchin15:03:19

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?

djblue16:03:19

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)

1
seancorfield18:03:40

> 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...