# encoding: utf-8 require "logstash/filters/base" require "logstash/namespace" # Execute ruby code. # # For example, to cancel 90% of events, you can do this: # [source,ruby] # filter { # ruby { # # Cancel 90% of events # code => "event.cancel if rand <= 0.90" # } # } # # If you need to create additional events, it cannot be done as in other filters where you would use `yield`, # you must use a specific syntax `new_event_block.call(event)` like in this example duplicating the input event # [source,ruby] # filter { # ruby { # code => "new_event_block.call(event.clone)" # } # } # class LogStash::Filters::Ruby < LogStash::Filters::Base require "logstash/filters/ruby/script" class ScriptError < StandardError; end config_name "ruby" # Any code to execute at logstash startup-time config :init, :validate => :string # The code to execute for every event. # You will have an `event` variable available that is the event itself. See the <> for more information. config :code, :validate => :string # Path to the script config :path, :validate => :path # Parameters for this specific script config :script_params, :type => :hash, :default => {} # Tag to add to events that cause an exception in the script filter config :tag_on_exception, :type => :string, :default => "_rubyexception" # Flag for add exception message to tag_on_exception config :tag_with_exception_message, :type => :boolean, :default => false def initialize(*params) super(*params) if @path # run tests on the ruby file script @script = Script.new(@path, @script_params) @script.load @script.test end end def register if @code && @path.nil? eval(@init, binding, "(ruby filter init)") if @init eval("define_singleton_method :filter_method do |event, &new_event_block|\n #{@code} \nend", binding, "(ruby filter code)") elsif @path && @code.nil? @script.register else @logger.fatal("You must either use an inline script with the \"code\" option or a script file using \"path\".") end rescue ::ScriptError => e # SyntaxError, LoadError etc. @logger.error("error in register", message: e.message, exception: e.class, backtrace: e.backtrace) fail("unexpected error: #{e}") end def self.check_result_events!(results) if !results.is_a?(Array) raise "Custom script did not return an array from 'filter'. Only arrays may be returned!" end results.each do |r_event| if !r_event.is_a?(::LogStash::Event) raise "Custom script returned a non event object '#{r_event.inspect}'!" + " You must an array of events from this function! To drop an event simply return nil." end end end def filter(event, &block) if @code inline_script(event, &block) elsif @path file_script(event, &block) end end def inline_script(event, &block) filter_method(event, &block) filter_matched(event) rescue Exception => e @logger.error("Ruby exception occurred: #{e.message}", :class => e.class.name, :backtrace => e.backtrace) if @tag_with_exception_message event.tag("#{@tag_on_exception}: #{e}") end event.tag(@tag_on_exception) end def file_script(event) begin results = @script.execute(event) filter_matched(event) self.class.check_result_events!(results) rescue => e if @tag_with_exception_message event.tag("#{@tag_on_exception}: #{e}") end event.tag(@tag_on_exception) message = "Could not process event: " + e.message @logger.error(message, :script_path => @path, :class => e.class.name, :backtrace => e.backtrace) return event end returned_original = false results.each do |r_event| # If the user has generated a new event we yield that for them here if event == r_event returned_original = true else yield r_event end r_event end event.cancel unless returned_original end end