mount

tolitius 2017-03-17T01:58:22.050150Z

> the db function should be wrapped in a defstate also @lucian303 this would not be a good solution

tolitius 2017-03-17T01:58:31.050978Z

calls to db are just functions

tolitius 2017-03-17T01:59:14.054383Z

defstate should be only used for stateful things (I/O, thread pools, etc.)

tolitius 2017-03-17T02:00:14.059852Z

take a look at this sample web app: https://github.com/tolitius/stater/tree/master/neo

lucian303 2017-03-17T03:13:25.416044Z

@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.

yenda 2017-03-17T11:30:47.518948Z

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

yenda 2017-03-17T12:02:29.776170Z

I just saw in the hubble repo that there is an on-change function I will look into that

tolitius 2017-03-17T12:02:53.779733Z

@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 2017-03-17T12:04:08.790379Z

@yenda how / where do you check > if a connection becomes available ?

yenda 2017-03-17T12:04:38.794510Z

@tolitius it eventually becomes available in the config at some point during the startup of the application

tolitius 2017-03-17T12:05:19.800235Z

> I want to add logging to a message broker and stop logging to console?

yenda 2017-03-17T12:07:33.819110Z

not necessarily does it matter ?

tolitius 2017-03-17T12:09:00.831177Z

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.

tolitius 2017-03-17T12:09:30.835599Z

i.e. I am not sure why you would need a state for a logger, as this is usually baked into the logger lib

yenda 2017-03-17T12:10:01.839945Z

I use my own logging library

tolitius 2017-03-17T12:10:44.846057Z

ah.. ok, that's interesting, why not any of libs that exist in JVM: log4j2, logback, etc. ?

yenda 2017-03-17T12:11:40.854081Z

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...

tolitius 2017-03-17T12:12:02.857189Z

right, so this is a function, not a state

tolitius 2017-03-17T12:12:42.862853Z

but what do you usually use to log?

tolitius 2017-03-17T12:12:58.865316Z

i.e. clojure.tools.logging / timbre / ?

yenda 2017-03-17T12:13:08.866693Z

my macros

tolitius 2017-03-17T12:13:50.873034Z

macros is evaluated at compile time: i.e. there is no way to pass it a connection / open file

tolitius 2017-03-17T12:14:10.875675Z

do you have a dependency on clojure.tool.logging?

tolitius 2017-03-17T12:14:26.878240Z

in boot.biuld, project.clj?

yenda 2017-03-17T12:14:52.881923Z

no my macros then expand to a call to my log function

tolitius 2017-03-17T12:15:29.887404Z

ok, and what does that function do, does it use something like clojure.tool.logging?

yenda 2017-03-17T12:15:59.891676Z

no it writes to stdout and publish the message if the broker connection is available

tolitius 2017-03-17T12:16:47.898437Z

interesting, how do you control the log level: i.e. info vs. trace vs. error, etc.. ?

tolitius 2017-03-17T12:16:51.899051Z

or you don't need to?

yenda 2017-03-17T12:16:52.899153Z

I am currently experimenting by doing a rewrite with mount

tolitius 2017-03-17T12:17:11.901752Z

yea, sure, I'd just like to understand the problem better

yenda 2017-03-17T12:17:18.902799Z

before log would call an append multimethod dispatching to the list of appenders

tolitius 2017-03-17T12:17:51.907670Z

I see, so you have a list of appenders and those are states because they need access to external resources?

yenda 2017-03-17T12:18:01.908948Z

yes

yenda 2017-03-17T12:18:49.915729Z

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

tolitius 2017-03-17T12:19:02.917527Z

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

yenda 2017-03-17T12:19:49.923972Z

why is it preferable ?

yenda 2017-03-17T12:25:39.975306Z

I thought this was similar to my use-case https://github.com/tolitius/stater/blob/master/hubble/src/clj/hubble/watch.clj

yenda 2017-03-17T12:44:52.151397Z

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

yenda 2017-03-17T12:45:03.153090Z

and only this one will mutate and the logger will not be a state at all

tolitius 2017-03-17T12:48:11.183082Z

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

tolitius 2017-03-17T12:50:13.203050Z

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

yenda 2017-03-17T12:50:58.210701Z

well actually in some cases I would not want all appenders it can be configured

tolitius 2017-03-17T12:52:56.231049Z

right, but you would read that config at the start time, right?

yenda 2017-03-17T12:53:42.238815Z

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

tolitius 2017-03-17T12:55:25.256885Z

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))

yenda 2017-03-17T12:55:26.257043Z

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

tolitius 2017-03-17T12:56:57.272967Z

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

