This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-09
Channels
- # announcements (23)
- # babashka (7)
- # beginners (69)
- # biff (5)
- # calva (12)
- # cider (10)
- # cljfx (9)
- # clojure (60)
- # clojure-austin (1)
- # clojure-europe (14)
- # clojure-korea (2)
- # clojure-losangeles (2)
- # clojure-madison (1)
- # clojure-nl (1)
- # clojure-norway (23)
- # clojure-uk (7)
- # clojuredesign-podcast (16)
- # clojurescript (40)
- # datomic (8)
- # gratitude (4)
- # mount (3)
- # nrepl (2)
- # off-topic (38)
- # pathom (3)
- # releases (1)
- # ring (8)
- # shadow-cljs (7)
Hoping someone here can look at my code.
I have these two routes:
["/admin-login-verify/:token" {:get {:handler page-handlers/admin-login-verify
:middleware [session/wrap-session {:store (cookie-store/cookie-store)}]}}]
["/admin" {:get {:handler page-handlers/admin
:middleware [session/wrap-session middleware/wrap-admin-auth {:store (cookie-store/cookie-store)}]}}]
My middleware function is:
(defn wrap-admin-auth
"Middleware function to verify admin authentication"
[handler]
(fn [req]
(let [session (get-in req [:session])
_ (log/info "wrap-admin-auth session:" session)
user-id (:user-id session)
_ (log/info "wrap-admin-auth user-id:" user-id)
user (when user-id (user/get-by-id user-id))
_ (log/info "wrap-admin-auth user:" user)]
(if user
(handler (assoc req :user user))
(response/redirect "/admin-login")))))
The first handler is this:
(defn admin-login-verify [req]
(let [token (get-in req [:path-params :token])
session (get-in req [:session])
user (user/verify-login-token token)
_ (log/info "admin-login-verify user:" user)]
(if user
(do
(user/delete-login-token (:id user))
(log/info "admin-login-verify session:" session)
(let [updated-session (-> session
(assoc :user-id (:id user)))
_ (log/info "admin-login-verify updated-session:" updated-session)]
{:status 302
:headers {"Location" "/admin"}
:body ""
:session updated-session}))
(response/bad-request "Invalid login token"))))
We never get to the second handler admin
as wrap-admin-auth
see the session cookie as seen by wrap-admin-auth
is an empty map.
The session stuff by default stores in memory, and every time you call wrap-session you get a new store
The way reitit puts middleware at the leaves means each of your handlers is getting a separate session store
ok, so how could I rewrite the reitit handlers definitions?
If I don't specify to read in and un-encrypt the session cookie each time, how does the middleware know to do it if I don't use the wrap-session middleware?
You need to apply the session middleware once at the top of the tree of routes, not on each leaf
wow. I have some routes that shouldn't have cookies. Only the admin routes need it.
You can create the session store once outside, then pass it in to each wrap-session so they share the store
But I would recommend asking in #C7YF1SBT3, I am sure they have some prepackaged thing for this, I've never used reitit so don't really know
thanks! I think I've found a way to set the cookie only once by using the {:data {:middleware
method for all my routes. This gets me past part of the problem. Now I need to figure out how to only set this for a subset of my routes.
@U0NCTKEV8 really appreciate your response. I've gotten past the worst part. 🙂.
Hi friends, I am still finding my way around the build / project system with all these moving parts and think i'm missing something so simple it's hard to google effectively. Maybe y'al lcan help? 🙏
Problem is basically this: i have a project with a main namespace that I am distributing as an uberjar, built with tools.build
. There is an optional dependency that is loaded with dynaload
in only certain settings; this is not in the standard :deps
but rather in :extra-deps
in an alias.
I can build the uberjar with a "basis" that includes this dependency by specifying :aliases
in create-basis, but it is still not "required" and clojure fails if it hits that code path. Outside of the uberjar setting, i use clj -Sdeps
and it does get required. However I am not seeing hwo to do equivalent thing when the invocation is java uber.jar
.
What's the right way to get the uberjar to run with an assumd -Sdeps
addition? hopefully simplified because the alias already declares that?
It's going to depend on a couple of things: how exactly you are using dynaload
to make the dependency available; and how you are requiring/resolving the symbols in that dependency in your code.
You can use requiring-resolve
directly in Clojure to load & resolve a fully-qualified symbol. It will throw if the ns can't be loaded, so you could use that in your code that refers to symbols in the optional dependency. It will load them from the classpath (i.e., the uberjar if you're building it with that dependency bundled into it).
thankss @U04V70XH6 i will try this out. For reference this iis how dynaload is invoked: https://github.com/OpenGen/GenSQL.query/blob/32adb67ff07c1bba67255384adbe30d80d4e0f9f/src/gensql/query/db.cljc#L13-L14
It simply loads a parsing function from the optional library.
and i am trying to start with just replacing dynaload
with requiring-resolve
.
Thanks again @U04V70XH6. When I drop-in replace requiring-resolve
, it seems to work in the JAR setting, but clj
w ith -Sdeps
now gives:
Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:445).
jdk.incubator.foreign.MemoryAddress
Does requiring-resolve need special handling in t his context?You might need different dependencies on different platforms? I haven't worked with stuff like that on the JVM...
@U06NHGRJ4E4 It might be good to ping @U04V15CAJ about dynaload questions. Also, it doesn't look like requiring-resolve
works/exists in cljs, so that's a non-starter for us. (It looks like the sppl lib requires libpython-clj, so it's clj-only, but we'll probably have other cross-platform libs in the future.)
How to use time
macro
I tried sample (time (Thread/sleep 1000))
which should give
"Elapsed time: 1002.4437 msecs"
But after a second it just gives nil
I using emacs and cider
Tha's because Thread/sleep is returning nil and time macro is returning what the inner form is returning.
If you're benchmarking use https://github.com/hugoduncan/criterium
oh. I was checking console (terminal app in macos) but it was empty. I now looked in cider-repl buffer and it is now showing. thanks, l'll take a look at criterium as well
Can some please me understand the messages Goal - I'm trying to build a cli tool for exporting sms to a file Details - MacOS (java installed via brew), polylith architecture (refered https://github.com/furkan3ayraktar/clojure-polylith-realworld-example-app for build config) Steps - 1. Function to fetch sms from a sqlite file and dump them to a file 2. Create Uberjar 3. Run uberjar 4. Check file data tldr - Step 1 is working fine. I ran function directly (emacs + cider) and Step 4 is as expected but I get error when going through step 2 and 3 When I run Step 2, I get message on console
Skipping pom.xml because :lib and/or :version were omitted...
Copying src, resources...
Compiling in.arthya.cli.core...
operator.clj:172 recur arg for primitive local: sum is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: sum
Warning: environ value /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home for key :java-home has been overwritten with /opt/homebrew/Cellar/openjdk/21.0.2/libexec/openjdk.jdk/Contents/Home
Building uberjar target/sms-export-cli.jar...
Uberjar is built.
and when I run step 3, I get error (https://pastebin.com/Ki3dhEhZ)
java -jar projects/sms-export-cli/target/sms-export-cli.jar
Starting export of sms
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class clojure.lang.IPersistentMap (java.lang.Integer is in module java.base of loader 'bootstrap'; clojure.lang.IPersistentMap is in unnamed module of loader 'app')
at clojure.core$ex_info.invokeStatic(core.clj:4807)
at in.arthya.sms.core$messages$fn__13938$fn__13940.invoke(core.clj:65)
at in.arthya.sms.core$messages$fn__13938.invoke(core.clj:65)
...
The loop in question is
(defn find-pattern [bytes pattern]
(let [len (alength bytes)
pat-len (alength pattern)]
(loop [i 0]
(when (< (+ i pat-len) len)
(if (java.util.Arrays/equals pattern (java.util.Arrays/copyOfRange bytes i (+ i pat-len)))
i
(recur (inc i)))))))
I've used chatgpt to convert rust code to clojure. https://github.com/ReagentX/imessage-exporter/blob/develop/imessage-database/src/util/streamtyped.rs code reads data from sqlite and parses streamtyped data to string
1. I didn't understand what sum and/or long value the error message is talking about and what to research to fix it
2. How come the fn runs in repl and not via uberjar@UMY8HG1HR https://github.com/nickdex/arthya/blob/main/components/sms/src/in/arthya/sms/core.clj It is couple of days since I pushed. I’m AFK right now. I’ll push latest in couple of hours
server=> (let [rowid 3]
(ex-info "Error in parsing " rowid))
Execution error (ClassCastException) at metabase.server/eval5155 (REPL:68).
class java.lang.Long cannot be cast to class clojure.lang.IPersistentMap (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IPersistentMap is in unnamed module of loader 'app')
vs
server=> (let [rowid 3]
(ex-info "Error in parsing " {:row-id rowid}))
#error {
:cause "Error in parsing "
:data {:row-id 3}
:via
[{:type clojure.lang.ExceptionInfo
:message "Error in parsing "
:data {:row-id 3}
:at [metabase.server$eval5157 invokeStatic "NO_SOURCE_FILE" 70]}]
...
your error is thrown from clojure.core$ex_info
from in.arthya.sms.core$messages$fn__13938$fn__13940.invoke(core.clj:65)
and it’s problem is that it doesn’t know how to turn a Long
into a map
because ex-info
has the following signature:
server=> (doc ex-info)
-------------------------
clojure.core/ex-info
([msg map] [msg map cause])
Create an instance of ExceptionInfo, a RuntimeException subclass
that carries a map of additional data.
nil
@U11BV7MTK Thanks!! it working now. Based on this then I assume that these exceptions are coming only while running uberjar, Since I'm not seeing any exception message on terminal app or cider-repl
yes you are hitting errors when running the jar that you are not hitting from your dev repl
howdy! -- I am following this tutorial: https://ericnormand.me/guide/clojure-web-tutorial -- but trying to do it with deps
instead of lein
as a bit of an experiment. When I run the tiny project with lein
I don't get any errors about logging, but when I run it with clj
I get:
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See for further details.
While I am curious about what dependency I can load to fix this.. I am more curious about why it doesn't happen with lein
? Is lein
loading more dependencies than are listed in the defproject
block? Both ways of running the app I have only specified ring-core
and ring-jetty-adapter
as dependencies. Thanks for any insights! I'm happy to do any required reading but my blind googling hasn't turned up anything that has helped me understand yet.org.slf4j/slf4j-nop {:mvn/version "1.7.36"}
will provide an explicit "no-op" logging implementation for SLF4J but you can just ignore the warning.
Java logging is a giant hot mess and Leiningen probably uses a combination of logging dependencies to control logging explicitly, whereas tools.deps
(behind the CLI and deps.edn
) leaves it up to the "user", which is often tooling.
interesting! that's kinda what I figured was going on. If I put that no-op dep in..will I not see logs from jetty? I am.. hoping to see web requests get logged.. and was assuming jetty would do that but.. maybe I need to put in a no-op logger for the libraries dep then setup my own request logging in my app?
Yeah, here's Leiningen specifically dealing with logging: https://codeberg.org/leiningen/leiningen/src/branch/main/leiningen-core/project.clj#L13-L19
ohhh I didn't even think to check lein's deps but of course it's just a piece of code with its own deps
I generally just ignore the No SLF4J
warning... unless I specifically want to handle logging in my own app code in a particular way.
am I correct in my thinking that if i provided an slf4j
provider then jetty would log requests to standard out?
It depends on many things. Different Java libraries use different logging solutions so you usually end up having to bridge things into a specific library. And then there's your own application logging.
hmm ok so I guess I can see why you'd provide the no-op to silence the warning (or just ignore it) then do your own application logging for things you specifically care about
At work, we use tools.logging
for our application, and then we bridge everything to log4j2 (because we prefer how that is configured):
org.clojure/tools.logging {:mvn/version "1.3.0"}
;; use log4j 2.x:
org.apache.logging.log4j/log4j-api {:mvn/version "2.23.1"}
;; bridge into log4j:
org.apache.logging.log4j/log4j-1.2-api {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-jcl {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.23.1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.23.1"}
and then we force tools.logging
to select log4j via a JVM option:
-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory
Java logging is just awful 😞does that mean any of your deps that happen to use any of those other libraries will end up logging through tools.logging?
i guess ultimately it all goes through log4j
Our application explicitly logs via tools.logging
, which uses log4j2 because of the JVM option, then any 3rd party logging via Java libraries will be routed to log4j2, regardless of how they log: log4j 1.x, java.commons.logging, java.util.logging, slf4j 1.x, or slf4j 2.x.
This is an alternative setup (that routes everything to slf4j 1.x), written four years ago: https://lambdaisland.com/blog/2020-06-12-logging-in-clojure-making-sense-of-the-mess (before slf4j 2.x came out, I guess).
So, bottom line, for now it's probably best to just ignore the whole thing until you're a bit further down the learning path and you actually want to do logging in your application 🙂
2020 was 4 years ago 🤯 -- thank you I will read this and keep trudging along!
really appreciate the help
https://github.com/BrunoBonacci/mulog provides a simpler and very effective approach to logging. I've used it very successfully in several commercial projects.
@U05254DQM How does that handle all the 3rd party library logging?
https://gitlab.com/nonseldiha/slf4j-mulog could be used to include all slf4j logs where those logs provide value, optionally transforming each log message before publishing, e.g ensuring logs are events rather than only a string to simplify searching log messages and provide any additional context.
OK, so given the caveats in the README for that, mulog is completely irrelevant as an answer to the OP's question, right? "This combines 2 approaches. Mulog creates structured events, log lines are text. The union of the 2 might just result in a mess. No MDC or Marker support. PRs welcome though!"
It's like "Oh, I have several logging problems -- here's one more to add on top of that!"
(I'm sure mulog is great for non-beginners when deciding how to do structured logging from their application, but that's not what this thread is about unfortunately)
I assumed knowing a simpler alternative to dealing with the "Java logging giant hot mess" as Sean termed it would have been valuable. It seems this is not the case, my apologies.
@U05254DQM The OP was just concerned about the SLF4J warnings that appear for the CLI but don't appear for Leiningen -- and that's because Leiningen explicitly depends on the no-op implementation to suppress slf4j logging but the CLI does not. Just for future reference if other beginners have this question (which I see come up from time to time in various places, e.g., I have an issue open against http://clojure-doc.org because the latest version of Ring causes this warning and the tutorial, using an earlier version, doesn't mention it).
hi 🙂 if we use futures, do we need to be concerned about using too many? Like, what happens?
hmm "If you are using Clojure's http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/future it will submit your tasks to https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Agent.java#L52. The soloExecutor is created with http://download.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool%28java.util.concurrent.ThreadFactory%29, so it will re-use threads and run as many as possible.
If you create thousands and thousands of long-running futures, yes, you will eventually have a problem. You'll either bog down your machine or you'll run out of memory for the allocation of new thread stacks. In general in the real world, it's rarely a problem. You can always drop down to the JVM executor stuff directly and run Clojure functions on pooled executors (which returns Java futures so it's all very similar) if you need that level of control.
Promesa has a decent abstraction around executors so you could use that if you need more control of the thread model: https://funcool.github.io/promesa/latest/promesa.core.html#var-future
What about https://github.com/clj-commons/claypoole ? Just remember from one of @U05PG8XLSNS videos, never used myself