fluent-plugin-windows-eventlog 0.2.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,187 +1,234 @@
1
- require 'win32/eventlog'
2
- require 'fluent/plugin/input'
3
- require 'fluent/plugin'
4
-
5
- module Fluent::Plugin
6
- class WindowsEventLogInput < Input
7
- Fluent::Plugin.register_input('windows_eventlog', self)
8
-
9
- helpers :timer, :storage
10
-
11
- DEFAULT_STORAGE_TYPE = 'local'
12
- KEY_MAP = {"record_number" => [:record_number, :string],
13
- "time_generated" => [:time_generated, :string],
14
- "time_written" => [:time_written, :string],
15
- "event_id" => [:event_id, :string],
16
- "event_type" => [:event_type, :string],
17
- "event_category" => [:category, :string],
18
- "source_name" => [:source, :string],
19
- "computer_name" => [:computer, :string],
20
- "user" => [:user, :string],
21
- "description" => [:description, :string],
22
- "string_inserts" => [:string_inserts, :array]}
23
-
24
- config_param :tag, :string
25
- config_param :read_interval, :time, default: 2
26
- config_param :pos_file, :string, default: nil,
27
- obsoleted: "This section is not used anymore. Use 'store_pos' instead."
28
- config_param :channels, :array, default: ['application']
29
- config_param :keys, :array, default: []
30
- config_param :read_from_head, :bool, default: false
31
- config_param :from_encoding, :string, default: nil
32
- config_param :encoding, :string, default: nil
33
-
34
- config_section :storage do
35
- config_set_default :usage, "positions"
36
- config_set_default :@type, DEFAULT_STORAGE_TYPE
37
- config_set_default :persistent, true
38
- end
39
-
40
- attr_reader :chs
41
-
42
- def initialize
43
- super
44
- @chs = []
45
- @keynames = []
46
- @tails = {}
47
- end
48
-
49
- def configure(conf)
50
- super
51
- @chs = @channels.map {|ch| ch.strip.downcase }.uniq
52
- if @chs.empty?
53
- raise Fluent::ConfigError, "windows_eventlog: 'channels' parameter is required on windows_eventlog input"
54
- end
55
- @keynames = @keys.map {|k| k.strip }.uniq
56
- if @keynames.empty?
57
- @keynames = KEY_MAP.keys
58
- end
59
- @tag = tag
60
- @stop = false
61
- configure_encoding
62
- @receive_handlers = if @encoding
63
- method(:encode_record)
64
- else
65
- method(:no_encode_record)
66
- end
67
- @pos_storage = storage_create(usage: "positions")
68
- end
69
-
70
- def configure_encoding
71
- unless @encoding
72
- if @from_encoding
73
- raise Fluent::ConfigError, "windows_eventlog: 'from_encoding' parameter must be specied with 'encoding' parameter."
74
- end
75
- end
76
-
77
- @encoding = parse_encoding_param(@encoding) if @encoding
78
- @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
79
- end
80
-
81
- def parse_encoding_param(encoding_name)
82
- begin
83
- Encoding.find(encoding_name) if encoding_name
84
- rescue ArgumentError => e
85
- raise Fluent::ConfigError, e.message
86
- end
87
- end
88
-
89
- def encode_record(record)
90
- if @encoding
91
- if @from_encoding
92
- record.encode!(@encoding, @from_encoding)
93
- else
94
- record.force_encoding(@encoding)
95
- end
96
- end
97
- end
98
-
99
- def no_encode_record(record)
100
- record
101
- end
102
-
103
- def start
104
- super
105
- @chs.each do |ch|
106
- start, num = @pos_storage.get(ch)
107
- if @read_from_head || (!num || num.zero?)
108
- el = Win32::EventLog.open(ch)
109
- @pos_storage.put(ch, [el.oldest_record_number - 1, 1])
110
- el.close
111
- end
112
- timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
113
- on_notify(ch)
114
- end
115
- end
116
- end
117
-
118
- def escape_channel(ch)
119
- ch.gsub(/[^a-zA-Z0-9]/, '_')
120
- end
121
-
122
- def receive_lines(ch, lines)
123
- return if lines.empty?
124
- begin
125
- for r in lines
126
- h = {"channel" => ch}
127
- @keynames.each do |k|
128
- type = KEY_MAP[k][1]
129
- value = r.send(KEY_MAP[k][0])
130
- h[k]=case type
131
- when :string
132
- @receive_handlers.call(value.to_s)
133
- when :array
134
- value.map {|v| @receive_handlers.call(v.to_s)}
135
- else
136
- raise "Unknown value type: #{type}"
137
- end
138
- end
139
- #h = Hash[@keynames.map {|k| [k, r.send(KEY_MAP[k][0]).to_s]}]
140
- router.emit(@tag, Fluent::Engine.now, h)
141
- end
142
- rescue => e
143
- log.error "unexpected error", error: e
144
- log.error_backtrace
145
- end
146
- end
147
-
148
- def on_notify(ch)
149
- el = Win32::EventLog.open(ch)
150
-
151
- current_oldest_record_number = el.oldest_record_number
152
- current_total_records = el.total_records
153
-
154
- read_start, read_num = @pos_storage.get(ch)
155
-
156
- # if total_records is zero, oldest_record_number has no meaning.
157
- if current_total_records == 0
158
- return
159
- end
160
-
161
- if read_start == 0 && read_num == 0
162
- @pos_storage.put(ch, [current_oldest_record_number, current_total_records])
163
- return
164
- end
165
-
166
- current_end = current_oldest_record_number + current_total_records - 1
167
- old_end = read_start + read_num - 1
168
-
169
- if current_oldest_record_number < read_start
170
- # may be a record number rotated.
171
- current_end += 0xFFFFFFFF
172
- end
173
-
174
- if current_end < old_end
175
- # something occured.
176
- @pos_storage.put(ch, [current_oldest_record_number, current_total_records])
177
- return
178
- end
179
-
180
- winlogs = el.read(Win32::EventLog::SEEK_READ | Win32::EventLog::FORWARDS_READ, old_end + 1)
181
- receive_lines(ch, winlogs)
182
- @pos_storage.put(ch, [read_start, read_num + winlogs.size])
183
- ensure
184
- el.close
185
- end
186
- end
187
- end
1
+ require 'win32/eventlog'
2
+ require 'fluent/plugin/input'
3
+ require 'fluent/plugin'
4
+
5
+ module Fluent::Plugin
6
+ class WindowsEventLogInput < Input
7
+ Fluent::Plugin.register_input('windows_eventlog', self)
8
+
9
+ helpers :timer, :storage
10
+
11
+ DEFAULT_STORAGE_TYPE = 'local'
12
+ KEY_MAP = {"record_number" => [:record_number, :string],
13
+ "time_generated" => [:time_generated, :string],
14
+ "time_written" => [:time_written, :string],
15
+ "event_id" => [:event_id, :string],
16
+ "event_type" => [:event_type, :string],
17
+ "event_category" => [:category, :string],
18
+ "source_name" => [:source, :string],
19
+ "computer_name" => [:computer, :string],
20
+ "user" => [:user, :string],
21
+ "description" => [:description, :string],
22
+ "string_inserts" => [:string_inserts, :array]}
23
+
24
+ config_param :tag, :string
25
+ config_param :read_interval, :time, default: 2
26
+ config_param :pos_file, :string, default: nil,
27
+ obsoleted: "This section is not used anymore. Use 'store_pos' instead."
28
+ config_param :channels, :array, default: ['application']
29
+ config_param :keys, :array, default: []
30
+ config_param :read_from_head, :bool, default: false
31
+ config_param :from_encoding, :string, default: nil
32
+ config_param :encoding, :string, default: nil
33
+ desc "Parse 'description' field and set parsed result into event record. 'description' and 'string_inserts' fields are removed from the record"
34
+ config_param :parse_description, :bool, default: false
35
+
36
+ config_section :storage do
37
+ config_set_default :usage, "positions"
38
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
39
+ config_set_default :persistent, true
40
+ end
41
+
42
+ attr_reader :chs
43
+
44
+ def initialize
45
+ super
46
+ @chs = []
47
+ @keynames = []
48
+ @tails = {}
49
+ end
50
+
51
+ def configure(conf)
52
+ super
53
+ @chs = @channels.map {|ch| ch.strip.downcase }.uniq
54
+ if @chs.empty?
55
+ raise Fluent::ConfigError, "windows_eventlog: 'channels' parameter is required on windows_eventlog input"
56
+ end
57
+ @keynames = @keys.map {|k| k.strip }.uniq
58
+ if @keynames.empty?
59
+ @keynames = KEY_MAP.keys
60
+ end
61
+ @keynames.delete('string_inserts') if @parse_description
62
+
63
+ @tag = tag
64
+ @stop = false
65
+ configure_encoding
66
+ @receive_handlers = if @encoding
67
+ method(:encode_record)
68
+ else
69
+ method(:no_encode_record)
70
+ end
71
+ @pos_storage = storage_create(usage: "positions")
72
+ end
73
+
74
+ def configure_encoding
75
+ unless @encoding
76
+ if @from_encoding
77
+ raise Fluent::ConfigError, "windows_eventlog: 'from_encoding' parameter must be specied with 'encoding' parameter."
78
+ end
79
+ end
80
+
81
+ @encoding = parse_encoding_param(@encoding) if @encoding
82
+ @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
83
+ end
84
+
85
+ def parse_encoding_param(encoding_name)
86
+ begin
87
+ Encoding.find(encoding_name) if encoding_name
88
+ rescue ArgumentError => e
89
+ raise Fluent::ConfigError, e.message
90
+ end
91
+ end
92
+
93
+ def encode_record(record)
94
+ if @encoding
95
+ if @from_encoding
96
+ record.encode!(@encoding, @from_encoding)
97
+ else
98
+ record.force_encoding(@encoding)
99
+ end
100
+ end
101
+ end
102
+
103
+ def no_encode_record(record)
104
+ record
105
+ end
106
+
107
+ def start
108
+ super
109
+ @chs.each do |ch|
110
+ start, num = @pos_storage.get(ch)
111
+ if @read_from_head || (!num || num.zero?)
112
+ el = Win32::EventLog.open(ch)
113
+ @pos_storage.put(ch, [el.oldest_record_number - 1, 1])
114
+ el.close
115
+ end
116
+ timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
117
+ on_notify(ch)
118
+ end
119
+ end
120
+ end
121
+
122
+ def escape_channel(ch)
123
+ ch.gsub(/[^a-zA-Z0-9]/, '_')
124
+ end
125
+
126
+ def receive_lines(ch, lines)
127
+ return if lines.empty?
128
+ begin
129
+ for r in lines
130
+ h = {"channel" => ch}
131
+ @keynames.each do |k|
132
+ type = KEY_MAP[k][1]
133
+ value = r.send(KEY_MAP[k][0])
134
+ h[k]=case type
135
+ when :string
136
+ @receive_handlers.call(value.to_s)
137
+ when :array
138
+ value.map {|v| @receive_handlers.call(v.to_s)}
139
+ else
140
+ raise "Unknown value type: #{type}"
141
+ end
142
+ end
143
+ parse_desc(h) if @parse_description
144
+ #h = Hash[@keynames.map {|k| [k, r.send(KEY_MAP[k][0]).to_s]}]
145
+ router.emit(@tag, Fluent::Engine.now, h)
146
+ end
147
+ rescue => e
148
+ log.error "unexpected error", error: e
149
+ log.error_backtrace
150
+ end
151
+ end
152
+
153
+ def on_notify(ch)
154
+ el = Win32::EventLog.open(ch)
155
+
156
+ current_oldest_record_number = el.oldest_record_number
157
+ current_total_records = el.total_records
158
+
159
+ read_start, read_num = @pos_storage.get(ch)
160
+
161
+ # if total_records is zero, oldest_record_number has no meaning.
162
+ if current_total_records == 0
163
+ return
164
+ end
165
+
166
+ if read_start == 0 && read_num == 0
167
+ @pos_storage.put(ch, [current_oldest_record_number, current_total_records])
168
+ return
169
+ end
170
+
171
+ current_end = current_oldest_record_number + current_total_records - 1
172
+ old_end = read_start + read_num - 1
173
+
174
+ if current_oldest_record_number < read_start
175
+ # may be a record number rotated.
176
+ current_end += 0xFFFFFFFF
177
+ end
178
+
179
+ if current_end < old_end
180
+ # something occured.
181
+ @pos_storage.put(ch, [current_oldest_record_number, current_total_records])
182
+ return
183
+ end
184
+
185
+ winlogs = el.read(Win32::EventLog::SEEK_READ | Win32::EventLog::FORWARDS_READ, old_end + 1)
186
+ receive_lines(ch, winlogs)
187
+ @pos_storage.put(ch, [read_start, read_num + winlogs.size])
188
+ ensure
189
+ el.close
190
+ end
191
+
192
+ GROUP_DELIMITER = "\r\n\r\n".freeze
193
+ RECORD_DELIMITER = "\r\n\t".freeze
194
+ FIELD_DELIMITER = "\t\t".freeze
195
+ NONE_FIELD_DELIMITER = "\t".freeze
196
+
197
+ def parse_desc(record)
198
+ desc = record.delete('description'.freeze)
199
+ return if desc.nil?
200
+
201
+ elems = desc.split(GROUP_DELIMITER)
202
+ record['description_title'] = elems.shift
203
+ elems.each { |elem|
204
+ parent_key = nil
205
+ elem.split(RECORD_DELIMITER).each { |r|
206
+ key, value = if r.index(FIELD_DELIMITER)
207
+ r.split(FIELD_DELIMITER)
208
+ else
209
+ r.split(NONE_FIELD_DELIMITER)
210
+ end
211
+ key.chop! # remove ':' from key
212
+ if value.nil?
213
+ parent_key = to_key(key)
214
+ else
215
+ # parsed value sometimes contain unexpected "\t". So remove it.
216
+ value.strip!
217
+ if parent_key.nil?
218
+ record[to_key(key)] = value
219
+ else
220
+ k = "#{parent_key}.#{to_key(key)}"
221
+ record[k] = value
222
+ end
223
+ end
224
+ }
225
+ }
226
+ end
227
+
228
+ def to_key(key)
229
+ key.downcase!
230
+ key.gsub!(' '.freeze, '_'.freeze)
231
+ key
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,227 @@
1
+ require 'winevt'
2
+ require 'fluent/plugin/input'
3
+ require 'fluent/plugin'
4
+
5
+ module Fluent::Plugin
6
+ class WindowsEventLog2Input < Input
7
+ Fluent::Plugin.register_input('windows_eventlog2', self)
8
+
9
+ helpers :timer, :storage, :parser
10
+
11
+ DEFAULT_STORAGE_TYPE = 'local'
12
+ KEY_MAP = {"ProviderName" => ["ProviderName", :string],
13
+ "ProviderGUID" => ["ProviderGUID", :string],
14
+ "EventID" => ["EventID", :string],
15
+ "Qualifiers" => ["Qualifiers", :string],
16
+ "Level" => ["Level", :string],
17
+ "Task" => ["Task", :string],
18
+ "Opcode" => ["Opcode", :string],
19
+ "Keywords" => ["Keywords", :string],
20
+ "TimeCreated" => ["TimeCreated", :string],
21
+ "EventRecordID" => ["EventRecordID", :string],
22
+ "ActivityID" => ["ActivityID", :string],
23
+ "RelatedActivityID" => ["RelatedActivityID", :string],
24
+ "ProcessID" => ["ProcessID", :string],
25
+ "ThreadID" => ["ThreadID", :string],
26
+ "Channel" => ["Channel", :string],
27
+ "Computer" => ["Computer", :string],
28
+ "UserID" => ["UserID", :string],
29
+ "Version" => ["Version", :string],
30
+ "Description" => ["Description", :string],
31
+ "EventData" => ["EventData", :array]}
32
+
33
+ config_param :tag, :string
34
+ config_param :read_interval, :time, default: 2
35
+ config_param :channels, :array, default: ['application']
36
+ config_param :keys, :array, default: []
37
+ config_param :read_from_head, :bool, default: false
38
+ config_param :parse_description, :bool, default: false
39
+ config_param :render_as_xml, :bool, default: true
40
+ config_param :rate_limit, :integer, default: Winevt::EventLog::Subscribe::RATE_INFINITE
41
+
42
+ config_section :storage do
43
+ config_set_default :usage, "bookmarks"
44
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
45
+ config_set_default :persistent, true
46
+ end
47
+
48
+ config_section :parse do
49
+ config_set_default :@type, 'winevt_xml'
50
+ config_set_default :estimate_current_event, false
51
+ end
52
+
53
+ def initalize
54
+ super
55
+ @chs = []
56
+ @keynames = []
57
+ end
58
+
59
+ def configure(conf)
60
+ super
61
+ @chs = @channels.map {|ch| ch.strip.downcase }.uniq
62
+ @keynames = @keys.map {|k| k.strip }.uniq
63
+ if @keynames.empty?
64
+ @keynames = KEY_MAP.keys
65
+ end
66
+ @keynames.delete('Qualifiers') unless @render_as_xml
67
+ @keynames.delete('EventData') if @parse_description
68
+
69
+ @tag = tag
70
+ @tailing = @read_from_head ? false : true
71
+ @bookmarks_storage = storage_create(usage: "bookmarks")
72
+ @winevt_xml = false
73
+ if @render_as_xml
74
+ @parser = parser_create
75
+ @winevt_xml = @parser.respond_to?(:winevt_xml?) && @parser.winevt_xml?
76
+ class << self
77
+ alias_method :on_notify, :on_notify_xml
78
+ end
79
+ else
80
+ class << self
81
+ alias_method :on_notify, :on_notify_hash
82
+ end
83
+ end
84
+ end
85
+
86
+ def start
87
+ super
88
+
89
+ @chs.each do |ch|
90
+ bookmarkXml = @bookmarks_storage.get(ch) || ""
91
+ subscribe = Winevt::EventLog::Subscribe.new
92
+ bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
93
+ subscribe.tail = @tailing
94
+ subscribe.subscribe(ch, "*", bookmark)
95
+ subscribe.render_as_xml = @render_as_xml
96
+ subscribe.rate_limit = @rate_limit
97
+ timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
98
+ on_notify(ch, subscribe)
99
+ end
100
+ end
101
+ end
102
+
103
+ def escape_channel(ch)
104
+ ch.gsub(/[^a-zA-Z0-9]/, '_')
105
+ end
106
+
107
+ def on_notify(ch, subscribe)
108
+ # for safety.
109
+ end
110
+
111
+ def on_notify_xml(ch, subscribe)
112
+ es = Fluent::MultiEventStream.new
113
+ begin
114
+ subscribe.each do |xml, message, string_inserts|
115
+ @parser.parse(xml) do |time, record|
116
+ # record.has_key?("EventData") for none parser checking.
117
+ if @winevt_xml
118
+ record["Description"] = message
119
+ record["EventData"] = string_inserts
120
+
121
+ h = {}
122
+ @keynames.each do |k|
123
+ type = KEY_MAP[k][1]
124
+ value = record[KEY_MAP[k][0]]
125
+ h[k]=case type
126
+ when :string
127
+ value.to_s
128
+ when :array
129
+ value.map {|v| v.to_s}
130
+ else
131
+ raise "Unknown value type: #{type}"
132
+ end
133
+ end
134
+ parse_desc(h) if @parse_description
135
+ es.add(Fluent::Engine.now, h)
136
+ else
137
+ record["Description"] = message
138
+ record["EventData"] = string_inserts
139
+ # for none parser
140
+ es.add(Fluent::Engine.now, record)
141
+ end
142
+ end
143
+ end
144
+ rescue Winevt::EventLog::Query::Error => e
145
+ log.warn "Invalid XML data", error: e
146
+ log.warn_backtrace
147
+ end
148
+ router.emit_stream(@tag, es)
149
+ @bookmarks_storage.put(ch, subscribe.bookmark)
150
+ end
151
+
152
+ def on_notify_hash(ch, subscribe)
153
+ es = Fluent::MultiEventStream.new
154
+ begin
155
+ subscribe.each do |record, message, string_inserts|
156
+ record["Description"] = message
157
+ record["EventData"] = string_inserts
158
+ h = {}
159
+ @keynames.each do |k|
160
+ type = KEY_MAP[k][1]
161
+ value = record[KEY_MAP[k][0]]
162
+ h[k]=case type
163
+ when :string
164
+ value.to_s
165
+ when :array
166
+ value.map {|v| v.to_s}
167
+ else
168
+ raise "Unknown value type: #{type}"
169
+ end
170
+ end
171
+ parse_desc(h) if @parse_description
172
+ es.add(Fluent::Engine.now, h)
173
+ end
174
+ rescue Winevt::EventLog::Query::Error => e
175
+ log.warn "Invalid Hash data", error: e
176
+ log.warn_backtrace
177
+ end
178
+ router.emit_stream(@tag, es)
179
+ @bookmarks_storage.put(ch, subscribe.bookmark)
180
+ end
181
+
182
+ #### These lines copied from in_windows_eventlog plugin:
183
+ #### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
184
+ GROUP_DELIMITER = "\r\n\r\n".freeze
185
+ RECORD_DELIMITER = "\r\n\t".freeze
186
+ FIELD_DELIMITER = "\t\t".freeze
187
+ NONE_FIELD_DELIMITER = "\t".freeze
188
+
189
+ def parse_desc(record)
190
+ desc = record.delete("Description".freeze)
191
+ return if desc.nil?
192
+
193
+ elems = desc.split(GROUP_DELIMITER)
194
+ record['DescriptionTitle'] = elems.shift
195
+ elems.each { |elem|
196
+ parent_key = nil
197
+ elem.split(RECORD_DELIMITER).each { |r|
198
+ key, value = if r.index(FIELD_DELIMITER)
199
+ r.split(FIELD_DELIMITER)
200
+ else
201
+ r.split(NONE_FIELD_DELIMITER)
202
+ end
203
+ key.chop! # remove ':' from key
204
+ if value.nil?
205
+ parent_key = to_key(key)
206
+ else
207
+ # parsed value sometimes contain unexpected "\t". So remove it.
208
+ value.strip!
209
+ if parent_key.nil?
210
+ record[to_key(key)] = value
211
+ else
212
+ k = "#{parent_key}.#{to_key(key)}"
213
+ record[k] = value
214
+ end
215
+ end
216
+ }
217
+ }
218
+ end
219
+
220
+ def to_key(key)
221
+ key.downcase!
222
+ key.gsub!(' '.freeze, '_'.freeze)
223
+ key
224
+ end
225
+ ####
226
+ end
227
+ end