fluent-plugin-windows-eventlog 0.2.2 → 0.3.0

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,25 +1,27 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "fluent-plugin-windows-eventlog"
7
- spec.version = "0.2.2"
8
- spec.authors = ["okahashi117", "Hiroshi Hatake", "Masahiro Nakagawa"]
9
- spec.email = ["naruki_okahashi@jbat.co.jp", "cosmo0920.oucc@gmail.com", "repeatedly@gmail.com"]
10
- spec.summary = %q{Fluentd Input plugin to read windows event log.}
11
- spec.description = %q{Fluentd Input plugin to read windwos event log.}
12
- spec.homepage = "https://github.com/fluent/fluent-plugin-windows-eventlog"
13
- spec.license = "Apache-2.0"
14
-
15
- spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_development_dependency "bundler"
21
- spec.add_development_dependency "rake"
22
- spec.add_development_dependency "test-unit", "~> 3.2.0"
23
- spec.add_runtime_dependency "fluentd", [">= 0.14.12", "< 2"]
24
- spec.add_runtime_dependency "win32-eventlog"
25
- end
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-windows-eventlog"
7
+ spec.version = "0.3.0"
8
+ spec.authors = ["okahashi117", "Hiroshi Hatake", "Masahiro Nakagawa"]
9
+ spec.email = ["naruki_okahashi@jbat.co.jp", "cosmo0920.oucc@gmail.com", "repeatedly@gmail.com"]
10
+ spec.summary = %q{Fluentd Input plugin to read windows event log.}
11
+ spec.description = %q{Fluentd Input plugin to read windows event log.}
12
+ spec.homepage = "https://github.com/fluent/fluent-plugin-windows-eventlog"
13
+ spec.license = "Apache-2.0"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "test-unit", "~> 3.2.0"
23
+ spec.add_runtime_dependency "fluentd", [">= 0.14.12", "< 2"]
24
+ spec.add_runtime_dependency "win32-eventlog"
25
+ spec.add_runtime_dependency "winevt_c"
26
+ spec.add_runtime_dependency "nokogiri", "~> 1.10"
27
+ end
@@ -1,234 +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
- 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
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