fluent-plugin-windows-eventlog 0.2.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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