Thanks Sean, the stuff with file/line and thread name was helpful! I updated my own logging-future macro(s) - here's an interesting version (logging-future+) that logs the client stacktrace at the time when it called future. I often find that much more useful that the stacktrace inside the thread itself, especially when using things like Hystrix: https://github.com/jumarko/clojure-experiments/blob/master/src/clojure_experiments/concurrency.clj#L31-L50 ``` (defn logging-future+* [file line body] `(let [client-stack-trace# (Exception. "Client stack trace")] (future (try ~@body (catch Throwable e# (log/error e# "Unhandled exception at:" ~file "line:" ~line "on thread:" (.getName (Thread/currentThread))) (log/error client-stack-trace# "client stack trace:")))))) ```
On Tuesday, 5 January 2021 at 17:51:42 UTC+1 Austin Haas wrote: > Thank you, Sean. That is an excellent example. > > -austin > > On Sunday, January 3, 2021 at 12:48:55 PM UTC-8 Sean Corfield wrote: > >> Austin, >> >> You might find a macro like this helpful -- just use it directly instead >> of future. You can replace println with whatever sort of logging you want. >> >> (defmacro logged-future >> "Given a body, execute it in a try/catch and log any errors." >> [& body] >> (let [line (:line (meta &form)) >> file *file*] >> `(future >> (try >> ~@body >> (catch Throwable t# >> (println t# "Unhandled exception at:" >> ~file "line:" ~line >> "on thread:" >> (.getName (Thread/currentThread)))))))) >> >> >> On Sat, Jan 2, 2021 at 5:59 PM Austin Haas <aus...@pettomato.com> wrote: >> >>> Ah, thanks for pointing that out. I must've overlooked your example, >>> because I'd already written off futures. >>> >>> It seems like what you are suggesting, catch and print, might be about >>> as good as I could hope for. If I don't want to block the main thread, then >>> I don't see what else I could possibly do but print the exception. I guess >>> I could store it somewhere, but in any case, I'd use this same pattern. >>> >>> Thanks, Justin. >>> On Saturday, January 2, 2021 at 1:44:55 PM UTC-8 noise...@gmail.com >>> wrote: >>> >>>> to be clear, in my second example you see the error from the future >>>> without using deref >>>> >>>> good luck finding your solution >>>> >>>> On Sat, Jan 2, 2021 at 12:50 PM Austin Haas <aus...@pettomato.com> >>>> wrote: >>>> >>>>> Thank you very much for the explanation, Justin. >>>>> >>>>> I don't see how I can use futures, though, without blocking on the >>>>> main thread (to get the exception when it occurs). I'm spawning a >>>>> long-running process that never returns a value. >>>>> On Saturday, January 2, 2021 at 12:43:14 AM UTC-8 noise...@gmail.com >>>>> wrote: >>>>> >>>>>> By the time the exception is caught, you are already outside the >>>>>> context of the Thread which the repl client is interacting with. The >>>>>> default exception handler has no information tying the executing thread >>>>>> to >>>>>> the repl process (not to mention the dynamic variables clojure is using >>>>>> to >>>>>> associate output from code your client runs with that socket connection). >>>>>> >>>>>> You probably don't want to rebind the root exception handler to show >>>>>> *all* exceptions to your client socket. Which means that you need to set >>>>>> up >>>>>> some soft of infrastructure connecting the information about the failed >>>>>> function to the socket you are listening to. >>>>>> >>>>>> I find "future" very convenient for this, it uses a pool which will >>>>>> perform better than creating Threads ad-hoc, and will capture >>>>>> Exceptions >>>>>> and re-throw when you deref (of course, it's up to you to ensure you >>>>>> deref, >>>>>> or use try/catch and otherwise forward the failure information via the >>>>>> catch block). Also, it conveys dynamic bindings for things like >>>>>> clojure.core/*out* and clojure.core/*err* that java classes don't know >>>>>> about. >>>>>> >>>>>> (ins)user=> (def fut (future (throw (Exception. "oops")))) >>>>>> #'user/fut >>>>>> (ins)user=> @fut ; waits until deref to raise the error >>>>>> Execution error at user/fn (REPL:11). >>>>>> oops >>>>>> (ins)user=> (def fut2 (future (try (throw (Exception. "oops")) (catch >>>>>> Exception e (println "wat\n" e))))) ; prints instead of raising >>>>>> #'user/fut2 >>>>>> user=> wat >>>>>> #error { >>>>>> :cause oops >>>>>> :via >>>>>> [{:type java.lang.Exception >>>>>> :message oops >>>>>> :at [user$fn__165 invokeStatic NO_SOURCE_FILE 13]}] >>>>>> :trace >>>>>> [[user$fn__165 invokeStatic NO_SOURCE_FILE 13] >>>>>> [user$fn__165 invoke NO_SOURCE_FILE 13] >>>>>> [clojure.core$binding_conveyor_fn$fn__5754 invoke core.clj 2030] >>>>>> [clojure.lang.AFn call AFn.java 18] >>>>>> [java.util.concurrent.FutureTask run FutureTask.java 264] >>>>>> [java.util.concurrent.ThreadPoolExecutor runWorker >>>>>> ThreadPoolExecutor.java 1128] >>>>>> [java.util.concurrent.ThreadPoolExecutor$Worker run >>>>>> ThreadPoolExecutor.java 628] >>>>>> [java.lang.Thread run Thread.java 834]]} >>>>>> >>>>>> >>>>>> >>>>>> On Thu, Dec 31, 2020 at 1:48 PM Austin Haas <aus...@pettomato.com> >>>>>> wrote: >>>>>> >>>>>>> >>>>>>> Problem: When I connect to a socket server and create a thread, >>>>>>> exceptions in the thread are printed in the server's process, not the >>>>>>> client's. I'd like them to appear in the client's process, where the >>>>>>> thread >>>>>>> was created. >>>>>>> >>>>>>> (I'm using the term "process" very generally here, because I don't >>>>>>> understand what is going on.) >>>>>>> >>>>>>> From searching, I understand that there are some other things at >>>>>>> play, like System/err, but I don't understand what is happening or how >>>>>>> I >>>>>>> can work around it. Why does an exception thrown in the client process >>>>>>> show >>>>>>> in the client process, but an exception thrown in a thread created by >>>>>>> the >>>>>>> client process shows in the server process? Why doesn't binding *err* >>>>>>> in a >>>>>>> thread seem to have any effect? Any suggestions or workarounds? >>>>>>> >>>>>>> I'm not using futures, because this is a long-running process that >>>>>>> never returns a value. >>>>>>> >>>>>>> Example transcript: >>>>>>> >>>>>>> # Socker server >>>>>>> >>>>>>> (The only command entered is the first one, which begins with clj. >>>>>>> Everything after "user=>" is due to the client below.) >>>>>>> >>>>>>> $ clj -J-Dclojure.server.myrepl='{:port >>>>>>> 5555,:accept,clojure.core.server/repl}' >>>>>>> Clojure 1.10.1 >>>>>>> user=> My second message. >>>>>>> Exception in thread "Thread-0" clojure.lang.ExceptionInfo: My second >>>>>>> exception {} >>>>>>> at user$eval5$fn__141.invoke(NO_SOURCE_FILE:7) >>>>>>> at clojure.lang.AFn.run(AFn.java:22) >>>>>>> at java.lang.Thread.run(Thread.java:745) >>>>>>> Exception in thread "Thread-1" clojure.lang.ExceptionInfo: My third >>>>>>> exception {} >>>>>>> at user$eval144$fn__145.invoke(NO_SOURCE_FILE:16) >>>>>>> at clojure.lang.AFn.run(AFn.java:22) >>>>>>> at java.lang.Thread.run(Thread.java:745) >>>>>>> >>>>>>> # Client >>>>>>> >>>>>>> $ nc localhost 5555 >>>>>>> user=> (println "My first message.") >>>>>>> My first message. >>>>>>> nil >>>>>>> user=> (throw (ex-info "My first exception." {})) >>>>>>> Execution error (ExceptionInfo) at user/eval3 (REPL:2). >>>>>>> My first exception. >>>>>>> user=> (.start >>>>>>> (Thread. >>>>>>> (fn [] >>>>>>> (println "My second message.") >>>>>>> (throw (ex-info "My second exception" {}))))) >>>>>>> nil >>>>>>> user=> (.start >>>>>>> (Thread. >>>>>>> (let [out *out* >>>>>>> err *err*] >>>>>>> (fn [] >>>>>>> (binding [*out* out >>>>>>> *err* err] >>>>>>> (println "My third message.") >>>>>>> (throw (ex-info "My third exception" {}))))))) >>>>>>> nil >>>>>>> My third message. >>>>>>> >>>>>>> Any clues would be appreciated. Thanks! >>>>>>> >>>>>>> -- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "Clojure" group. >>>>>>> To post to this group, send email to clo...@googlegroups.com >>>>>>> Note that posts from new members are moderated - please be patient >>>>>>> with your first post. >>>>>>> To unsubscribe from this group, send email to >>>>>>> clojure+u...@googlegroups.com >>>>>>> For more options, visit this group at >>>>>>> http://groups.google.com/group/clojure?hl=en >>>>>>> --- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "Clojure" group. >>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>> send an email to clojure+u...@googlegroups.com. >>>>>>> To view this discussion on the web visit >>>>>>> https://groups.google.com/d/msgid/clojure/d084b0c0-0a1b-4db2-95a1-f38ff894bfa6n%40googlegroups.com >>>>>>> >>>>>>> <https://groups.google.com/d/msgid/clojure/d084b0c0-0a1b-4db2-95a1-f38ff894bfa6n%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>>> . >>>>>>> >>>>>> -- >>>>> You received this message because you are subscribed to the Google >>>>> Groups "Clojure" group. >>>>> To post to this group, send email to clo...@googlegroups.com >>>>> Note that posts from new members are moderated - please be patient >>>>> with your first post. >>>>> To unsubscribe from this group, send email to >>>>> clojure+u...@googlegroups.com >>>>> For more options, visit this group at >>>>> http://groups.google.com/group/clojure?hl=en >>>>> --- >>>>> You received this message because you are subscribed to the Google >>>>> Groups "Clojure" group. >>>>> To unsubscribe from this group and stop receiving emails from it, send >>>>> an email to clojure+u...@googlegroups.com. >>>>> >>>> To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/clojure/8dd61a48-0195-4b2d-bbee-7d24f976268fn%40googlegroups.com >>>>> >>>>> <https://groups.google.com/d/msgid/clojure/8dd61a48-0195-4b2d-bbee-7d24f976268fn%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>> . >>>>> >>>> -- >>> You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> To post to this group, send email to clo...@googlegroups.com >>> Note that posts from new members are moderated - please be patient with >>> your first post. >>> To unsubscribe from this group, send email to >>> clojure+u...@googlegroups.com >>> For more options, visit this group at >>> http://groups.google.com/group/clojure?hl=en >>> --- >>> You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to clojure+u...@googlegroups.com. >>> >> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/clojure/82e8cba4-c8a3-4d9a-81be-f703d8e0aa52n%40googlegroups.com >>> >>> <https://groups.google.com/d/msgid/clojure/82e8cba4-c8a3-4d9a-81be-f703d8e0aa52n%40googlegroups.com?utm_medium=email&utm_source=footer> >>> . >>> >> >> >> -- >> Sean A Corfield -- (904) 302-SEAN >> An Architect's View -- https://corfield.org/ >> World Singles Networks, LLC. -- https://worldsinglesnetworks.com/ >> >> "Perfection is the enemy of the good." >> -- Gustave Flaubert, French realist novelist (1821-1880) >> > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/aaceb84c-76aa-49e7-9b87-e9a443234d2fn%40googlegroups.com.