custom body message email triggering in Riemann - clojure

I want to trigger a mail with custom body message. I can parse timestamp in my body, but i want to add the string in my body when i try to add string value in body i'm unable to reproduce it. can anyone help me to resolve it. I'm in very critical implementation.
Please find my sample code below,
(let [email (mailer {:host "cccc"
:port 25
:user "111111"
:pass "111111"
:auth "true"
:subject (fn [events] "1DD Monitoring - Response time SLA breach")
:body (fn [events]
(apply str "Hello Team, now the time is" (:**silo** events) "Thank You!"))
:from "xxx#xxxx.com"})]
I'm sending value from logstash to riemann in silo field and i want to print silo field value in body

IMHO the handle function that you define for :body, has a wrong syntax. According to the doc, you must define a function that takes a sequence of events and return a string. For example:
(defn prn-str [& events]
...)

So you have a :**silo** key in your event. But in the :body function, you will have a list of events. (:**silo** events) will be nil.
You can for example get the :**silo** values separated by a comma with:
:body (fn [events]
(str "Hello Team, now the time is "
(clojure.string/join "," (map #(:**silo** %) events))
" Thank You!"))

Related

Reagent atom value is stil nil after reset function

I made service endpoint api for getting single object by id and it works as expected. I tested it with Postman and in handler function. I use cljs-ajax library for asynchronous client. I cant change the state of Reagent atom when I get response. Here is the code:
(ns businesspartners.core
(:require [reagent.core :as r]
[ajax.core :refer [GET POST]]
[clojure.string :as string]))
(def business-partner (r/atom nil))
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler #(reset! business-partner (:business-partner %))}))
When I tried to access business-partner atom I got nil value for that atom. I can't figure out why because another method is almost the same except it get's list of business partners and works fine.
When I change the get-partner-by-id function:
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg))
(println "Business partner from handler: " #business-partner))}))
Output in the browser console:
:handler-arg {:_id 5e7ad2c84b5c2d44583e8ecd,
:address Main Street,
:email nenmit#gmail.com,
:phone 555888,
:name Nen Mit}
Business partner from handler: nil
So, as you can see, I have my object in handler as desired, but when I try to reset my atom nothing happens. That's the core of the problem I think. Thank you Alan.
When in doubt, use debug print statements. Make your handler look like this:
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg)))
You may also want to use clojure.pprint/pprint to pretty-print the output, or also add (type arg) to the output.
You may also want to initialize the atom to a specific value like
:bp-default so you can see if the nil you observe is the original one or if it is being reset to nil.
Update
So it is clear the key :business-partner does not exist in the map you are receiveing. This is what you must debug.
Trying to pull a non-existent key out of a map always returns nil. You could also use the 3-arg version of get to make this explicit. Convert
(:business-partner arg) => (get arg :business-partner ::not-found)
and you'll see the keyword ::not-found appear in your atom, verifying what is occurring.
In order to catch these problems early, I nearly always use a simple function grab from the Tupelo library like so:
(:business-partner arg) => (grab :business-partner arg)
The grab function will throw an exception if the expected key is not found. This provides early-warning of problems so you can track them down faster.
Another hint: next time use prn instead of println and it will retain double-quotes on string output like:
"Main Street"

Pass values from a collection as the first argument of a function

