clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Calum Boal 2020-12-18T00:09:58.178Z

Having some issues implementing something using core.async. What I want is to use go blocks to do a bunch of HTTP requests and process the responses. I'm approaching this coming from golang which may be where my issues are originating. The way i'm approaching this is to have a pool of go blocks trying to read from a channel which is feeding them urls, and then returning results over another channel. The issue im having is that I can't ensure all go blocks are finished working.

seancorfield 2020-12-18T00:11:40.178100Z

Because it would have to be at that exact same relative path in resources and your :exclude would still exclude it.

Calum Boal 2020-12-18T00:12:03.178500Z

(defn worker [urls-chan results-chan]
  (async/go (while true
              (let [url (async/<! urls-chan)
                    result (is-vulnerable? url)]
                (if (and (not (nil? result)) (not (empty? result)))
                  (async/>! results-chan (string/join "\n" result)))))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)]
    (dotimes [_ 10]
      (worker urls-chan results-chan))
    (async/go (while true
          (println (async/<! results-chan))))
  (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
    (async/>!! urls-chan ln))))

kwladyka 2020-12-18T00:12:18.178600Z

yes… so I don’t have more ideas

kwladyka 2020-12-18T00:23:42.179500Z

Anyone have an example of java.util.logging formatter in Clojure? The best to print custom JSON :)

2020-12-18T00:25:24.179600Z

https://gist.github.com/hiredman/64bc7ee3e89dbdb3bb2d92c6bddf1ff6 is a mini logging library built on java.util.logging that logs clojure data

👍 2
kwladyka 2020-12-18T00:26:19.179800Z

thank you, I check this one!

2020-12-18T00:29:03.180100Z

don't use while true

2020-12-18T00:29:23.180300Z

loop only when you take a non-nil from urls-chan

2020-12-18T00:29:30.180500Z

close urls-chan when you are done

2020-12-18T00:29:53.180700Z

in general using go blocks as workers is not useful

2020-12-18T00:30:13.180900Z

since they run on a limited size threadpool (usually capped at 8 threads)

2020-12-18T00:30:54.181100Z

something like async/pipeline-blocking is likely much more of what you want

Calum Boal 2020-12-18T00:43:42.181400Z

Hmm yeah potentially, in go you would use go routines even though they are also capped by physical threads, but there's a runtime for switching while they're sleeping

Calum Boal 2020-12-18T00:43:48.181600Z

Figured it was the same here

Calum Boal 2020-12-18T00:43:54.181800Z

Managed to get this now:

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/<! urls-chan)]
                (if (not (nil? url))
                    (let [result (is-vulnerable? url)]
                      (println url)
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/>! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/<! results-chan)]
                (if (not (nil? result))
                  (do
                    (println result)
                    (recur)))))))
(defn -main
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan)
        worker-chans (vec [])
        printer-chans (vec [])]
    (dotimes [_ 10]
      (conj worker-chans (worker urls-chan results-chan)))
    (conj printer-chans (printer results-chan))
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/>!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/<!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/<!! %) printer-chans)))

Calum Boal 2020-12-18T00:44:15.182Z

It's not working though haha, it was almost working a min ago

2020-12-18T00:57:09.182200Z

my initial suspicion is you are doing some blocking operations in go blocks, which is gumming up the threadpool

2020-12-18T00:58:31.182400Z

no, the go macro is just a macro

2020-12-18T00:58:38.182600Z

it is a syntax transformation

2020-12-18T00:59:13.182800Z

it can't turn your blocking io (or blocking on promises, locks, etc) in to not blocking

Calum Boal 2020-12-18T01:01:57.183Z

Current issue is still that it's just exiting before doing anything 😞

Calum Boal 2020-12-18T01:04:38.183200Z

Doesn't even take a single value from url-chan in the worker

2020-12-18T01:12:08.183500Z

