# encoding: utf-8 require "logstash/inputs/base" require "logstash/namespace" require "logstash/timestamp" require "win32/eventlog" require "stud/interval" require "logstash/util/charset" # This input will pull events from a http://msdn.microsoft.com/en-us/library/windows/desktop/bb309026%28v=vs.85%29.aspx[Windows Event Log]. # Note that Windows Event Logs are stored on disk in a binary format and are only accessible from the Win32 API. # This means Losgtash needs to be running as an agent on Windows servers where you wish to collect logs # from, and will not be accesible across the network. # # To collect Events from the System Event Log, use a config like: # [source,ruby] # input { # eventlog { # type => 'Win32-EventLog' # logfile => 'System' # } # } class LogStash::Inputs::EventLog < LogStash::Inputs::Base config_name "eventlog" default :codec, "plain" # Event Log Name # System and Security may require that privileges are given to the user running logstash. # see more at: https://social.technet.microsoft.com/forums/windowsserver/en-US/d2f813db-6142-4b5b-8d86-253ebb740473/easy-way-to-read-security-log config :logfile, :validate => :string, :validate => [ "Application", "Security", "System" ], :default => "Application" # How frequently should tail check for new event logs in ms (default: 1 second) config :interval, :validate => :number, :default => 1000 # Event Log string encoding (default: UTF-16LE), however your system might be using another encoding, if you # are seeing strange characters, inspect this variable. config :charset, :validate => :string, :default => "UTF-16LE" public def register # wrap specified logfiles in suitable OR statements @hostname = Socket.gethostname @logger.info("Opening eventlog #{@logfile}") begin @eventlog = Win32::EventLog.open(@logfile) rescue SystemCallError => e if e.errno == 1314 # ERROR_PRIVILEGE_NOT_HELD @logger.fatal("No privilege held to open logfile", :logfile => @logfile) end raise end @converter = LogStash::Util::Charset.new(Encoding.find(@charset)) end # def register public def run(queue) @logger.debug("Tailing Windows Event Log '#{@logfile}'") old_total = @eventlog.total_records() flags = Win32::EventLog::FORWARDS_READ | Win32::EventLog::SEEK_READ rec_num = @eventlog.read_last_event.record_number while !stop? new_total = @eventlog.total_records() if new_total != old_total rec_num = @eventlog.oldest_record_number() if @eventlog.full? @eventlog.read(flags, rec_num).each { |log| e = process(log); decorate(e); queue << e } old_total = new_total rec_num = @eventlog.read_last_event.record_number + 1 end Stud.stoppable_sleep(@interval/1000.0) { stop? } end end private def process(log) attrs = { "host" => @hostname, "Logfile" => @logfile, "message" => log["description"].strip, "Category" => log["category"], "ComputerName" => log["computer"], "EventIdentifier" => log["event_id"], "EventType" => log["event_type"], "RecordNumber" => log["record_number"], "SourceName" => log["source"], "TimeGenerated" => log["time_generated"], "TimeWritten" => log["time_written"], "Type" => log["event_type"], "User" => log["user"], "InsertionStrings" => log["string_inserts"] } attrs.each do |k,v| next if ["host", "Logfile"].include?(k) attrs[k] = convert(v) end LogStash::Event.new(attrs) end # def run def convert(field) if field.is_a?(String) @converter.convert(field) elsif field.is_a?(Array) field.map { |v| @converter.convert(v) } else field end end end # class LogStash::Inputs::EventLog