This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-07-08
Channels
- # announcements (2)
- # babashka (100)
- # beginners (25)
- # biff (7)
- # calva (13)
- # cider (24)
- # clj-kondo (39)
- # cljsrn (2)
- # clojure (22)
- # clojure-dev (13)
- # clojure-europe (12)
- # clojure-gamedev (3)
- # clojure-losangeles (2)
- # clojure-nl (1)
- # clojure-norway (3)
- # clojure-spec (11)
- # clojure-uk (2)
- # clojurescript (20)
- # core-async (8)
- # cursive (7)
- # data-science (2)
- # datomic (14)
- # emacs (6)
- # events (7)
- # fulcro (9)
- # honeysql (1)
- # kaocha (24)
- # lambdaisland (3)
- # leiningen (6)
- # lsp (30)
- # membrane (7)
- # missionary (10)
- # nbb (48)
- # nextjournal (13)
- # off-topic (6)
- # parinfer (4)
- # pathom (1)
- # polylith (1)
- # reagent (7)
- # rewrite-clj (6)
- # ring (11)
- # sci (7)
- # shadow-cljs (8)
- # sql (13)
@lilactown not a stupid question at all - so currently this is supported when a script uses (babashka.deps/add-deps '{:deps ..})
but for script "authors" that do not want their users to use the internet post-installation, it's better to distribute an uberjar probably and then bb install --name tree
or perhaps we can piggieback on the clojure -T stuff a bit, still need to look at this
<stack trace redacted>
note that even the invocation of clojure.core/requiring-resolve
has been lost, and ideally, it would also include the stack trace from the code that was required, considering that's where the error is.
Could you maybe link to a gist instead of posting a long stacktrace in this channel?
i'm working on creating a small test case but i was hoping there was a pat answer already 😆
is this what you meant by "gist"?
so what you are seeing here is the stack trace from the interpreter. what do you expect to see?
i'm working on creating an isolated demonstration case
i'm invoking clojure-core/requiring-resolve, and the code that is thus required throws an exception. I need to be able to get the info about what is happening inside the required code, but instead, the interpreter is eating the stacktrace and only giving the underlying exception message, and nothing about the actual trace.
It's possible that in the process of creating the isolated test case, i may also show a problem with our exception handling, but my visual inspections haven't shown that. isolating the test case from the rest of our code will take a bit.
So, bb vs clojure:
$ clojure -M -e '(try (requiring-resolve (quote foo/bar)) (catch Exception e [(ex-message e) (type e) (ex-data e)]))'
["Could not locate foo__init.class, foo.clj or foo.cljc on classpath." java.io.FileNotFoundException nil]
$ bb -e '(try (requiring-resolve (quote foo/bar)) (catch Exception e [(ex-message e) (type e) (ex-data e)]))'
["Could not find namespace: foo." java.lang.Exception nil]
i'm getting the exception message but not the trace - the trace data shows the interpreter invocation up to, i assume, the point in the interpreter where it caught the exception
that's not helpful though
the exception trace to have the path the code took to the exception in the required code
not just the trace to this point:
at java.lang.reflect.Constructor.newInstance (Constructor.java:490)
sci.impl.Reflector.invokeConstructor (Reflector.java:310)
sci.impl.interop$invoke_constructor.invokeStatic (interop.cljc:58)
which is in bb internals and has no reference to code in my systemthere's no reference to code in on my machine anywhere in the trace
this is available, but only when you catch the exception that the interpreter threw, but I'm not sure how to do this within the interpreter, since you cannot change Java stack traces and add random information to that
if you have any ideas how to improve this without sacrificing performance, please share them
e.g. when you invoke bb your_script.clj
then you will see the stack trace printed with references to your code
but since you can catch any exception like FileNotFoundException
you can't add arbitrary clojure data to those
i don't know anything about bb internals, so I can't help with suggestions like that; i just know that I'm losing trace information that is critical to debugging
I agree it would be helpful to have this information, it's just not there at the moment in the trace on the exception
But for debugging you can just execute your script and then see the stack trace being spit out to stdout
i'm not sure what you mean by "execute your script" - executing my script includes a call to requiring-resolve, which is the thing that is eating the stack trace
the script involves dynamic lookup of code, in fact, intentionally trying to avoid having to interpret the entire (growing) codebase to execute very small portions of it
What I mean is this:
$ bb foo.clj
----- Error --------------------------------------------------------------------
Type: java.lang.Exception
Message: Could not find namespace: dude.
Location: /private/tmp/foo.clj:4:1
----- Context ------------------------------------------------------------------
1: (defn foo []
2: (requiring-resolve 'dude/bar))
3:
4: (foo)
^--- Could not find namespace: dude.
----- Stack trace --------------------------------------------------------------
user/foo - /private/tmp/foo.clj:1:1
user - /private/tmp/foo.clj:4:1
the error you just posted is not coming from the required code; the require itself is failing
ah, so the error is thrown when the required code is loaded - not because it's unavailable?
there's no hidden trace information, the error is explicit - it can't find the namespace
the error is thrown when the loaded symbol is run with arguments taken from the command line
(the last sentence is for my situation, the prior for the example you posted)
I'm not sure I understand your problem. Are you trying to debug a problem, or are you just asserting that debugging is hard due to the missing stack trace?
I'm trying to figure out why debugging a problem is hard; in parallel, i'm trying to construct a test case that i can show you because trying to talk about code without code to look at is hard
In the first case, I can help you debug it if you make a repro (which you are doing). For the second, there might be a solution, but not this week, more longer term, perhaps.
it's possible there's an error in our framework code that handles the dynamic code loading, exceptions and logging; it's possible there's not a solution; you might be able to suggest a better framework pattern as well.
okay, i have a tarball - where do you want it?
it's only 2k, i think here should be ok
it's always harder than i expect to isolate a test case. all code is tangled with its web of dependencies 🙂
there's a bit of extra code in here, but hopefully not too much for you to figure out what you're looking at.
there's a readme.txt
bb -f main.clj open wrong
ok, so, as already stated, there is no useful info for you in the stacktrace, so printing that won't be helpful to the user (right now). Removing that try/catch will result into more helpful info:
$ bb -f main.clj open wrong
running (open wrong)
----- Error --------------------------------------------------------------------
Type: java.lang.IllegalArgumentException
Message: No matching clause: wrong
Location: /Users/borkdude/Downloads/trace-fail/main.clj:11:5
----- Context ------------------------------------------------------------------
7:
8: (let [[command & args] *command-line-args*]
9: (println "running" *command-line-args*)
10: (try
11: (resolve-and-run command args)
^--- No matching clause: wrong
12: #_(catch Exception e
13: (clojure.stacktrace/print-stack-trace e)
14: (println "\n\nfailed (see trace before help):\n" (str (.getName (.getClass e)) ":") (ex-message e)))))
----- Stack trace --------------------------------------------------------------
main/resolve-and-run - /Users/borkdude/Downloads/trace-fail/main.clj:4:1
main - /Users/borkdude/Downloads/trace-fail/main.clj:11:5
My advice would be to make the scope of your try/catch smaller, e.g. add a fallback to your case statement which prints that it's an unsupported option for example.What I could maybe support short term is an uncaught exception handler in which you get to see the babashka stacktrace, but I'm not sure why that would be more useful than what bb prints by default
Your command line pattern looks like it could potentially benefit from babashka cli: https://github.com/babashka/cli It has support for subcommands, but it's a bit new and there may be changes along the road
yeah, that's something that didn't exist when we first started this (like 2 months ago, lol, things are evolving fast for both bb and for us 😆 )
it definitely could and if i were doing it over again i'd totally support it
as it is, i want to try cli out on something smaller before trying to wade into back propagating it to our existing structure
sure, makes sense, just thought I'd mention it in case you didn't know about it. in the future it's going to be a built-in, but not before it's "ready"
i'm curious - why doesn't the interpreter have a stack trace in this situation, just like it does when it is running code that it required via the namespace require?
One problem e.g. is when you catch a thrown java.lang.FileNotExistsException
, there is no place to put the interpreter stacktrace (which is different from the host stack trace)
one possible API could be:
(catch java.lang.WhateverException e (babashka.core/stacktrace))
but then it'd be already too late to get the stacktraceit seems like the ideal would be for the exception to have a cause on it, one way or the other
so if you caught a FileNotFoundException, and the cause would have an interpreter stacktrace thing, wouldn't that be a bit reversed?
i'd take it reversed if i had to 😆 although i would think you could put them in whichever configuration made the most sense, as long as the exception thrown to the host system worked for dropping a good exception into the main output
you can either find the bottom of the exception you have and call setCause on it (where cause is nil, in other words), or you can create a new exception to throw and include the one you have as the cause on it...
that's a good point, but if the user says (catch FileNotFoundException ...) they want of course not some wrapped thing right?
hmm - that's a tough one. you'd wind up, i guess, with different exceptions in the requiring-resolve case vs the straight execution path... which is gross
i wonder if you could reify a new exception of the same type but with a new function like "get-other-trace" or something
but if the users says "catch Exception" then we could return some wrapped thing with a cause, maybe
could you make it an interpreter argument whether to wrap it or not?
or a binding?
I'm going to track the issue here: https://github.com/babashka/sci/issues/774 I think there are some possibilities. You can share your interest there with a "thumbs up" or a comment. @U038RGYDGUR also asked about something similar a while ago. It'd be nice if we could support this in a good way.
So I've got this working now:
$ clojure -M:babashka/dev -e "(require '[sci.core :as sci]) (defn foo [] (/ 1 0)) (defn bar [] (try (foo) (catch Exception e (run! prn (sci/format-stacktrace (sci/stacktrace e)))))) (bar)"
"clojure.core// - <built-in>"
"user/foo - <expr>:1:44"
"user/foo - <expr>:1:31"
"user - <expr>:1:71"
Here, I expose a part of sci.core itself in bb and also let the exception hang on to the stack trace info. I just haven't figured out yet the part where the user catches a certain type of Exception. Maybe the contract could be, when the user catches the most general type of exception, then it will contain this info, otherwise it will be the vanilla type or so.I may have found a good way: when you just say (catch Exception ...)
you get the "wrapped exception. And then your code would become:
(catch Exception e
(->> e stacktrace format-stacktrace (run! prn))
(println "\n\nfailed (see trace before help):\n" (str (.getName (.getClass e)) ":") (ex-message e)))))
which then gives (with a locally compiled bb):
$ bb -f main.clj
running nil
"main/resolve-and-run - /Users/borkdude/Downloads/trace-fail/main.clj:4:1"
"main - /Users/borkdude/Downloads/trace-fail/main.clj:11:5"
failed (see trace before help):
clojure.lang.ExceptionInfo: no conversion to symbol
I get one failing case with babashka's CI tests, though, for example with clj-http-lite:
FAIL in (exception-test) (/Users/borkdude/dev/babashka/test-resources/lib_tests/clj_http/lite/client_test.clj:)
expected: (:headers (ex-data e))
So it might be better to catch a very specific type of exception when you want the interpreter trace. E.g. (catch sci.lang.Exception)
to not break libraries (and also for perf reasons probably)
@U038RGYDGUR Would that seem good to you too?
(grump: this would require me to compile a specific exception type, but that's a small price to pay)
Perhaps going with (catch ^::sci/preserve-stacktrace Exception)
would be a better choice for not breaking compatibility
So now your code is going to look like:
(ns main
(:require [sci.core :as sci]))
(defn resolve-and-run [command args]
(let [command-fn (requiring-resolve (symbol (str "foo." command) command))]
(apply command-fn args)))
(let [[command & args] *command-line-args*]
(println "running" *command-line-args*)
(try
(resolve-and-run command args)
(catch ^:sci/error Exception e
(->> e sci/stacktrace sci/format-stacktrace (run! prn))
(println "\n\nfailed (see trace before help):\n" (-> e ex-cause class .getName (str ":")) (ex-message e)))))
and the output:
$ bb main.clj
running nil
"main/resolve-and-run - /Users/borkdude/Downloads/trace-fail/main.clj:4:1"
"main - /Users/borkdude/Downloads/trace-fail/main.clj:11:5"
failed (see trace before help):
java.lang.IllegalArgumentException: no conversion to symbol
If you let me know your OS @U03A0EGF82E, I can link you to a binary to try out locally
i'm on a mac
thanks so much for working on this - the path you are on seems great to me.
@U03A0EGF82E I merged it to master already. You can install a dev version from : https://github.com/babashka/babashka-dev-builds