tolitius 2017-03-17T12:57:14.275780Z

so it merges the one in consul on top of the one in a file

tolitius 2017-03-17T12:57:48.281803Z

i.e. this way once the config is loaded:

(defstate appenders :start (do-with config))
knows which one to use

tolitius 2017-03-17T12:58:07.285045Z

and appenders will be started after the config since it depends on it

yenda 2017-03-17T12:58:41.291199Z

yes 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

yenda 2017-03-17T13:00:06.307510Z

the problem is that it needs to be really dynamic to accomodate with different use cases

yenda 2017-03-17T13:01:22.322423Z

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

tolitius 2017-03-17T13:02:02.330052Z

I see, so you want to be able to plug appenders at runtime

tolitius 2017-03-17T13:02:19.333114Z

not necessarily delaying them on start

yenda 2017-03-17T13:02:46.338287Z

yes I want to make things more dynamic and also add mount as the system management library

yenda 2017-03-17T13:03:04.341582Z

basically the project is a library for config and logging and messaging for microservices

yenda 2017-03-17T13:03:45.349493Z

so a microservice code would define a few defstate and mount/start in the main

yenda 2017-03-17T13:04:35.358722Z

and be able to mount/stop during dev

yenda 2017-03-17T13:05:23.367440Z

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

tolitius 2017-03-17T13:05:44.371250Z

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

tolitius 2017-03-17T13:07:05.386327Z

> you can't restart cleanly in the repl during development why?

yenda 2017-03-17T13:09:16.410380Z

for instance tif you close the rabbitmq connection you also close open channels

yenda 2017-03-17T13:09:58.418286Z

restarting it won't make the rabbitmq appender work again

tolitius 2017-03-17T13:11:23.434751Z

> you also close open channels what do you mean?

yenda 2017-03-17T13:12:31.447659Z

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

tolitius 2017-03-17T13:13:59.464957Z

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)

yenda 2017-03-17T13:14:37.472578Z

sorry I wasn't clear I was talking about the current working solution that doesn't use mount

yenda 2017-03-17T13:15:23.481694Z

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

tolitius 2017-03-17T13:16:13.491915Z

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

tolitius 2017-03-17T13:17:08.502685Z

or (mount/stop-except #'app/rabbit-conn) in cases you don't need to stop / start it

tolitius 2017-03-17T13:18:00.512961Z

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

tolitius 2017-03-17T13:19:32.530713Z

but usually you would just do (mount/start) / (mount/start) and it would successfully restart this connection along with the channels

yenda 2017-03-17T13:19:35.531385Z

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

tolitius 2017-03-17T13:20:24.541429Z

I usually keep something like this around for dev: https://github.com/tolitius/stater/blob/master/smsio/dev/dev.clj

yenda 2017-03-17T13:22:58.572738Z

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

yenda 2017-03-17T13:31:51.685627Z

I think I need to play a bit more with mount first to find a better design

tolitius 2017-03-17T14:12:36.272859Z

sure

tolitius 2017-03-17T14:12:52.276898Z

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))

tolitius 2017-03-17T14:13:36.287856Z

+ a possible logger / appenders state that would be a vector behind an atom

tolitius 2017-03-17T14:14:47.306026Z

@yenda: just curious, why do you need to implement your own logging solution vs. using clojure.tools.logging / timbre / slj4j, etc..

yenda 2017-03-17T14:20:23.394390Z

Convenience and structured logs, it captures variables from the scope as well as informations that other loggers don't

tolitius 2017-03-17T14:23:47.448659Z

but since this is done by your macro, can it just wrap an existing logging solution?

tolitius 2017-03-17T14:24:03.452930Z

to add "variables from the scope as well as informations that other loggers don't"

yenda 2017-03-17T14:31:02.567730Z

It wouldn't really add any benefit

tolitius 2017-03-17T14:33:26.608306Z

for one you would not have to deal with appenders

yenda 2017-03-17T14:34:02.618004Z

As far as I remember you still need to configure the appenders in timbre

yenda 2017-03-17T14:34:13.621205Z

And you can't do it at runtime

tolitius 2017-03-17T14:46:08.819152Z

in both, log4j and logback you can

yenda 2017-03-17T15:40:46.754865Z

I'll think about it. Thanks for all the help !

tolitius 2017-03-17T17:21:34.392359Z

sure, good luck. let me know where you end up

yenda 2017-03-17T19:10:15.048605Z

@tolitius for now I didn't work on the error handling part but I went again with the bootstrap namespaces method to avoid the circular dependencies

yenda 2017-03-17T19:10:48.056220Z

and it work quite nicely it starts/stops and everything including logging seems to work nicely