mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 14:47:19 -04:00
Email Notification Plugin - really only SMTP tested and confirmed
working.
This commit is contained in:
parent
6c1c5e3c62
commit
8f48d1ed42
4 changed files with 352 additions and 2 deletions
1
Gemfile
1
Gemfile
|
@ -11,6 +11,7 @@ gem "onstomp" # for stomp protocol, Apache 2.0 License
|
|||
gem "json" # Ruby license
|
||||
#gem "awesome_print" # MIT License
|
||||
gem "jruby-openssl", :platforms => :jruby # For enabling SSL support, CPL/GPL 2.0
|
||||
gem "mail" #outputs/email, # License: MIT License
|
||||
|
||||
gem "minitest" # License: Ruby
|
||||
gem "rack" # License: MIT
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -33,6 +33,7 @@ GEM
|
|||
rubyzip
|
||||
http_parser.rb (0.5.3)
|
||||
http_parser.rb (0.5.3-java)
|
||||
i18n (0.6.0)
|
||||
jls-grok (0.10.6)
|
||||
cabin (~> 0.4.0)
|
||||
jruby-elasticsearch (0.0.11)
|
||||
|
@ -46,12 +47,18 @@ GEM
|
|||
addressable (~> 2.2.6)
|
||||
ffi (~> 1.0.9)
|
||||
spoon (~> 0.0.1)
|
||||
mail (2.4.4)
|
||||
i18n (>= 0.4.0)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.18)
|
||||
minitest (2.11.4)
|
||||
mongo (1.6.1)
|
||||
bson (~> 1.6.1)
|
||||
mtrc (0.0.4)
|
||||
netrc (0.7.1)
|
||||
onstomp (1.0.6)
|
||||
polyglot (0.3.3)
|
||||
rack (1.4.1)
|
||||
rack-protection (1.2.0)
|
||||
rack
|
||||
|
@ -70,8 +77,10 @@ GEM
|
|||
tilt (~> 1.3, >= 1.3.3)
|
||||
spoon (0.0.1)
|
||||
statsd-ruby (0.3.0)
|
||||
stomp (1.2.1)
|
||||
tilt (1.3.3)
|
||||
treetop (1.4.10)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
trollop (1.16.2)
|
||||
uuidtools (2.1.2)
|
||||
xmpp4r (0.5)
|
||||
|
@ -96,14 +105,15 @@ DEPENDENCIES
|
|||
jruby-elasticsearch (= 0.0.11)
|
||||
jruby-openssl
|
||||
json
|
||||
mail
|
||||
minitest
|
||||
mongo
|
||||
onstomp
|
||||
rack
|
||||
redis
|
||||
riemann-client (= 0.0.6)
|
||||
sass
|
||||
sinatra
|
||||
statsd-ruby (= 0.3.0)
|
||||
stomp
|
||||
uuidtools
|
||||
xmpp4r (= 0.5)
|
||||
|
|
338
lib/logstash/outputs/email.rb
Executable file
338
lib/logstash/outputs/email.rb
Executable file
|
@ -0,0 +1,338 @@
|
|||
require "logstash/outputs/base"
|
||||
require "logstash/namespace"
|
||||
|
||||
# https://github.com/mikel/mail
|
||||
# supports equal(default), not equal(!), greater than(>), less than(<), greater than or equal(>=), less than or equal(<=), contains(*), does not contain(!*)
|
||||
# you must provide a matchName - which is the key. Then provide your query values - again in key value pairs, separated by a ',' in the value spot.
|
||||
# You can say this :
|
||||
# [ "response errors", "response,501,,or,response,301" ]
|
||||
# I hate making requirements like this but this is the format that is the most flexible for making fine selections over data.
|
||||
# NOTE: In the above example we are using just an equality test - so the two values must be exact for matches to be made. You must provide an AND/OR block
|
||||
# between conditions so we know how to deal with them. Please see below for an example where you wanted an AND instead of the OR default - this would require both to be valid.
|
||||
# [ "response errors", "response,501,,and,response,301" ]
|
||||
# as you can see you can just seperate the Operator logic with a blank key and the operator of your liking - AND/OR
|
||||
# IMPORTANT : you MUST provide a "matchName". This is so I can easily be able to provide a label of sorts for the alert.
|
||||
# In addition, we break after we find the first valid match.
|
||||
#
|
||||
# email {
|
||||
# tags => [ "sometag" ]
|
||||
# match => [ "response errors", "response,501,,or,response,301",
|
||||
# "multiple response errors", "response,501,,and,response,301" ]
|
||||
# to => "main.contact@domain.com"
|
||||
# from => "alert.account@domain.com" # default: logstash.alert@nowhere.com
|
||||
# cc => "" # provide additional recipients
|
||||
# options => [ "smtpIporHost", "smtp.gmail.com",
|
||||
# "port", "587",
|
||||
# "domain", "yourDomain", # optional
|
||||
# "userName", "yourSMTPUsername",
|
||||
# "password", "PASS",
|
||||
# "starttls", "true",
|
||||
# "authenticationType", "plain",
|
||||
# "debug", "true" # optional
|
||||
# ]
|
||||
# via => "smtp" # or pop or sendmail
|
||||
# subject => "Found '%{matchName}' Alert on %{@source_host}"
|
||||
# body => "Here is the event line %{@message}"
|
||||
# htmlbody => "<h2>%{matchName}</h2><br/><br/><h3>Full Event</h3><br/><br/><div align='center'>%{@message}</div>"
|
||||
# }
|
||||
class LogStash::Outputs::Email < LogStash::Outputs::Base
|
||||
|
||||
config_name "email"
|
||||
plugin_status "beta"
|
||||
|
||||
# the registered fields that we want to monitor
|
||||
# A hash of matches of field => value
|
||||
config :match, :validate => :hash, :required => true
|
||||
|
||||
# the To address setting - fully qualified email address to send to
|
||||
config :to, :validate => :string, :required => true
|
||||
|
||||
# The From setting for email - fully qualified email address for the From:
|
||||
config :from, :validate => :string, :default => "logstash.alert@nowhere.com"
|
||||
|
||||
# cc - send to others
|
||||
config :cc, :validate => :string, :default => ""
|
||||
|
||||
# how to send email: either smtp or sendmail - default to 'smtp'
|
||||
config :via, :validate => :string, :default => "smtp"
|
||||
|
||||
# the options to use:
|
||||
# smtp: address, port, enable_starttls_auto, user_name, password, authentication(bool), domain
|
||||
# sendmail: location, arguments
|
||||
# If you do not specify anything, you will get the following equivalent code set in
|
||||
# every new mail object:
|
||||
#
|
||||
# Mail.defaults do
|
||||
# delivery_method :smtp, { :address => "localhost",
|
||||
# :port => 25,
|
||||
# :domain => 'localhost.localdomain',
|
||||
# :user_name => nil,
|
||||
# :password => nil,
|
||||
# :authentication => nil,(plain, login and cram_md5)
|
||||
# :enable_starttls_auto => true }
|
||||
#
|
||||
# retriever_method :pop3, { :address => "localhost",
|
||||
# :port => 995,
|
||||
# :user_name => nil,
|
||||
# :password => nil,
|
||||
# :enable_ssl => true }
|
||||
# end
|
||||
#
|
||||
# Mail.delivery_method.new #=> Mail::SMTP instance
|
||||
# Mail.retriever_method.new #=> Mail::POP3 instance
|
||||
#
|
||||
# Each mail object inherits the default set in Mail.delivery_method, however, on
|
||||
# a per email basis, you can override the method:
|
||||
#
|
||||
# mail.delivery_method :sendmail
|
||||
#
|
||||
# Or you can override the method and pass in settings:
|
||||
#
|
||||
# mail.delivery_method :sendmail, { :address => 'some.host' }
|
||||
#
|
||||
# You can also just modify the settings:
|
||||
#
|
||||
# mail.delivery_settings = { :address => 'some.host' }
|
||||
#
|
||||
# The passed in hash is just merged against the defaults with +merge!+ and the result
|
||||
# assigned the mail object. So the above example will change only the :address value
|
||||
# of the global smtp_settings to be 'some.host', keeping all other values
|
||||
config :options, :validate => :hash, :default => {}
|
||||
|
||||
# subject for email
|
||||
config :subject, :validate => :string, :default => ""
|
||||
|
||||
# body for email - just plain text
|
||||
config :body, :validate => :string, :default => ""
|
||||
|
||||
# body for email - can contain html markup
|
||||
config :htmlbody, :validate => :string, :default => ""
|
||||
|
||||
# attachments - has of name of file and file location
|
||||
config :attachments, :validate => :array, :default => []
|
||||
|
||||
# contenttype : for multipart messages, set the content type and/or charset of the html part
|
||||
config :contenttype, :validate => :string, :default => "text/html; charset=UTF-8"
|
||||
|
||||
public
|
||||
def register
|
||||
require "mail"
|
||||
if @via == "smtp"
|
||||
debug = @options.include?("debug")
|
||||
if !debug
|
||||
debug = false
|
||||
else
|
||||
debug = @options.fetch("debug")
|
||||
end
|
||||
smtpIporHost = @options.include?("smtpIporHost")
|
||||
if !smtpIporHost
|
||||
smtpIporHost = "localhost"
|
||||
else
|
||||
smtpIporHost = @options.fetch("smtpIporHost")
|
||||
end
|
||||
domain = @options.include?("domain")
|
||||
if !domain
|
||||
domain = "localhost"
|
||||
else
|
||||
domain = @options.fetch("domain")
|
||||
end
|
||||
port = @options.include?("port")
|
||||
if !port
|
||||
port = 25
|
||||
else
|
||||
port = @options.fetch("port")
|
||||
end
|
||||
tls = @options.include?("starttls")
|
||||
if !tls
|
||||
tls = false
|
||||
else
|
||||
tls = @options.fetch("starttls")
|
||||
end
|
||||
pass = @options.include?("password")
|
||||
if !pass
|
||||
pass = ""
|
||||
else
|
||||
pass = @options.fetch("password")
|
||||
end
|
||||
userName = @options.include?("userName")
|
||||
if !userName
|
||||
userName = ""
|
||||
else
|
||||
userName = @options.fetch("userName")
|
||||
end
|
||||
authenticationType = @options.include?("authenticationType")
|
||||
if !authenticationType
|
||||
authenticationType = "plain"
|
||||
else
|
||||
authenticationType = @options.fetch("authenticationType")
|
||||
end
|
||||
Mail.defaults do
|
||||
delivery_method :smtp , { :address => smtpIporHost,
|
||||
:port => port,
|
||||
:domain => domain,
|
||||
:user_name => userName,
|
||||
:password => pass,
|
||||
:authentication => authenticationType,
|
||||
:enable_starttls_auto => tls,
|
||||
:debug => debug }
|
||||
end
|
||||
else
|
||||
Mail.defaults do
|
||||
delivery_method :@via, @options
|
||||
end
|
||||
end # @via tests
|
||||
@logger.debug("Email Output Registered!", :config => @config)
|
||||
end # def register
|
||||
|
||||
public
|
||||
def receive(event)
|
||||
return unless output?(event)
|
||||
@logger.debug("Event being tested for Email", :tags => @tags, :event => event)
|
||||
# Set Intersection - returns a new array with the items that are the same between the two
|
||||
if !@tags.empty? && (event.tags & @tags).size == 0
|
||||
# Skip events that have no tags in common with what we were configured
|
||||
@logger.debug("No Tags match for Email Output!")
|
||||
return
|
||||
end
|
||||
|
||||
@logger.debug("Match data for Email - ", :match => @match)
|
||||
successful = false
|
||||
matchName = ""
|
||||
operator = ""
|
||||
@match.each do |name, query|
|
||||
if successful
|
||||
break
|
||||
else
|
||||
matchName = name
|
||||
end
|
||||
# now loop over the csv query
|
||||
queryArray = query.split(',')
|
||||
index = 1
|
||||
while index < queryArray.length
|
||||
field = queryArray.at(index -1)
|
||||
value = queryArray.at(index)
|
||||
index = index + 2
|
||||
if field == ""
|
||||
if value.downcase == "and"
|
||||
operator = "and"
|
||||
elsif value.downcase == "or"
|
||||
operator = "or"
|
||||
else
|
||||
operator = "or"
|
||||
@logger.error("Operator Provided Is Not Found, Currently We Only Support AND/OR Values! - defaulting to OR")
|
||||
end
|
||||
else
|
||||
hasField = event.fields.has_key?(field)
|
||||
@logger.debug("Does Event Contain Field - ", :hasField => hasField)
|
||||
isValid = false
|
||||
# if we have maching field and value is wildcard - we have a success
|
||||
if hasField
|
||||
if value == "*"
|
||||
isValid = true
|
||||
else
|
||||
# we get an array so we need to loop over the values and find if we have a match
|
||||
eventFieldValues = event.fields.fetch(field)
|
||||
@logger.debug("Event Field Values - ", :eventFieldValues => eventFieldValues)
|
||||
eventFieldValues.each do |eventFieldValue|
|
||||
isValid = validateValue(eventFieldValue, value)
|
||||
if isValid # no need to iterate any further
|
||||
@logger.debug("VALID CONDITION FOUND - ", :eventFieldValue => eventFieldValue, :value => value)
|
||||
break
|
||||
end
|
||||
end # end eventFieldValues.each do
|
||||
end # end value == "*"
|
||||
end # end hasField
|
||||
# if we have an AND operator and we have a successful == false break
|
||||
if operator == "and" && !isValid
|
||||
successful = false
|
||||
elsif operator == "or" && (isValid || successful)
|
||||
successful = true
|
||||
else
|
||||
successful = isValid
|
||||
end
|
||||
end
|
||||
end
|
||||
end # @match.each do
|
||||
|
||||
@logger.debug("Email Did we match any alerts for event : ", :successful => successful)
|
||||
|
||||
if successful
|
||||
# first add our custom field - matchName - so we can use it in the sprintf function
|
||||
event["matchName"] = matchName
|
||||
@logger.debug("Sending mail with these settings : ", :via => @via, :options => @options, :from => @from, :to => @to, :cc => @cc, :subject => @subject, :body => @body, :content_type => @contenttype, :htmlbody => @htmlbody, :attachments => @attachments, :to => to, :to => to)
|
||||
formatedSubject = event.sprintf(@subject)
|
||||
formattedBody = event.sprintf(@body)
|
||||
formattedHtmlBody = event.sprintf(@htmlbody)
|
||||
# we have a match(s) - send email
|
||||
mail = Mail.new
|
||||
mail.from = event.sprintf(@from)
|
||||
mail.to = event.sprintf(@to)
|
||||
mail.cc = event.sprintf(@cc)
|
||||
mail.subject = formatedSubject
|
||||
if @htmlbody.empty?
|
||||
mail.body = formattedBody
|
||||
else
|
||||
mail.text_part = Mail::Part.new do
|
||||
content_type "text/plain; charset=UTF-8"
|
||||
body formattedBody
|
||||
end
|
||||
mail.html_part = Mail::Part.new do
|
||||
content_type "text/html; charset=UTF-8"
|
||||
body formattedHtmlBody
|
||||
end
|
||||
end
|
||||
@attachments.each do |fileLocation|
|
||||
mail.add_file(fileLocation)
|
||||
end # end @attachments.each
|
||||
mail.deliver!
|
||||
end # end if successful
|
||||
end # def receive
|
||||
|
||||
private
|
||||
def validateValue(eventFieldValue, value)
|
||||
valid = false
|
||||
# order of this if-else is important - please don't change it
|
||||
if value.start_with?(">=")# greater than or equal
|
||||
value.gsub!(">=","")
|
||||
if eventFieldValue.to_i >= value.to_i
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?("<=")# less than or equal
|
||||
value.gsub!("<=","")
|
||||
if eventFieldValue.to_i <= value.to_i
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?(">")# greater than
|
||||
value.gsub!(">","")
|
||||
if eventFieldValue.to_i > value.to_i
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?("<")# less than
|
||||
value.gsub!("<","")
|
||||
if eventFieldValue.to_i < value.to_i
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?("*")# contains
|
||||
value.gsub!("*","")
|
||||
if eventFieldValue.include?(value)
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?("!*")# does not contain
|
||||
value.gsub!("!*","")
|
||||
if !eventFieldValue.include?(value)
|
||||
valid = true
|
||||
end
|
||||
elsif value.start_with?("!")# not equal
|
||||
value.gsub!("!","")
|
||||
if eventFieldValue != value
|
||||
valid = true
|
||||
end
|
||||
else # default equal
|
||||
if eventFieldValue == value
|
||||
valid = true
|
||||
end
|
||||
end
|
||||
return valid
|
||||
end # end validateValue()
|
||||
|
||||
end # class LogStash::Outputs::Email
|
|
@ -47,6 +47,7 @@ Gem::Specification.new do |spec|
|
|||
spec.add_dependency "stomp" # for stomp protocol, Apache 2.0 License
|
||||
spec.add_dependency "uuidtools" # for naming amqp queues, License ???
|
||||
spec.add_dependency "xmpp4r", "~> 0.5" # outputs/xmpp, # License: As-Is
|
||||
spec.add_dependency "mail" #outputs/email, # License: MIT License
|
||||
|
||||
spec.add_dependency("ffi-rzmq")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue