Skip to content

Commit

Permalink
Merge pull request elastic#140 from hectcastro/aws_sns_output_plugin
Browse files Browse the repository at this point in the history
AWS SNS output plugin
  • Loading branch information
jordansissel committed Apr 29, 2012
2 parents 6e02c63 + a2ef981 commit af1362b
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 15 deletions.
6 changes: 6 additions & 0 deletions Gemfile
Expand Up @@ -6,6 +6,7 @@ gem "uuidtools" # for naming amqp queues, License ???

gem "filewatch", "0.3.3" # for file tailing, BSD License
gem "jls-grok", "0.10.6" # for grok filter, BSD License
gem "aws-sdk" # for AWS access: SNS and S3 log tailing. Apache 2.0 License
gem "jruby-elasticsearch", "0.0.11", :platforms => :jruby # BSD License
gem "onstomp" # for stomp protocol, Apache 2.0 License
gem "json" # Ruby license
Expand Down Expand Up @@ -34,6 +35,11 @@ gem "ffi"

gem "riemann-client", "0.0.6" #outputs/riemann, License: MIT

group :test do
gem "mocha"
gem "shoulda"
end

# ruby-debug is broken in 1.9.x due, at a minimum, the following:
# Installing rbx-require-relative (0.0.5)
# Gem::InstallError: rbx-require-relative requires Ruby version ~> 1.8.7.
Expand Down
43 changes: 29 additions & 14 deletions Gemfile.lock
Expand Up @@ -2,19 +2,22 @@ GEM
remote: http://rubygems.org/
specs:
addressable (2.2.6)
aws-sdk (1.4.1)
httparty (~> 0.7)
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
backports (2.3.0)
beefcake (0.3.7)
bouncy-castle-java (1.5.0146.1)
bson (1.6.1)
bson (1.6.1-java)
bson (1.6.2-java)
bunny (0.7.9)
cabin (0.4.1)
json
ffi (1.0.11)
ffi (1.0.11-java)
ffi-rzmq (0.9.0)
filewatch (0.3.3)
ftw (0.0.13)
ftw (0.0.14)
addressable (= 2.2.6)
backports (= 2.3.0)
cabin (> 0)
Expand All @@ -26,32 +29,37 @@ GEM
gelfd (0.2.0)
gmetric (0.1.3)
haml (3.1.4)
heroku (2.22.0)
heroku (2.24.1)
launchy (>= 0.3.2)
netrc (~> 0.7.1)
rest-client (~> 1.6.1)
rubyzip
http_parser.rb (0.5.3)
http_parser.rb (0.5.3-java)
httparty (0.8.2)
multi_json
multi_xml
jls-grok (0.10.6)
cabin (~> 0.4.0)
jruby-elasticsearch (0.0.11)
jruby-openssl (0.7.6.1)
bouncy-castle-java (>= 1.5.0146.1)
json (1.6.5)
json (1.6.5-java)
launchy (2.1.0)
addressable (~> 2.2.6)
launchy (2.1.0-java)
addressable (~> 2.2.6)
ffi (~> 1.0.9)
spoon (~> 0.0.1)
metaclass (0.0.1)
mime-types (1.18)
minitest (2.11.4)
mongo (1.6.1)
bson (~> 1.6.1)
minitest (2.12.1)
mocha (0.11.0)
metaclass (~> 0.0.1)
mongo (1.6.2)
bson (~> 1.6.2)
mtrc (0.0.4)
multi_json (1.3.2)
multi_xml (0.4.4)
netrc (0.7.1)
nokogiri (1.5.2-java)
onstomp (1.0.6)
rack (1.4.1)
rack-protection (1.2.0)
Expand All @@ -63,8 +71,13 @@ GEM
beefcake (>= 0.3.5)
mtrc (>= 0.0.4)
trollop (>= 1.16.2)
rubyzip (0.9.6.1)
rubyzip (0.9.7)
sass (3.1.15)
shoulda (3.0.1)
shoulda-context (~> 1.0.0)
shoulda-matchers (~> 1.0.0)
shoulda-context (1.0.0)
shoulda-matchers (1.0.0)
sinatra (1.3.2)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
Expand All @@ -78,9 +91,9 @@ GEM

PLATFORMS
java
ruby

DEPENDENCIES
aws-sdk
bunny
cabin (= 0.4.1)
ffi
Expand All @@ -97,12 +110,14 @@ DEPENDENCIES
jruby-openssl
json
minitest
mocha
mongo
onstomp
rack
redis
riemann-client (= 0.0.6)
sass
shoulda
sinatra
statsd-ruby (= 0.3.0)
uuidtools
Expand Down
144 changes: 144 additions & 0 deletions lib/logstash/outputs/sns.rb
@@ -0,0 +1,144 @@
require "logstash/outputs/base"
require "logstash/namespace"

# SNS output.
#
# Send events to Amazon's Simple Notification Service, a hosted pub/sub
# framework. It supports subscribers of type email, HTTP/S, SMS, and
# SQS.
#
# For further documentation about the service see:
#
# http://docs.amazonwebservices.com/sns/latest/api/
#
# This plugin looks for the following fields on events it receives:
#
# "sns" => If no ARN is found in the configuration file,
# this will be used as the ARN to publish.
# "sns_subject" => The subject line that should be used. Optional.
# "%{@source}" will be used if not present
# (truncated at MAX_SUBJECT_SIZE_IN_CHARACTERS).
# "sns_message" => The message that should be sent. Optional. The
# event serialzed as JSON will be used if not
# present (with @message truncated so that the
# length of the JSON fits in
# MAX_MESSAGE_SIZE_IN_BYTES).
class LogStash::Outputs::Sns < LogStash::Outputs::Base
MAX_SUBJECT_SIZE_IN_CHARACTERS = 100
MAX_MESSAGE_SIZE_IN_BYTES = 32768

config_name "sns"
plugin_status "experimental"

# Amazon API credentials.
config :access_key_id, :validate => :string
config :secret_access_key, :validate => :string

# Path to YAML file containing a hash of AWS credentials. This file
# will be loaded if `access_key_id` and `secret_access_key` aren't
# set.
#
# Example:
#
# The path to YAML file containing a hash of the AWS credentials for
# your account. The contents of the file should look like this:
#
# ---
# :access_key_id: "12345"
# :secret_access_key: "54321"
#
config :credentials, :validate => :string

# Message format. Defaults to plain text.
config :format, :validate => [ "json", "plain" ], :default => "plain"

# SNS topic ARN.
config :arn, :validate => :string

# When an ARN for an SNS topic is specified here, the message
# "Logstash successfully booted" will be sent to it when this plugin
# is registered.
#
# Example: arn:aws:sns:us-east-1:770975001275:logstash-testing
#
config :publish_boot_message_arn, :validate => :string

public
def register
require "aws-sdk"

# Credentials weren't specified in the configuration.
unless @access_key_id && @secret_access_key
access_creds = YAML.load_file(@credentials)

@access_key_id = access_creds[:access_key_id]
@secret_access_key = access_creds[:secret_access_key]
end

@sns = AWS::SNS.new(
:access_key_id => @access_key_id,
:secret_access_key => @secret_access_key
)

# Try to publish a "Logstash booted" message to the ARN provided to
# cause an error ASAP if the credentials are bad.
if @publish_boot_message_arn
@sns.topics[@publish_boot_message_arn].publish("Logstash successfully booted", :subject => "Logstash booted")
end
end

public
def receive(event)
return unless output?(event)

arn = Array(event.fields["sns"]).first || @arn

raise "An SNS ARN required." unless arn

message = Array(event.fields["sns_message"]).first
subject = Array(event.fields["sns_subject"]).first || event.source

# Ensure message doesn't exceed the maximum size.
if message
# TODO: Utilize `byteslice` in JRuby 1.7: http://jira.codehaus.org/browse/JRUBY-5547
message = message.slice(0, MAX_MESSAGE_SIZE_IN_BYTES)
else
if @format == "plain"
message = self.class.format_message(event)
else
message = self.class.json_message(event)
end
end

# Log event.
@logger.debug("Sending event to SNS topic [#{arn}] with subject [#{subject}] and message:")
message.split("\n").each { |line| @logger.debug(line) }

# Publish the message.
@sns.topics[arn].publish(message, :subject => subject.slice(0, MAX_SUBJECT_SIZE_IN_CHARACTERS))
end

def self.json_message(event)
json = event.to_json
json_size = json.bytesize

# Truncate only the message if the JSON structure is too large.
if json_size > MAX_MESSAGE_SIZE_IN_BYTES
# TODO: Utilize `byteslice` in JRuby 1.7: http://jira.codehaus.org/browse/JRUBY-5547
event.message = event.message.slice(0, (event.message.bytesize - (json_size - MAX_MESSAGE_SIZE_IN_BYTES)))
end

event.to_json
end

def self.format_message(event)
message = "Date: #{event.timestamp}\n"
message << "Source: #{event.source}\n"
message << "Tags: #{event.tags.join(', ')}\n"
message << "Fields: #{event.fields.inspect}\n"
message << "Message: #{event.message}"

# TODO: Utilize `byteslice` in JRuby 1.7: http://jira.codehaus.org/browse/JRUBY-5547
message.slice(0, MAX_MESSAGE_SIZE_IN_BYTES)
end
end
5 changes: 4 additions & 1 deletion logstash.gemspec
Expand Up @@ -25,7 +25,8 @@ Gem::Specification.new do |spec|

spec.add_dependency "awesome_print" # MIT License
spec.add_dependency "bunny" # for amqp support, MIT-style license
spec.add_dependency "cabin", "0.3.1" # for logging. apache 2 license
spec.add_dependency "cabin", "0.3.8" # for logging. apache 2 license
spec.add_dependency "aws-sdk" # for AWS access: SNS and S3 log tailing. Apache 2.0 License
spec.add_dependency "filewatch", "~> 0.3.3" # for file tailing, BSD License
spec.add_dependency "gelfd", "~> 0.2.0" #inputs/gelf, # License: Apache 2.0
spec.add_dependency "gelf" # outputs/gelf, # License: MIT-style
Expand All @@ -49,6 +50,8 @@ Gem::Specification.new do |spec|
spec.add_dependency "xmpp4r", "~> 0.5" # outputs/xmpp, # License: As-Is

spec.add_dependency("ffi-rzmq")
spec.add_development_dependency 'mocha'
spec.add_development_dependency 'shoulda'

spec.files = files
spec.require_paths << "lib"
Expand Down

0 comments on commit af1362b

Please sign in to comment.