@cgboal521 what does is-vulnerable? return? A boolean? When I tested it I was getting an error from calling empty? on result with result being a boolean

Calum Boal 2020-12-18T01:12:46.183700Z

Think i've figured out the issue, just way too tired haha

Calum Boal 2020-12-18T01:12:59.183900Z

I can post the full code if you like, but nah it was a boolean now it returns a string

2020-12-18T01:13:46.184100Z

btw if you don't have it already it might help to have an uncaught exception handler, like this:

;;<https://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions>
;; Assuming require [clojure.tools.logging :as log]
(Thread/setDefaultUncaughtExceptionHandler
 (reify Thread$UncaughtExceptionHandler
   (uncaughtException [_ thread ex]
     (log/error ex "Uncaught exception on" (.getName thread)))))
because that's the only way I was seeing the error

Calum Boal 2020-12-18T01:25:32.184300Z

(ns the-great-escape.core
  (:require
   [clj-http.client :as client]
   [hickory.core :as html]
   [hickory.select :as s]
   [clojure.core.async :as async]
   [clojure.string :as string]
   [lambdaisland.uri :refer [uri]])
  (:gen-class))

(defn doc-&gt;hickory [doc]
  (-&gt; (html/parse doc)
      (html/as-hickory)))

(defn fetch-and-parse-page [url]
  (-&gt; (client/get url
                  {:cookie-policy :none
                   :redirect-strategy :none})
      (:body)
      (doc-&gt;hickory)))

(defn extract-asset-tags [body]
  (s/select (s/or
             (s/attr :href)
             (s/attr :src))
            body))

(defn extract-href-urls [tags]
  (map #(get-in %1 [:attrs :href]) tags))

(defn extract-src-urls [tags]
  (map #(get-in %1 [:attrs :src]) tags))

(defn extract-asset-urls [tags]
  (-&gt;&gt; (extract-href-urls tags)
       (filter #(not (nil? %1)))))

(defn parse-urls [urls]
  (map uri urls))

(defn local-asset? [base-domain {:keys [host]}]
  (or (nil? host) (= base-domain host)))

(defn filter-local-paths [base-domain parsed-urls]
    (-&gt;&gt; (filter (partial local-asset? base-domain) parsed-urls)
         (map :path)
         (filter #(not (nil? %1)))
         ))

(defn url-&gt;parts-map [url]
  (let [parts (string/split url #"/")]
    {:dir (str "/" (second parts))
     :path (str "/" (string/join "/" (drop 2 parts)))}))

(defn urls-&gt;parts-maps [urls]
  (map url-&gt;parts-map urls))
(defn get-unique-dirs [parts-map]
  (map #(first (val %1)) (group-by :dir parts-map)))

(defn filter-bad-traversals [parts-map]
  (filter #(and (not= (:dir %1) "/") (not= (:path %1) "/")) parts-map))

(defn build-original-url [base-url parts-map]
  (str (assoc base-url :path (str (:dir parts-map) (:path parts-map)))))

(defn build-traversal-url [base-url parts-map]
  (str (assoc base-url :path (str (:dir parts-map) ".." (:dir parts-map) (:path parts-map)))))

(defn request-and-hash [url]
  (-&gt; (client/get url {:redirect-strategy :none
                       :cookie-policy :none})
      (:body)
      (hash)))


(defn doc-&gt;candidates [base-domain doc]
  (-&gt;&gt; doc
       (extract-asset-tags)
       (extract-asset-urls)
       (parse-urls)
       (filter-local-paths base-domain)
       (urls-&gt;parts-maps)
       (get-unique-dirs)
       (filter-bad-traversals)))

(defn traversal-comparison [base-url candidate]
  (try
    (let [baseline (request-and-hash (build-original-url base-url candidate))
          traversal (request-and-hash (build-traversal-url base-url candidate))
          blank (hash "")]
      (if (not= baseline blank)
        (if (= baseline traversal)
          (build-traversal-url base-url candidate))))
    (catch Exception _
        nil)))

(defn is-vulnerable? [url]
  (try 
  (let [url (uri url)
        doc (fetch-and-parse-page (str url))
        candidates (doc-&gt;candidates (:host url) doc)]
    (filter #(not (nil? %1)) (map #(traversal-comparison url %1) candidates)))
  (catch Exception _
    nil)))


(defn stdin-urls []
  (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
    (println ln)))

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/&lt;! urls-chan)]
                (if (some? url)
                    (let [result (is-vulnerable? url)]
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/&gt;! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/&lt;! results-chan)]
                (if (some? result)
                  (do
                    (println result)
                    (recur)))))))

(defn spawn-workers [urls-chan results-chan n]
  (loop [workers []
         x 0]
    (if (= x n)
      (identity workers)
      (recur (conj workers (worker urls-chan results-chan)) (inc x)))))

(defn -main
  [&amp; args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)
        worker-chans (spawn-workers urls-chan results-chan 10)
        printer-chans (into [] (printer results-chan))]
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/&gt;!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/&lt;!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/&lt;!! %) printer-chans)))

Calum Boal 2020-12-18T01:25:53.184500Z

urls over stdin

Calum Boal 2020-12-18T01:26:04.184700Z

Don't worry about it though, think i'm close to figuring it out

alexmiller 2020-12-18T01:31:31.184900Z

not yet

alexmiller 2020-12-18T01:31:48.185100Z

tools.build is coming...

3
😮 3
Calum Boal 2020-12-18T01:32:08.185300Z

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/&lt;! urls-chan)]
                (if (some? url)
                    (let [result (is-vulnerable? url)]
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/&gt;! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/&lt;! results-chan)]
                (if (some? result)
                  (do
                    (println result)
                    (recur)))))))

(defn spawn-workers [urls-chan results-chan n]
  (loop [workers []
         x 0]
    (if (= x n)
      (identity workers)
      (recur (conj workers (worker urls-chan results-chan)) (inc x)))))

(defn -main
  [&amp; args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)
        worker-chans (spawn-workers urls-chan results-chan 10)
        printer-chans (conj [] (printer results-chan))]
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/&gt;!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/&lt;!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/&lt;!! %) printer-chans)))

Calum Boal 2020-12-18T01:32:09.185500Z

That works

bringe 2020-12-18T01:42:43.185700Z

Oh cool, ty

dpsutton 2020-12-18T06:55:26.189900Z

on http://clojure.org, the section about typehinting (in the weird characters guide) in the reader gives the following example for typehinting: (def ^Integer five 5). Yet all over Clojure's source code, ^long is used (although possibly in different contexts, typehinting locals and return types for functions). When I typehint a simple var like that if i use ^Long i get its tag as java.lang.Long and when typehinted as ^long i get #function[clojure.core/long]. Are these things semantically different? One obviously wrong?

alexmiller 2020-12-18T07:18:05.190700Z

they are semantically different

phronmophobic 2020-12-18T07:18:12.191Z

https://clojure.org/reference/java_interop#TypeAliases gives some background.

alexmiller 2020-12-18T07:18:25.191300Z

Java has 7 primitive types with lowercase letters - int, long, etc

alexmiller 2020-12-18T07:18:46.191800Z

it also has object-based wrappers around those primitive types java.lang.Integer, java.lang.Long, etc

alexmiller 2020-12-18T07:19:23.192400Z

in many places these will automatically converted if needed so that hides a lot of mismatches

alexmiller 2020-12-18T07:20:01.192800Z

I'd say that particular example is confusing

alexmiller 2020-12-18T07:20:38.193600Z

by default, Clojure will read integer numbers as java.lang.Long objects

alexmiller 2020-12-18T07:22:29.195300Z

so ^Long there would probably make more sense, but this is case is probably not a good place to really need/want that

dpsutton 2020-12-18T07:28:55.196Z

