fluent-plugin-windows-eventlog 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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