Say I have a collection of user-ids i.e. [001 002 003], and then I would have a function that does something and requires the user-id as its first argument.
(defn some-function [user-id name e-mail] (do-something user-id name e-mail))
What I'd like to do is to use this "some-function" to go through the collection of user-ids so that it would just change the user-id argument but the other arguments would remain the same i.e. so that it would return the following:
=>
[(some-function 001 name e-mail) (some-function 002 name e-mail) (some-function 003 name e-mail)]
Any help here? :) Thanks!
You can just use map:
(map #(some-function % name email) user-ids)
If "does something" is side-effecting then you should be using doseq rather than map:
(def user-ids [1 2 3])
(def email "me#my.com")
(def named "me")
(defn some-function [id name email]
(println (str id ", " name ", " email)))
(doseq [user-id user-ids]
(some-function user-id named email))
"Doing something" normally means affecting the world in some way - from printing to the screen to launching rockets into space.
However if you wanted to return a series of functions that can be executed later then map would be fine:
(def fns (map (fn [id]
(fn []
(some-function id named email)))
user-ids))
Here fns is the data structure you wrote out in your question.
To actually execute these 'thunks' you still need to doseq:
(doseq [f fns]
(f))
As a side-note, the kind of function you are talking about, that accepts different arguments at different times, is normally described as a 'higher order function', and it is best to code it that way from the start:
(defn some-function-hof [name email]
(fn [id]
(println (str id ", " name ", " email))))
(def some-fn! (some-function-hof named email))

Checkbox in Luminus

I started to learn Clojure this week, specifically I'm learning web development with Luminus. Since I want to understand the CRUD process, I setup a function to save my post into the DB:
(defn save-post! [{:keys [params]}]
(if-let [errors (validate-post params)]
(-> (response/found "/posts")
(assoc :flash (assoc params :errors errors)))
(do
(db/save-post!
(assoc params :created_at (java.util.Date.)))
(response/found "/posts"))))
The query is pretty basic:
-- :name save-post! :! :n
-- :doc creates a new post record
INSERT INTO posts
(title, body, active, created_at)
VALUES (:title, :body, :active, :created_at)
but the HTML form has a checkbox field:
<input type="checkbox" name="active" value="1">Published<br />
and when it is not selected, the field is not send and the SQL insert query sends the error message "No active field". How can I check if the element "active" is set and add it to "params" as true or false?
Something like:
(assoc params :active (if (nil? params/active) false true))
after the ":created_at (java.util.Date.)" line.
How can I check if the element "active" is set and add it to "params" as true or false?
Looks like your code isn't far from working. You'll need to check the params map to see if it has the checkbox's value. If (:active params) is equal to "1" when the checkbox is checked, then you might do something like this:
(assoc params :active (= "1" (:active params)))
But what this is really trying to do is update a particular value in the map, which can be done more idiomatically:
(update params :active #(= "1" %))
Where the final argument is a function that takes any current value of the keyword and returns the new value.
Another potential gotcha: you may not want to use the params map as direct input to your DB query, because it could very easily contain keys/values that you don't want or expect. It'd be safer to pull only the values you need from it explicitly e.g. (select-keys params [:title :body :active]).
(def params {:active "1", :admin true}) ;; wouldn't want admin to leak through!
(-> params
(select-keys [:title :body :active])
(assoc :created_at (java.util.Date.))
(update :active #(= "1" %)))
;;=> {:active true, :created_at #inst "2017-10-09T20:16:06.167-00:00"}

Custom body message in riemann email

I am trying to create a custom message in the body section of email using riemann.
I couldn't append the field dynamically.
Riemann config:
(let [email (mailer
{:host "XXXXX" :port XX :user "XXX" :pass "XXX" :auth "true"
:subject (fn [events] "Team")
:body (fn [events]
(apply str "Hello Team, now the time is" (:timestamp event) "Thank You!"))
:from "xxx#gmail.com"})]
My output:
Hello Team, now the time is Thank You!
My expected output:
Hello Team, now the time is 12:13:45 Thank You!.
My timestamp not getting appended in the :body.
from the docs:
These formatting functions take a sequence of
events and return a string.
so the question is which event in the sequence would you like to get the timestamp from? If you lookup a keyword in a sequence, as opposed to one of the members of that sequence, you will get back nil as a default:
core> (:asdf '(1 2 3))
nil
and if you apply str that into a couple other strings it will have no effect because str will ignore it. Which is where your output is coming from. Here is an analogous example function:
core> (let [body-fn (fn [events]
(apply str "Hello Team, now the time is"
(:timestamp events)
"Thank You!"))]
(body-fn [{:timestamp 42} {:timestamp 43}]))
"Hello Team, now the time isThank You!"
if we choose the timestamp from the first event:
core> (let [body-fn (fn [events]
(apply str "Hello Team, now the time is"
(:timestamp (first events))
"Thank You!"))]
(body-fn [{:timestamp 42} {:timestamp 43}]))
"Hello Team, now the time is42Thank You!"
When alerting through riemann my personal opinion is to wrap the things being alerted into fixed time windows and use the timestamp from the start of that window, though this is primarily because of personal preference.

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