Clojure:riemann.streams$smap$stream IllegalArgumentException: Key must be integer - clojure

I have a clojure code(riemann) to send an email if certain condition was met. I am facing some issue while passing the event to riemann server.
Riemann code
(let [email (mailer {"......"})]
(streams
(where (service "system_log")
(by :RefNo
(smap
(fn [events]
(let [count-of-failures (count (filter #(= "Failed" (:Status %)) events))]
(assoc (first events)
:status "Failure"
:metric count-of-failures
:total-fail (>= count-of-failures 2))))
(where (and (= (:status event) "Failure")
(:total-fail event))
(email "XXXXX#gmail.com"))prn)))))
O/P in riemann server
WARN [2015-11-18 05:24:49,596] defaultEventExecutorGroup-2-2 - riemann.streams - riemann.streams$smap$stream__3695#7addde9e threw
java.lang.IllegalArgumentException: Key must be integer
at clojure.lang.APersistentVector.assoc(APersistentVector.java:335)
at clojure.lang.APersistentVector.assoc(APersistentVector.java:18)
Update 2:
I simply changed the smap to sreduce. How I should update, since I am newbie to this I am little bit confused about altering the code as per your suggestion
(let [email (mailer {"......"})]
(streams
(where (service "system_log")
(by :RefNo
(sreduce
(fn [events]
(let [count-of-failures (count (filter #(= "Failed" (:Status %)) events))]
(assoc (first events)
:status "Failure"
:metric count-of-failures
:total-fail (>= count-of-failures 2))))
(where (and (= (:status event) "Failure")
(:total-fail event))
(email "XXXXX#gmail.com"))prn)))))
Update 3:
I have updated my code using coalesce and smap has its child. Now its not showing any error but email didn't get triggered. I am getting count-of-failures as 0. I guess count function is not working.
(let [email (mailer {"......"})]
(streams
(where (service "system_log")
(by :RefNo
(coalesce
(smap
(fn [events]
(let [count-of-failures (count (filter #(= "Failed" (:status %)) events))]
(assoc (first events)
:status "Failure"
:metric count-of-failures
:total-fail (>= count-of-failures 2))))
(where (and (= (:status event) "Failure")
(:total-fail event))
(email "XXXXX#gmail.com"))))prn))))

Off the top of my hat, by accepts a vector not a symbol:
(by [:Refno] ...
As a side note, I recommend using REPL (e.g. https://github.com/aphyr/riemann/wiki/playing-with-the-REPL) so you can build your stream processing gradually while testing functions in the REPL. It worked great for me.
Update: I'm also not sure if you shouldn't nest the where inside smap because you're assigning "Failure" but the where runs in parallel to smap so unless I'm missing something, I think it won't see it.
Update 2: I've ran it through the REPL connected to Riemann like this:
(require '[riemann.streams :refer :all])
(def f (stream
(where (service "system_log")
(by :RefNo
(smap
(fn [events]
(let [count-of-failures (count (filter #(= "Failed" (:Status %)) events))]
(prn events)
(assoc (first events)
:status "Failure"
:metric count-of-failures
:total-fail (>= count-of-failures 2))))
#_(where (and (= (:status event) "Failure")
(:total-fail event)))
prn)))))
(f {:RefNo 4444 :service "system_log" :status "Failed"})
It produces the same error that you've got. The error is there because you're assuming that the function passed to smap receives a list of events. It doesn't, it receives a single event (see the prn there). Calling first on a hashmap produces a vector, then trying to assoc using a symbol as a key gives you the error because vectors support only integers.
You cannot count failures this way just like you wouldn't use a regular map in Clojure for this purpose because you need past events.
Here's what I think might be compatible with your smap example.
Either:
Use coalesce http://riemann.io/api/riemann.streams.html#var-coalesce and smap as its child; I think smap will receive a list of events just like you wanted originally. I haven't tried it but there's no reason it shouldn't work.
You can control the time window you need (let's say max 2 failures per hour) by sending events with 1 hour TTL and querying the index within the stream. Here's a complete example: http://riemann.io/howto.html#query-the-index-from-within-a-stream
Apart from the that, I believe :Status should be in lower case. I hope it helps.

Related

Rollup function is not working in riemman when clj-time.core is added

I am using riemann to trigger an email alert which should get triggered only during 3AM to 8PM daily. When I have added clj-time.core function rollup function is not working.
Don't know where I am going wrong. Any help is appreciated.
(tcp-server {:host "127.0.0.1" :port 5555})
(let [userindex1 (default :ttl 300 (update-index (index)))])
(let [email (mailer {....email configuration})]
(streams
(where (service "log")
(smap
(fn [events]
(let [count-of-transaction (count (filter #(= "error" (:type %)) events))]
(event
{
:status "Failure"
:metric count-of-failures
:total-fail (< count-of-failures 2)})))
(where (let [now (clj-time.core/now)]
(and (<= 3 (clj-time.core/hour now) 20)
(= (:status event) "Failure")
(:total-fail event)))
(rollup 1 200
(email "xxx#xx.com")
))prn))))
Thanks in advance

Render resource when PUT

I am using liberator to build a API using Clojure. Given the follow code:
(defresource single-customer [id]
:allowed-methods [:get, :put]
:exists? (fn [_]
(let [e (get #cust/customers (keyword id))]
(if-not (nil? e)
{::entry e})))
:existed? (fn [_] (nil? (get #cust/customers (keyword id) ::sentinel)))
:available-media-types ["application/json"]
:can-put-to-missing? false
:put! (fn [q] (cust/set-as-fraudulent id))
:handle-ok ::entry)
Someone when can tell me if is possible, like the GET request, when I send a PUT request it be redirected to the resource ? "/customer/1" (for example) ?
Looking at the liberator decision graph, :put! can lead to either:
:handle-created (if :new?)
:handle-no-content (if not :new? and not :respond-with-entity?)
:handle-ok (if not :new, but :respond-with-entity?)
Try implementing :put! so it stores the entity as ::entry, and :handle-created similar to your current :handle-ok.

Event count at certain time interval in riemann

I have to check the number of count appearing in an event at each interval of every 30 seconds. If the count is greater than 5 means, I need to trigger an email.
I am using the below code, but email didn't get triggered.
(let [userindex1 (default :ttl 300 (update-index (index)))]
(streams
prn
userindex1))
(streams
(where (and (service "system_log")
(not (expired? event)))
; fixed-time-window sends a vector of events out every 30 seconds
(fixed-time-window
30
; smap passes those events into a function
(smap
(fn [events]
;Calculate the no of count of events for failure
(let [numberofFailure (count (filter #(="IE" (:description %)) events))]
{:status "login failures"
:metric numberofFailure
:totalFail (boolean(numberofFailure > 5))}
(streams
prn
numberofFailure))))
;check if the variable status is true if condition satisfied then trigger an email
(let [email (mailer {:host "smtp.gmail.com"
:port 25
:user "aaaaa"
:pass "bbbbb"
:auth "true"
:subject (fn [events]
(clojure.string/join ", "
(map :service events)))
:from "abc#gmail.com"})]
(streams
(where (and (:status "login failures")
(:totalFail true))
(email "123#gmail.com")))))))
Where am I going wrong?
There are a couple of issues here. I'll try to address some of them, then post a minimal working example:
The first fn passed to smap should return an event. That event can be created with event or by assoc'ing into one of the received events. In your sample a plain map is created (which would not work, it's not a proper event), but that's even lost because then streams is called (which AFAIK should only be called at the top level). So instead of:
(smap
(fn [events]
(let [numberofFailure ...]
{:status "login failures"
:metric numberofFailure
:totalFail (boolean ...)}
(streams
prn
numberofFailure)))
...)
You should do something like:
(smap
(fn [events]
(let [numberofFailure ...]
(event {:status "login failures"
:metric numberofFailure
:totalFail (boolean ...)}))
...)
To calculate totalFail remember that you need to use prefix notation to call >, so it must be (> totalFail 5). And boolean is not needed, as > will already return a boolean.
I would initialize the mailer out of the top-level streams call, as an enclosing scope using let or with a def. But it should work as it is.
You should pass the last where as a children stream to smap, so it must be the second argument to smap. Let's recall the smap docs:
(smap f & children)
Streaming map. Calls children with (f event), whenever (f event) is non-nil.
Prefer this to (adjust f) and (combine f). Example:
(smap :metric prn) ; prints the metric of each event.
(smap #(assoc % :state "ok") index) ; Indexes each event with state "ok"
The last where should not be enclosed by streams, and the and sentence must work on the event, so it must be:
(where (and (= (:status event) "login failures")
(:total-fail event))
(email "123#gmail.com"))
The :subject fn for mailer should be passed as part of a second map, as explained in the mailer documentation
There's an open issue on fixed-time-window which makes it a bit unreliable: it doesn't fire as soon as the time window is due but waits until a new event is fired, so you might want to use a different windowing strategy until that get's fixed.
Here goes a full minimal working example based on yours:
(let [email (mailer {:host "localhost"
:port 1025
:from "abc#gmail.com"})]
(streams
(where (and (service "system_log")
(not (expired? event)))
(fixed-time-window
5
(smap
(fn [events]
(let [count-of-failures (count (filter #(= "IE" (:description %)) events))]
(event
{:status "login failures"
:metric count-of-failures
:total-fail (>= count-of-failures 2)})))
(where (and (= (:status event) "login failures")
(:total-fail event))
(email "hello123#gmail.com")))))))

Riemann - Build a stream dynamically from a map

I have the following function which gets a map with service name and threshold. It checks if the service crossed a defined threshold and then calls multiple downstream children on the event.
(defn tc
[s & children]
(where
(and (service (:service_name s)) (not (expired? event)))
(by [:host :service]
(where (> metric (:threshold s)
(with :state "critical"
(apply sdo children)))))))
I would like to build a stream dynamically using a vector of maps:
(def services [{:service "cpu/usage" :threshold 90}
{:service "memory/usage" :threshold 90}])
When trying to run it in a stream i'm getting the following warning:
(streams
(doseq [s services] (tc s prn)))
WARN [2015-01-05 14:27:07,187] Thread-15 - riemann.core - instrumentation service caught
java.lang.NullPointerException
at riemann.core$stream_BANG_$fn__11140.invoke(core.clj:19)
at riemann.core$stream_BANG_.invoke(core.clj:18)
at riemann.core$instrumentation_service$measure__11149.invoke(core.clj:57)
at riemann.service.ThreadService$thread_service_runner__8782$fn__8783.invoke(service.clj:66)
at riemann.service.ThreadService$thread_service_runner__8782.invoke(service.clj:65)
at clojure.lang.AFn.run(AFn.java:22)
at java.lang.Thread.run(Thread.java:701)
It works, if i run the streams function inside the doseq.
This one works and gives the following output:
(doseq [s services]
(streams (tc s prn)))
#riemann.codec.Event{:host "testhost", :service "memory/usage", :state "critical", :description nil, :metric 91.0, :tags nil, :time 1420460856, :ttl 60.0}
It seems to blow up if your events don't have all the required fields, here's a sample from a similar project where I build an event from a sequence of events (reducing) It's not exactly what you are doing though I'm generating events in the same way:
{:service (:service (first events))
:metric (->> events count)
:host "All-counts"
:state "OK"
:time (:time (last events))
:ttl default-interval}
I got NPE specifically when time was missing. If you can't inherit it form somewhere, just make it up (use now for instance) without a reasonable value here, event expiration will not work and you'll run out of RAM

how to avoid nesting in clojure

when my write a function to check a user can delete a post by clojure,I get this
(defn delete!
{:arglists}
[^String id]
(if (valid-number? id)
(let [result {:code 200 :status "error" :messag "delete success"}]
(if-let [user (session/get :userid)]
(if-let [post (pdb/id id)]
(if (= user (post :user_id))
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok"))
(assoc result :message (emsg :not-own)))
(assoc result :message (emsg :post-id-error))))
(assoc result :message (emsg :not-login)))))
so i want to fix it,i get this
https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/register.clj#L27
https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/utils.clj#L32
but it is line,but not a nest.
the delete! function is nest ugly and it is very hard to understand it,how to write a macro to avoid the nesting a lot.or other way to avoid it.
This doesn't need a macro. I guess cond is a macro, but it is the only one we need to make this code readable.
(defn delete!
;; {:arglists} ; this line will not compile
[^String id]
(let [result {:code 200 :status "error" :message "delete success"}
user (session/get :userid)
post (and user (valid-number? id) (pbd/id id))]
(cond
(not user)
(assoc result :message (emsg :not-login))
(not post)
(assoc result :message (emsg :post-id-error))
(not= user (:user_id post))
(assoc result :message (emsg :not-own))
:else
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok")))))
This is something a lot of people run into, so don't feel bad.
Check out this blog by Christophe Grand, which I think is a pretty nice (and concise!) solution.
Edit: you only need something fancy like this (or alternatively the version using delay in this other post) if you need to short-circuit execution like the original - otherwise noisesmith's answer is the way to go.
Here's how you could do this sort of thing with the Either monad -- I'm sure there are libraries for it already but I'll implement it here for completeness. (Note: this code hasn't been validated.)
(defn success? [v]
(contains? v :success))
(defn inject [v]
{:success v})
(defn bind [v f]
(if (success? v)
(apply f (:success v))
v))
(defmacro >>= [v & body]
(let [binds (map #(list 'bind %) body)]
`(-> ~v ~#binds)))
(defn delete!
{:arglists}
[^String id]
(if (valid-number? id)
(let [result {:code 200 :status "error" :message "delete success"}
check
(>>= (inject {:id id})
#(if-let [user (session/get :userid)]
{:success (assoc % :user user)}
(:failure (assoc result :message (emsg :not-login))))
#(if-let [post (pdb/id (:id %))]
{:success (assoc % :post post)}
{:failure (assoc result :message (emsg :post-id-error))})
#(if (= (:user %) ((:post %) :user_id))
{:success %}
{:failure (assoc result :message (emsg :not-own))}))]
(if (success? check)
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok"))
(:failure check)))))
The >>= macro works like the -> macro (obviously, since it uses it), but if any of the functions return a {:failure ...} then the chain short-circuits (thanks to bind) and the failure value of the function that failed becomes the value returned by >>=.
Edit
I should note that the function I have named inject is actually called return, but I decided to name it inject here since that's more along the lines of what it does in this monad.