Custom body message in riemann email - clojure

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.

Related

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

custom body message email triggering in Riemann

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

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

sequence comprehensions to get run

The below code I have found from a book (Functional Programming Patterns in Scala and Clojure). The for statement uses close-zip? to filter out people outside of the zips and then it generates a greeting to the people who are left. However, I am not quite sure how people should look like as argument for generate-greetings and print-greetings functions?
(def close-zip? #{19123 19103})
(defn generate-greetings [people]
(for [{:keys [name address]} people :when (close-zip? (address :zip-code))]
(str "Hello, " name ", and welcome to the Lambda Bar And Grille!")))
(defn print-greetings [people]
(doseq [{:keys [name address]} people :when (close-zip? (address :zip-code))]
(println (str "Hello, " name ", and welcome to the Lambda Bar And Grille!"))))
They need to be maps with :name and :address keys, like:
{:name "A Person", :address {:zip-code 19103}}
for will take each element from people and assign each one to {:keys [name address]}. This is called destructuring, and it's just a convenience. It's the same as saying:
(for [person people
:let [name (:name person)
address (:address person)]
:when (close-zip? (:zip-code address))]
(str ...))

How to get user input in Clojure?

I'm currently learning clojure, but I was wondering how to get and store user input in a clojure program. I was looking at the clojure api and I found a function called read-line, however I'm not sure how to use it if it's the right function to use...
Anyhow, how do you get user input in clojure ?
read-line is the correct function..
(println (read-line))
..would basically echo the users input:
Clojure 1.0.0-
user=> (println (read-line))
this is my input
this is my input
To use it in an if statement, you'd probably use let:
(let [yayinput (read-line)]
(if (= yayinput "1234")
(println "Correct")
(println "Wrong")))
Hope that's enough to get you started, because that's about the limit of my Clojure knowledge!
Remember also that you have access to all of Java ...
OK so perhaps I should present some examples ... my clojure skills are not good so these examples may need a bit of tweaking.
The System.console() way:
(let [console (. System console)
pwd (.readPassword console "tell me your password: ")]
(println "your password is " pwd))
The BufferedReader way:
(print "give me a line: ")
(let [reader (java.io.BufferedReader. *in*)
ln (.readLine reader)]
(println "your line is " ln))
My point is that one can leverage knowledge of Java, and Java itself, in Clojure. It's one of its principal, advertised strengths.
Wonder what would my score have been if the question were about user input from a GUI!
By the way, you could use JOptionPane to put up a little GUI to get user input ...
read-line is used to get user input and use let to bind it to some variable.
For example : if you want to read user ID and password from user and display it, you could use the following piece of code
(defn print-message [pid pw]
(println "PID : " pid)
(println "PW : " pw))
(defn inp[]
(println "Enter your PID and password")
(let[pid (read-line) pw (read-line)]
(print-message pid pw) ))