I think what really confused me was when i type hinted the var with ^long, I would get errors of > Syntax error (IllegalArgumentException) compiling . at (REPL:1:31). > Unable to resolve classname: clojure.core$long@67ec8477 when the compiler was using that typehint.

dpsutton 2020-12-18T07:29:48.196700Z

and that made me want to find some proper documentation instead of just the kinda rule of thumb nature of my knowledge of type hinting

dpsutton 2020-12-18T07:30:14.196800Z

(def ^long year-threshold 20)
#'user/year-threshold
(def ^:private past-threshold (.. (LocalDateTime/now)
                                  (minus year-threshold ChronoUnit/YEARS)
                                  (toInstant ZoneOffset/UTC)
                                  (getEpochSecond)))
Syntax error (IllegalArgumentException) compiling . at (REPL:1:31).
Unable to resolve classname: clojure.core$long@67ec8477

phronmophobic 2020-12-18T07:32:51.197300Z

can you type hint a def? I'm reading https://clojure.org/reference/java_interop#typehints > They can be placed on function parameters, let-bound names, var names (when defined), and expressions but I'm not sure

lassemaatta 2020-12-18T07:32:53.197500Z

or atleast the part discussing {:tag 'long}

zendevil 2020-12-18T07:33:39.197900Z

I’m using the monger database, which is showing the results from the find function. It’s showing the results as empty even though I have items in my database. Here’s the code:

(ns humboiserver.db.core
  (:require
   [monger.core :as mg]
   [monger.gridfs :as gfs]
   [monger.query :as q]
   [monger.collection :as mc]
   [cheshire.core :as json]
   )
  (:import
   org.bson.types.ObjectId))

(let [{:keys [conn db]}
      (mg/connect-via-uri “<mongodb+srv://user>:<mailto:pass@cluster0.ww5gh.mongodb.net|pass@cluster0.ww5gh.mongodb.net>/&lt;dbname&gt;?retryWrites=true&amp;w=majority”)]
  (defn find [coll query] (mc/find-maps db coll query))

Here’s the usage:
(db/find “featured” {})

And here’s the featured database: [![enter image description here][1]][1] [1]: https://i.stack.imgur.com/kWZbS.png Yet doing the find is returning an empty sequence. How to fix this?

phronmophobic 2020-12-18T07:34:20.198100Z

ah ok, @lasse.maatta’s link seems to show that you can.

alexmiller 2020-12-18T07:41:48.198300Z

you can type hint a def, you just have to be aware that type hints on the var (as in def, but also the function name in defn) are evaluated, so ^long will evaluate to the long function object, which is no good. defn type hints on the arg vector and in the arg vector are handled by defn macro and are not evaluated

dpsutton 2020-12-18T07:43:16.198500Z

ah ok. that's the secret sauce that i was missing in documentation. I didn't understand why inline typehints could be ^long but on a var like that had to be ^Long. I thought i remembered that the clojure.core/long function was identified with the `'long' and classname typehint, but that might be cljs or might just be me just completely making stuff up

dpsutton 2020-12-18T07:43:50.198700Z

thanks as always for your answers @alexmiller

Gerome 2020-12-18T09:19:28.200500Z

Hello! Is there an equivalent to a tap function in clojure? Something that does this: (fn [f x] (f x) x) ?

Gerome 2020-12-18T09:21:26.202Z

It would be most useful for stuff like (tap println (do-some-stuff data)) and is a little easier to read and write than what I usually do: (let [x (do-some-stuff data)] (println x) x).

2020-12-18T09:35:18.203200Z

@gerome.bochmann Hi! Maybe using doto, a little bit like in this example: https://twitter.com/lambdaisland/status/1338427872617250816

Gerome 2020-12-18T09:37:18.203600Z

Oh! This is awesome! Thanks! @admin055

👍 1
Lu 2020-12-18T11:34:23.208400Z

Hi! I am not sure this question belongs here, so if you think there’s a better channel please tell me. Anyways, for all mac users, how do you switch jdk globally - not limited to current shell. I can’t seem to find a one script solution i.e. switch jdk 11 that sets the jdk globally.

phoenixjj 2020-12-18T11:42:55.209900Z

I have set JAVA_HOME in ~/.zshrc.

Lu 2020-12-18T11:53:43.212500Z

I see but say you change JAVA_HOME in current shell, then when a new shell is opened it will default to whatever is in ~/.zshrc. To fix this I resorted to some emacs helpers that allow me to manage the jdk version emacs uses from within emacs itself.. but yeah ideally it would be great to be able to do this in a shell. Thanks for the response anyways 🙂

dharrigan 2020-12-18T12:01:54.213900Z

Maybe sdkman would help?

dharrigan 2020-12-18T12:02:09.214300Z

Or is that shell only?

phoenixjj 2020-12-18T12:02:28.214700Z

I am just guessing but if you set java path in project.clj/profile.clj cider should pick that version provided you are using lein

borkdude 2020-12-18T12:17:35.215300Z

@lucio Check out jenv

☝️ 1
Lu 2020-12-18T12:18:59.216Z

awesome it seems it does exactly what I need. Will try it out now.. @borkdude thanks!

Lu 2020-12-18T12:20:16.216100Z

It kinda helps but from the docs it doesn’t seem you can change a default besides right after installing a new jdk through the prompt 🙂

simongray 2020-12-18T12:24:21.217100Z

@lucio I’ll vouch for jenv too.

👍 1
kwladyka 2020-12-18T17:38:00.220600Z

Java file foo/bar/Class.java is foo.bar.Class. Clojure

(ns foo.bar)
(def custom (proxy [SimpleFormatter] []
                (format [^LogRecord record]
                  (println record)
                  "bar")))
is? What is the “path” to custom? It looks it is not api.logs.mycustom. I am trying to figure out settings for java.util.logging java.util.logging.ConsoleHandler.formatter = api.logs.custom How should I prepare this class to get this “path”?

kwladyka 2020-12-19T14:17:40.238Z

(def ANSI-colours {:reset "\u001B[0m"
                   :black "\u001B[30m"
                   :red "\u001B[31m"
                   :green "\u001B[32m"
                   :yellow "\u001B[33m"
                   :blue "\u001B[34m"
                   :purple "\u001B[35m"
                   :cyan "\u001B[36m"
                   :white "\u001B[37m"})

(defn getCause [^Throwable thrown]
  (when-let [cause ^Throwable (.getCause thrown)]
    {:class (.getClass cause)
     :message (.getMessage cause)
     :stack-trace (clojure.string/join "\n" (map str (.getStackTrace cause)))}))

(defn getThrown [^LogRecord record]
  (when-let [thrown ^Throwable (.getThrown record)]
    (let [cause (getCause thrown)]
      (cond-&gt;
        {:class (.getClass thrown)
         :message (.getMessage thrown)
         :stack-trace (clojure.string/join "\n" (map str (.getStackTrace thrown)))}
        cause (assoc :cause cause)))))

(defn record-&gt;map [^LogRecord record]
  (let [thrown (getThrown record)
        level (.getLevel record)]
    (cond-&gt;
      {:severity (.getName level)
       :message (.getMessage record)
       :logger-name (.getLoggerName record)}
      (= Level/SEVERE level) (assoc "@type" "<http://type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent|type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent>")
      thrown (assoc :thrown thrown))))

(def root-logger (let [root-logger (.getLogger (LogManager/getLogManager) "")
                       formatter (proxy [SimpleFormatter] []
                                   (^String format [^LogRecord record]
                                     (let [color (if (= Level/SEVERE (.getLevel record))
                                                   (:red ANSI-colours)
                                                   (:white ANSI-colours))]
                                       (str color
                                            (json/write-value-as-string
                                              (record-&gt;map record)
                                              #_(json/object-mapper {:pretty true}))
                                            (:reset ANSI-colours)
                                            (System/getProperty "line.separator")))))
                       console-handler (doto (ConsoleHandler.)
                                         ;(.setUseParentHandlers false)
                                         (.setFormatter formatter))]

                   (doseq [handler (.getHandlers root-logger)]
                     (.removeHandler root-logger handler))

                   (doto root-logger
                     (.setLevel Level/INFO)
                     (.addHandler console-handler))))
here I share with you my solution for jsonPayload for google cloud logging

dpsutton 2020-12-18T17:40:48.220900Z

(import '(java.util.logging ConsoleHandler
                            Logger
                            LogRecord
                            SimpleFormatter
                            Level))

(defonce ^Logger logger (doto (Logger/getLogger "clojure")
                          (.setUseParentHandlers false)
                          (.addHandler
                           (doto (ConsoleHandler.)
                             (.setLevel Level/ALL)
                             (.setFormatter
                              (proxy [SimpleFormatter] []
                                (format [^LogRecord record]
                                  (let [sb (StringBuilder.)]
                                    (.append sb "#:log{")
                                    (.append sb ":z ")
                                    (.append sb (pr-str (str (java.time.Instant/ofEpochMilli (.getMillis record)))))
                                    ;; (.append sb " :b ")
                                    ;; (.append sb (format "%02d" (.getSequenceNumber record)))
                                    ;; (.append sb " :c ")
                                    ;; (.append sb (format "%02d" (.getThreadID record)))
                                    (.append sb " :v :")
                                    (.append sb (.toLowerCase (.getName (.getLevel record))))
                                    (.append sb " :n ")
                                    (.append sb (.getSourceClassName record))
                                    (.append sb " :l ")
                                    (.append sb (.getSourceMethodName record))
                                    (.append sb " :m ")
                                    (.append sb (pr-str (.getMessage record)))
                                    (doseq [p (seq (.getParameters record))
                                            :when (map? p)
                                            [k v] (seq p)]
                                      (doto sb
                                        (.append " ")
                                        (cond-&gt;
                                            (namespace k) (.append (pr-str k))
                                            (not (namespace k)) (-&gt; (.append ":_/") (.append (name k))))
                                        (.append " ")
                                        (.append (pr-str v))))
                                    (when-let [t (.getThrown record)]
                                      (.append sb " :thrown \n")
                                      (.append sb (pr-str t)))
                                    (.append sb "}\n")
                                    (str sb)))))))))

(doseq [level '[severe warning info config fine finer finest]]
  (intern *ns*
          level
          (fn
            ([&amp;form &amp;env msg]
             (let [ns (name (ns-name *ns*))]
               `(.logp logger ~(symbol "java.util.logging.Level" (.toUpperCase (name level))) ~ns ~(str (:line (meta &amp;form))) (print-str ~msg))))
            ([&amp;form &amp;env msg parameters]
             (let [ns (name (ns-name *ns*))]
               `(let [p# ~parameters]
                  (if (map? p#)
                    (.logp  logger
                            ~(symbol "java.util.logging.Level" (.toUpperCase (name level)))
                            ~ns
                            ~(str (:line (meta &amp;form)))
                            (print-str ~msg)
                            p#)
                    (.logp  logger
                            ~(symbol "java.util.logging.Level" (.toUpperCase (name level)))
                            ~ns
                            ~(str (:line (meta &amp;form)))
                            (print-str ~msg)
                            ^Throwable p#)))))))
  (.setMacro ^clojure.lang.Var (ns-resolve *ns* level)))

dpsutton 2020-12-18T17:41:12.221100Z

i modified that snippet so it uses the fully qualified java.util.logging.Level/ stuff so you can use it from other namespaces

p-himik 2020-12-18T17:41:54.221300Z

Just in case you still need to access Clojure vars from Java: https://clojure.org/reference/java_interop#_calling_clojure_from_java

kwladyka 2020-12-18T17:49:02.221700Z

@dpsutton Do you know how to refer to this from configuration file for JUL? I know how to achieve this in pure Clojure / Java, but I wanted to use config file to point the formatter.

kwladyka 2020-12-18T17:49:12.221900Z

But I don’t know how to point to this heh

kwladyka 2020-12-18T17:49:46.222100Z

java.util.logging.ConsoleHandler.formatter = api.logs.mycustom <- exactly this line of config

dpsutton 2020-12-18T17:49:53.222300Z

no i was just playing around with it this morning. I think the point of it is that it uses JUL and ignores the config as it makes its own logger named clojure and its configured inline there

kwladyka 2020-12-18T17:50:49.222500Z

maybe I will end with this. I wanted to have only formatter in Clojure and set everything in config file

kwladyka 2020-12-18T17:51:03.222700Z

but I don’t know how to refer to the class made by proxy in clj file heh

dpsutton 2020-12-18T17:51:39.223Z

you want to use this from java?

kwladyka 2020-12-18T17:51:58.223200Z

not sure what you mean

dpsutton 2020-12-18T17:52:12.223400Z

why are you writing a line like this: java.util.logging.ConsoleHandler.formatter = api.logs.mycustom ?

kwladyka 2020-12-18T17:52:39.223600Z

(ns foo.bar)
(def custom (proxy [SimpleFormatter] []
                (format [^LogRecord record]
                  (println record)
                  "bar")))
I want to use above here in JUL.properties: java.util.logging.ConsoleHandler.formatter = api.logs.custom

kwladyka 2020-12-18T17:53:12.223900Z

JUL.properties is config file for java.util.logging

kwladyka 2020-12-18T17:53:24.224100Z

like log4j2.properties

dpsutton 2020-12-18T17:53:29.224400Z

dunno sorry

kwladyka 2020-12-18T17:53:37.224600Z

no problem

kwladyka 2020-12-18T17:54:04.224800Z

you can use config file by "-Djava.util.logging.config.file=JUL.properties"

kwladyka 2020-12-18T17:54:25.225Z

so then I could only use Clojure for Formatter code

kwladyka 2020-12-18T17:54:37.225200Z

And keep everything native

dpsutton 2020-12-18T17:57:03.225400Z

i think this snippet is kinda intended to just be evaluated in a random namespace and used there almost transiently. its a quick in the repl thing and not really what you're looking for. and for your usecase i think you'd need to aot the file for it to work as that classfile needs to exist already (but i'm not sure about that)

kwladyka 2020-12-18T18:00:42.225800Z

hmm

kwladyka 2020-12-18T18:08:55.226Z

BTW What / Why are you doing in (doseq [level '[severe warning info config fine finer finest]] ?

kwladyka 2020-12-18T18:09:35.226200Z

Why not just overwrite default root logger?

kwladyka 2020-12-18T18:42:53.226600Z

(let [root-logger (.getLogger (LogManager/getLogManager) "")
        formatter (proxy [SimpleFormatter] []
                    (format [^LogRecord record]
                      (println record)
                      "bar"))
        console-handler (doto (ConsoleHandler.)
                          ;(.setUseParentHandlers false)
                          (.setFormatter formatter))]

    (doseq [handler (.getHandlers root-logger)]
      (.removeHandler root-logger handler))

    (doto root-logger
      (.setLevel Level/INFO)
      (.addHandler console-handler)))
this is what I have so far

kwladyka 2020-12-18T18:44:47.226800Z

Do you understand when use Formatter vs SimpleFormatter?

borkdude 2020-12-18T22:40:50.229100Z

I know this isn't a very scientific benchmark, but I get the same-ish numbers with just nth and a self inlined version (.nth ^clojure.lang.Indexed [0 1] 1) here:

user=&gt; (time (dotimes [_ 100000000000] (.nth ^clojure.lang.Indexed [0 1] 1)))
"Elapsed time: 1937.860586 msecs"
nil
user=&gt; (time (dotimes [_ 100000000000] (nth [0 1] 1)))
"Elapsed time: 1901.539964 msecs"
which kind of surprised me since RT/nth does an extra step right? Maybe that step is just very very cheap or JIT-optimized.

phronmophobic 2020-12-18T22:42:27.229500Z

just based on the implementation, I'm not sure how much extra it's doing:

(defn nth
  "Returns the value at the index. get returns nil if index out of
  bounds, nth throws an exception unless not-found is supplied.  nth
  also works for strings, Java arrays, regex Matchers and Lists, and,
  in O(n) time, for sequences."
  {:inline (fn  [c i &amp; nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))
   :inline-arities #{2 3}
   :added "1.0"}
  ([coll index] (. clojure.lang.RT (nth coll index)))
  ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))

phronmophobic 2020-12-18T22:43:12.229700Z

how does it compare to the (. clojure.lang.RT (nth ~c ~i ~@nf)) version?

borkdude 2020-12-18T22:43:21.229900Z

I mean the RT#nth version is doing something extra

kwladyka 2020-12-18T22:43:23.230100Z

I don’t have time to dig into it but maybe ^clojure.lang.Indexed make a difference?

2020-12-18T22:53:12.230800Z

why do you think one version is doing something extra?

2020-12-18T22:54:59.231400Z

that kind of thing is, to make a crazily broad claim, basically free

borkdude 2020-12-18T22:55:47.231600Z

Right. And in clojure itself - less free but maybe not so bad either?

borkdude 2020-12-18T22:55:55.231800Z

(instance? ...) I mean

2020-12-18T22:56:11.232Z

it is complicated, but very often exactly the same

borkdude 2020-12-18T22:58:00.232200Z

cool

2020-12-18T22:58:18.232400Z

an instance check compiles to a single jvm instruction in java, in clojure we have a function instance?, and calling that would be more expensive, but several features of clojure (definline, and compiler intrinsics) will usually turn it into a single instruction

borkdude 2020-12-18T22:59:14.232600Z

I don't see any :inline stuff on instance? itself though

2020-12-18T22:59:27.232800Z

(the compiler recognizes some static methods and replaces them with bytecode directly instead of a method call)

borkdude 2020-12-18T22:59:35.233Z

right. clever :)

paulocuneo 2020-12-18T23:00:04.233200Z

how does it compare to (.get ^List [0 1] 1) ?

borkdude 2020-12-18T23:00:40.233400Z

getting something from a list is O(n) right

borkdude 2020-12-18T23:01:00.233600Z

indexed is way faster

borkdude 2020-12-18T23:01:24.233800Z

but it depends on what the List implementation is

2020-12-18T23:01:36.234Z

ah, actually it isn't definline and the intrinsics stuff

borkdude 2020-12-18T23:01:57.234200Z

in Clojure a list is a linked list, with O(n) access - but in Java it could also be an ArrayList which probably is indexed

2020-12-18T23:02:04.234400Z

the compiler special cases known calls to instance?

borkdude 2020-12-18T23:02:44.234600Z

that explains why it's so blazing fast

2020-12-18T23:03:24.234800Z

if it sees a call where the first thing is a reference to the var clojure.core/instance? it does the single bytecode instruction (with maybe an extra instruction to box the boolean)

2020-12-18T23:04:26.235Z

that must predate the more general pattern of using definline to emit a static method call, and the compiler having a mapping between certain static methods and the bytecode to emit instead

2020-12-18T23:08:46.235500Z

it is important to keep in mind instance? is not the same thing as satisfies?

borkdude 2020-12-18T23:08:59.235700Z

yeah - satisfies? can be slow I've heard

2020-12-18T23:10:53.236100Z

reminds me to remove a call to satisfies? from a patch