This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-03-17
Channels
- # bangalore-clj (1)
- # beginners (23)
- # boot (141)
- # cider (68)
- # cljs-dev (29)
- # cljsjs (1)
- # cljsrn (11)
- # clojure (150)
- # clojure-austin (3)
- # clojure-berlin (1)
- # clojure-france (2)
- # clojure-greece (13)
- # clojure-italy (5)
- # clojure-russia (49)
- # clojure-spec (15)
- # clojure-uk (45)
- # clojurescript (152)
- # code-art (1)
- # core-async (75)
- # cursive (12)
- # datascript (2)
- # datomic (90)
- # dirac (5)
- # emacs (10)
- # garden (1)
- # hoplon (52)
- # instaparse (4)
- # juxt (2)
- # lein-figwheel (2)
- # lumo (47)
- # mount (94)
- # off-topic (20)
- # om (21)
- # onyx (14)
- # parinfer (19)
- # pedestal (3)
- # protorepl (13)
- # re-frame (5)
- # reagent (20)
- # slack-help (10)
- # spacemacs (8)
- # specter (57)
- # unrepl (11)
- # untangled (3)
- # vim (1)
- # yada (1)
> the db function should be wrapped in a defstate
also
@lucian303 this would not be a good solution
take a look at this sample web app: https://github.com/tolitius/stater/tree/master/neo
@tolitius in my actual code i'm replacing a def w/ a defstate. it's stateful because it depends on the db as well as an external file. so (def ^:dynamic *nwords* (load-dict))
becomes (defstate ^:dynamic *nwords* :start (load-dict))
where load-dict
makes the actual call into the db namespace and also loads the external file. does that seem like a proper use case? i tried to pare all that down for the example on github so it's not apparent there.
is it possible with mount to change a state in a function ? for instance I have a logger that logs to the console and later during startup I want to add logging to a message broker if a connection becomes available
I just saw in the hubble repo that there is an on-change function I will look into that
@lucian303 yes, depending on the use case this is correct. for example if your application has configuration you are reading in, depending on what it is in dict
, it may or may not be loaded as a part of a :start
function of a config
state.
for example, in case you use: https://github.com/tolitius/cprop, it could be a part of:
(defstate config :start (-> (load-config) (merge (load-dict))))
@tolitius it eventually becomes available in the config at some point during the startup of the application
depending on the logging lib you use, this could be simply done with something like logback appenders. For example here is from the SocketAppender
:
Consequently, if the remote server is reachable, then log events will eventually arrive there. Otherwise, if the remote server is down or unreachable, the logging events will simply be dropped. If and when the server comes back up, then event transmission will be resumed transparently. This transparent reconnection is performed by a connector thread which periodically attempts to connect to the server.
i.e. I am not sure why you would need a state for a logger, as this is usually baked into the logger lib
ah.. ok, that's interesting, why not any of libs that exist in JVM: log4j2, logback, etc. ?
because I want to log structured logs and I made macros that capture variables in scope among other things such as project name and version, thread and hostname...
macros is evaluated at compile time: i.e. there is no way to pass it a connection / open file
ok, and what does that function do, does it use something like clojure.tool.logging
?
interesting, how do you control the log level: i.e. info vs. trace vs. error, etc.. ?
I see, so you have a list of appenders and those are states because they need access to external resources?
actually this is not really my problem the logger was one expemple, in general I would like to have a mean to redefine states as the program startsup because I have lots of chicken and egg problems
then I would have that "broker appender" as a state that would always log, but would drop or buffer log messages until the connection is available rather then mutate the logger state
I thought this was similar to my use-case https://github.com/tolitius/stater/blob/master/hubble/src/clj/hubble/watch.clj
is it bad to mutate state in mount ? instead I could just keep the way things were with the multimethod and have the list of available appenders to dispatch to in the config state
it's not "bad", but it is usually better solved on the "function" level. the hubble watcher watches changes from consul: i.e. there is an envoy: https://github.com/tolitius/envoy watcher that triggers the change. In your case, if I understood it correctly, it seems like you'd like to simply delay logging messages to a socket until the connection is there, which could simply be done with message buffer, in which case you won't lose any messages
since you have a list of appenders that you always want to keep around (until the app is stopped) I would not solve it by changing the state, but rather by creating apenders once at the app start
that is the chicken egg problem I was talking about earlier the config can be either a file or fetched from a configuration service that is accessed via rabbitmq
right, so you are reading a config and create you appender state(s) based on it: appender(s) state depends on a config state:
(defstate config :start (load-config))
(defstate appenders :start (do-with config))
to get the config it reads the config string which can be a file url or rabbitmq connection, if it is a rabbitmq connection it fetches the config from there else it looks for a rabbitmq config in the file
I think I see where you are coming from. hubble example does a similar thing: i.e. it has a config in a file + the one in consul
i.e. this way once the config is loaded:
(defstate appenders :start (do-with config))
knows which one to useyes but the trick is that the logging should start first with a console appender and update the list of appenders once new ones are added
the problem is that it needs to be really dynamic to accomodate with different use cases
I have a working version that doesn't use any library: it makes things available as soon as possible using delays and bootstrap namespaces to avoid circular dependencies
yes I want to make things more dynamic and also add mount as the system management library
basically the project is a library for config and logging and messaging for microservices
right now you start with (config/load) which loads the config and starts the broker-connection if one is needed but you can't restart cleanly in the repl during development
than it would probably be better to have an appenders
state which would be a vector behind an atom: i.e. you can start / stop it, and also swap! conj
it whenever you need to add to it. the problem with simply restarting that sate would be other components that are "currently" accessing it at runtime
it's just a rabbitmq thing to connect to rabbitmq you need a tcp connection and then a channel that is some lightweight not thread-safe connection on top of it
if rabbitmq connection
is a state, you don't have to restart it if you don't want to:
(mount/stop-except #'app/rabbit-conn)
(mount/start)
sorry I wasn't clear I was talking about the current working solution that doesn't use mount
I am trying to make a better version that is less complex and has a clearer and cleaner bootstraping and can be started/stopped during dev time
ah, ok. yes, the restart in REPL should not be a problem if you use mount. if you need to restart both a connection and channels, you rabbit state would be {:conn conn :channels channels}
, then your stop function would stop both
or ^{:on-reload :noop}
( https://github.com/tolitius/mount#on-reload ) in case you don't need it to get restarted as the namespace recompiles
but usually you would just do (mount/start)
/ (mount/start)
and it would successfully restart this connection along with the channels
there is no problem on that part before the heavy refactoring I tested it with mount and the old bootstraping model and it was perfect
I usually keep something like this around for dev: https://github.com/tolitius/stater/blob/master/smsio/dev/dev.clj
currently the problem could be put that way: states are defined in multiple steps - appenders: 1. starts with [:console] 2. update appenders when available in config - config: 1. starts reading from param 2. gets file to read or rabbitmq connection 3. fetch config from rabbit when connection is available - rabbit-connection 1. starts when available in config
so far the way I see it is something like:
(defstate config :start (-> (load-config)
(merge (read-rabbit-config))))
(read-rabbit-config)
would read config from rabbit only when needed, otherwise {}
(defstate rabbit-connection :start (r/connect (config :rabbit))
:stop (r/diconnect rabbit-connection))
@yenda: just curious, why do you need to implement your own logging solution vs. using clojure.tools.logging
/ timbre
/ slj4j
, etc..
Convenience and structured logs, it captures variables from the scope as well as informations that other loggers don't
but since this is done by your macro, can it just wrap an existing logging solution?