fluent-plugin-windows-eventlog 0.8.1 → 0.8.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.
- checksums.yaml +4 -4
- data/.github/workflows/unit-test.yml +36 -34
- data/.gitignore +14 -14
- data/CHANGELOG.md +77 -74
- data/Gemfile +4 -4
- data/LICENSE.txt +203 -203
- data/README.md +387 -387
- data/Rakefile +10 -10
- data/appveyor.yml +24 -24
- data/fluent-plugin-winevtlog.gemspec +28 -28
- data/lib/fluent/plugin/bookmark_sax_parser.rb +30 -30
- data/lib/fluent/plugin/in_windows_eventlog.rb +241 -241
- data/lib/fluent/plugin/in_windows_eventlog2.rb +410 -406
- data/test/generate-windows-event.rb +47 -47
- data/test/helper.rb +34 -34
- data/test/plugin/test_bookmark_sax_parser.rb +41 -41
- data/test/plugin/test_in_windows_eventlog2.rb +619 -600
- data/test/plugin/test_in_winevtlog.rb +48 -48
- metadata +8 -8
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
2
|
-
require "rake/testtask"
|
3
|
-
|
4
|
-
Rake::TestTask.new(:test) do |test|
|
5
|
-
test.libs << 'lib' << 'test'
|
6
|
-
test.pattern = 'test/**/test_*.rb'
|
7
|
-
test.verbose = true
|
8
|
-
end
|
9
|
-
|
10
|
-
task default: :test
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |test|
|
5
|
+
test.libs << 'lib' << 'test'
|
6
|
+
test.pattern = 'test/**/test_*.rb'
|
7
|
+
test.verbose = true
|
8
|
+
end
|
9
|
+
|
10
|
+
task default: :test
|
data/appveyor.yml
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
version: '{build}'
|
2
|
-
|
3
|
-
# init:
|
4
|
-
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
5
|
-
|
6
|
-
install:
|
7
|
-
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
8
|
-
- ruby --version
|
9
|
-
- gem --version
|
10
|
-
- ridk.cmd exec bundle install
|
11
|
-
build: off
|
12
|
-
test_script:
|
13
|
-
- bundle exec rake test
|
14
|
-
# - bundle exec rake test TESTOPTS=-v
|
15
|
-
|
16
|
-
branches:
|
17
|
-
only:
|
18
|
-
- master
|
19
|
-
|
20
|
-
# https://www.appveyor.com/docs/installed-software/#ruby
|
21
|
-
environment:
|
22
|
-
matrix:
|
23
|
-
- ruby_version: "24-x64"
|
24
|
-
- ruby_version: "24"
|
1
|
+
version: '{build}'
|
2
|
+
|
3
|
+
# init:
|
4
|
+
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
5
|
+
|
6
|
+
install:
|
7
|
+
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
8
|
+
- ruby --version
|
9
|
+
- gem --version
|
10
|
+
- ridk.cmd exec bundle install
|
11
|
+
build: off
|
12
|
+
test_script:
|
13
|
+
- bundle exec rake test
|
14
|
+
# - bundle exec rake test TESTOPTS=-v
|
15
|
+
|
16
|
+
branches:
|
17
|
+
only:
|
18
|
+
- master
|
19
|
+
|
20
|
+
# https://www.appveyor.com/docs/installed-software/#ruby
|
21
|
+
environment:
|
22
|
+
matrix:
|
23
|
+
- ruby_version: "24-x64"
|
24
|
+
- ruby_version: "24"
|
@@ -1,28 +1,28 @@
|
|
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.8.
|
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.4.0"
|
23
|
-
spec.add_development_dependency "nokogiri", [">= 1.10", "< 1.12"]
|
24
|
-
spec.add_development_dependency "fluent-plugin-parser-winevt_xml", ">= 0.1.2"
|
25
|
-
spec.add_runtime_dependency "fluentd", [">= 0.14.12", "< 2"]
|
26
|
-
spec.add_runtime_dependency "win32-eventlog"
|
27
|
-
spec.add_runtime_dependency "winevt_c", ">= 0.
|
28
|
-
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.8.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 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.4.0"
|
23
|
+
spec.add_development_dependency "nokogiri", [">= 1.10", "< 1.12"]
|
24
|
+
spec.add_development_dependency "fluent-plugin-parser-winevt_xml", ">= 0.1.2"
|
25
|
+
spec.add_runtime_dependency "fluentd", [">= 0.14.12", "< 2"]
|
26
|
+
spec.add_runtime_dependency "win32-eventlog"
|
27
|
+
spec.add_runtime_dependency "winevt_c", ">= 0.10.1"
|
28
|
+
end
|
@@ -1,30 +1,30 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
|
3
|
-
class WinevtBookmarkDocument < Nokogiri::XML::SAX::Document
|
4
|
-
attr_reader :result
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@result = {}
|
8
|
-
super
|
9
|
-
end
|
10
|
-
|
11
|
-
def start_document
|
12
|
-
end
|
13
|
-
|
14
|
-
def start_element(name, attributes = [])
|
15
|
-
if name == "Bookmark"
|
16
|
-
@result[:channel] = attributes[0][1] rescue nil
|
17
|
-
@result[:record_id] = attributes[1][1].to_i rescue nil
|
18
|
-
@result[:is_current] = attributes[2][1].downcase == "true" rescue nil
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def characters(string)
|
23
|
-
end
|
24
|
-
|
25
|
-
def end_element(name, attributes = [])
|
26
|
-
end
|
27
|
-
|
28
|
-
def end_document
|
29
|
-
end
|
30
|
-
end
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class WinevtBookmarkDocument < Nokogiri::XML::SAX::Document
|
4
|
+
attr_reader :result
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@result = {}
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_document
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_element(name, attributes = [])
|
15
|
+
if name == "Bookmark"
|
16
|
+
@result[:channel] = attributes[0][1] rescue nil
|
17
|
+
@result[:record_id] = attributes[1][1].to_i rescue nil
|
18
|
+
@result[:is_current] = attributes[2][1].downcase == "true" rescue nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def characters(string)
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_element(name, attributes = [])
|
26
|
+
end
|
27
|
+
|
28
|
+
def end_document
|
29
|
+
end
|
30
|
+
end
|
@@ -1,241 +1,241 @@
|
|
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
|
-
log.warn "in_windows_eventlog is deprecated. It will be removed in the future version."
|
53
|
-
super
|
54
|
-
@chs = @channels.map {|ch| ch.strip.downcase }.uniq
|
55
|
-
if @chs.empty?
|
56
|
-
raise Fluent::ConfigError, "windows_eventlog: 'channels' parameter is required on windows_eventlog input"
|
57
|
-
end
|
58
|
-
@keynames = @keys.map {|k| k.strip }.uniq
|
59
|
-
if @keynames.empty?
|
60
|
-
@keynames = KEY_MAP.keys
|
61
|
-
end
|
62
|
-
@keynames.delete('string_inserts') if @parse_description
|
63
|
-
|
64
|
-
@tag = tag
|
65
|
-
@stop = false
|
66
|
-
configure_encoding
|
67
|
-
@receive_handlers = if @encoding
|
68
|
-
method(:encode_record)
|
69
|
-
else
|
70
|
-
method(:no_encode_record)
|
71
|
-
end
|
72
|
-
@pos_storage = storage_create(usage: "positions")
|
73
|
-
end
|
74
|
-
|
75
|
-
def configure_encoding
|
76
|
-
unless @encoding
|
77
|
-
if @from_encoding
|
78
|
-
raise Fluent::ConfigError, "windows_eventlog: 'from_encoding' parameter must be specied with 'encoding' parameter."
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
@encoding = parse_encoding_param(@encoding) if @encoding
|
83
|
-
@from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
|
84
|
-
end
|
85
|
-
|
86
|
-
def parse_encoding_param(encoding_name)
|
87
|
-
begin
|
88
|
-
Encoding.find(encoding_name) if encoding_name
|
89
|
-
rescue ArgumentError => e
|
90
|
-
raise Fluent::ConfigError, e.message
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def encode_record(record)
|
95
|
-
if @encoding
|
96
|
-
if @from_encoding
|
97
|
-
record.encode!(@encoding, @from_encoding)
|
98
|
-
else
|
99
|
-
record.force_encoding(@encoding)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def no_encode_record(record)
|
105
|
-
record
|
106
|
-
end
|
107
|
-
|
108
|
-
def start
|
109
|
-
super
|
110
|
-
@chs.each do |ch|
|
111
|
-
start, num = @pos_storage.get(ch)
|
112
|
-
if @read_from_head || (!num || num.zero?)
|
113
|
-
el = Win32::EventLog.open(ch)
|
114
|
-
@pos_storage.put(ch, [el.oldest_record_number - 1, 1])
|
115
|
-
el.close
|
116
|
-
end
|
117
|
-
timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
|
118
|
-
on_notify(ch)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def escape_channel(ch)
|
124
|
-
ch.gsub(/[^a-zA-Z0-9]/, '_')
|
125
|
-
end
|
126
|
-
|
127
|
-
def receive_lines(ch, lines)
|
128
|
-
return if lines.empty?
|
129
|
-
begin
|
130
|
-
for r in lines
|
131
|
-
h = {"channel" => ch}
|
132
|
-
@keynames.each do |k|
|
133
|
-
type = KEY_MAP[k][1]
|
134
|
-
value = r.send(KEY_MAP[k][0])
|
135
|
-
h[k]=case type
|
136
|
-
when :string
|
137
|
-
@receive_handlers.call(value.to_s)
|
138
|
-
when :array
|
139
|
-
value.map {|v| @receive_handlers.call(v.to_s)}
|
140
|
-
else
|
141
|
-
raise "Unknown value type: #{type}"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
parse_desc(h) if @parse_description
|
145
|
-
#h = Hash[@keynames.map {|k| [k, r.send(KEY_MAP[k][0]).to_s]}]
|
146
|
-
router.emit(@tag, Fluent::Engine.now, h)
|
147
|
-
end
|
148
|
-
rescue => e
|
149
|
-
log.error "unexpected error", error: e
|
150
|
-
log.error_backtrace
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def on_notify(ch)
|
155
|
-
begin
|
156
|
-
el = Win32::EventLog.open(ch)
|
157
|
-
rescue => e
|
158
|
-
log.error "Failed to open Windows Event log.", error: e
|
159
|
-
end
|
160
|
-
|
161
|
-
current_oldest_record_number = el.oldest_record_number
|
162
|
-
current_total_records = el.total_records
|
163
|
-
|
164
|
-
read_start, read_num = @pos_storage.get(ch)
|
165
|
-
|
166
|
-
# if total_records is zero, oldest_record_number has no meaning.
|
167
|
-
if current_total_records == 0
|
168
|
-
return
|
169
|
-
end
|
170
|
-
|
171
|
-
if read_start == 0 && read_num == 0
|
172
|
-
@pos_storage.put(ch, [current_oldest_record_number, current_total_records])
|
173
|
-
return
|
174
|
-
end
|
175
|
-
|
176
|
-
current_end = current_oldest_record_number + current_total_records - 1
|
177
|
-
old_end = read_start + read_num - 1
|
178
|
-
|
179
|
-
if current_oldest_record_number < read_start
|
180
|
-
# may be a record number rotated.
|
181
|
-
current_end += 0xFFFFFFFF
|
182
|
-
end
|
183
|
-
|
184
|
-
if current_end < old_end
|
185
|
-
# something occured.
|
186
|
-
@pos_storage.put(ch, [current_oldest_record_number, current_total_records])
|
187
|
-
return
|
188
|
-
end
|
189
|
-
|
190
|
-
winlogs = el.read(Win32::EventLog::SEEK_READ | Win32::EventLog::FORWARDS_READ, old_end + 1)
|
191
|
-
receive_lines(ch, winlogs)
|
192
|
-
@pos_storage.put(ch, [read_start, read_num + winlogs.size])
|
193
|
-
ensure
|
194
|
-
if el
|
195
|
-
el.close
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
GROUP_DELIMITER = "\r\n\r\n".freeze
|
200
|
-
RECORD_DELIMITER = "\r\n\t".freeze
|
201
|
-
FIELD_DELIMITER = "\t\t".freeze
|
202
|
-
NONE_FIELD_DELIMITER = "\t".freeze
|
203
|
-
|
204
|
-
def parse_desc(record)
|
205
|
-
desc = record.delete('description'.freeze)
|
206
|
-
return if desc.nil?
|
207
|
-
|
208
|
-
elems = desc.split(GROUP_DELIMITER)
|
209
|
-
record['description_title'] = elems.shift
|
210
|
-
elems.each { |elem|
|
211
|
-
parent_key = nil
|
212
|
-
elem.split(RECORD_DELIMITER).each { |r|
|
213
|
-
key, value = if r.index(FIELD_DELIMITER)
|
214
|
-
r.split(FIELD_DELIMITER)
|
215
|
-
else
|
216
|
-
r.split(NONE_FIELD_DELIMITER)
|
217
|
-
end
|
218
|
-
key.chop! # remove ':' from key
|
219
|
-
if value.nil?
|
220
|
-
parent_key = to_key(key)
|
221
|
-
else
|
222
|
-
# parsed value sometimes contain unexpected "\t". So remove it.
|
223
|
-
value.strip!
|
224
|
-
if parent_key.nil?
|
225
|
-
record[to_key(key)] = value
|
226
|
-
else
|
227
|
-
k = "#{parent_key}.#{to_key(key)}"
|
228
|
-
record[k] = value
|
229
|
-
end
|
230
|
-
end
|
231
|
-
}
|
232
|
-
}
|
233
|
-
end
|
234
|
-
|
235
|
-
def to_key(key)
|
236
|
-
key.downcase!
|
237
|
-
key.gsub!(' '.freeze, '_'.freeze)
|
238
|
-
key
|
239
|
-
end
|
240
|
-
end
|
241
|
-
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
|
+
log.warn "in_windows_eventlog is deprecated. It will be removed in the future version."
|
53
|
+
super
|
54
|
+
@chs = @channels.map {|ch| ch.strip.downcase }.uniq
|
55
|
+
if @chs.empty?
|
56
|
+
raise Fluent::ConfigError, "windows_eventlog: 'channels' parameter is required on windows_eventlog input"
|
57
|
+
end
|
58
|
+
@keynames = @keys.map {|k| k.strip }.uniq
|
59
|
+
if @keynames.empty?
|
60
|
+
@keynames = KEY_MAP.keys
|
61
|
+
end
|
62
|
+
@keynames.delete('string_inserts') if @parse_description
|
63
|
+
|
64
|
+
@tag = tag
|
65
|
+
@stop = false
|
66
|
+
configure_encoding
|
67
|
+
@receive_handlers = if @encoding
|
68
|
+
method(:encode_record)
|
69
|
+
else
|
70
|
+
method(:no_encode_record)
|
71
|
+
end
|
72
|
+
@pos_storage = storage_create(usage: "positions")
|
73
|
+
end
|
74
|
+
|
75
|
+
def configure_encoding
|
76
|
+
unless @encoding
|
77
|
+
if @from_encoding
|
78
|
+
raise Fluent::ConfigError, "windows_eventlog: 'from_encoding' parameter must be specied with 'encoding' parameter."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@encoding = parse_encoding_param(@encoding) if @encoding
|
83
|
+
@from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_encoding_param(encoding_name)
|
87
|
+
begin
|
88
|
+
Encoding.find(encoding_name) if encoding_name
|
89
|
+
rescue ArgumentError => e
|
90
|
+
raise Fluent::ConfigError, e.message
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def encode_record(record)
|
95
|
+
if @encoding
|
96
|
+
if @from_encoding
|
97
|
+
record.encode!(@encoding, @from_encoding)
|
98
|
+
else
|
99
|
+
record.force_encoding(@encoding)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def no_encode_record(record)
|
105
|
+
record
|
106
|
+
end
|
107
|
+
|
108
|
+
def start
|
109
|
+
super
|
110
|
+
@chs.each do |ch|
|
111
|
+
start, num = @pos_storage.get(ch)
|
112
|
+
if @read_from_head || (!num || num.zero?)
|
113
|
+
el = Win32::EventLog.open(ch)
|
114
|
+
@pos_storage.put(ch, [el.oldest_record_number - 1, 1])
|
115
|
+
el.close
|
116
|
+
end
|
117
|
+
timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
|
118
|
+
on_notify(ch)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def escape_channel(ch)
|
124
|
+
ch.gsub(/[^a-zA-Z0-9]/, '_')
|
125
|
+
end
|
126
|
+
|
127
|
+
def receive_lines(ch, lines)
|
128
|
+
return if lines.empty?
|
129
|
+
begin
|
130
|
+
for r in lines
|
131
|
+
h = {"channel" => ch}
|
132
|
+
@keynames.each do |k|
|
133
|
+
type = KEY_MAP[k][1]
|
134
|
+
value = r.send(KEY_MAP[k][0])
|
135
|
+
h[k]=case type
|
136
|
+
when :string
|
137
|
+
@receive_handlers.call(value.to_s)
|
138
|
+
when :array
|
139
|
+
value.map {|v| @receive_handlers.call(v.to_s)}
|
140
|
+
else
|
141
|
+
raise "Unknown value type: #{type}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
parse_desc(h) if @parse_description
|
145
|
+
#h = Hash[@keynames.map {|k| [k, r.send(KEY_MAP[k][0]).to_s]}]
|
146
|
+
router.emit(@tag, Fluent::Engine.now, h)
|
147
|
+
end
|
148
|
+
rescue => e
|
149
|
+
log.error "unexpected error", error: e
|
150
|
+
log.error_backtrace
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_notify(ch)
|
155
|
+
begin
|
156
|
+
el = Win32::EventLog.open(ch)
|
157
|
+
rescue => e
|
158
|
+
log.error "Failed to open Windows Event log.", error: e
|
159
|
+
end
|
160
|
+
|
161
|
+
current_oldest_record_number = el.oldest_record_number
|
162
|
+
current_total_records = el.total_records
|
163
|
+
|
164
|
+
read_start, read_num = @pos_storage.get(ch)
|
165
|
+
|
166
|
+
# if total_records is zero, oldest_record_number has no meaning.
|
167
|
+
if current_total_records == 0
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
if read_start == 0 && read_num == 0
|
172
|
+
@pos_storage.put(ch, [current_oldest_record_number, current_total_records])
|
173
|
+
return
|
174
|
+
end
|
175
|
+
|
176
|
+
current_end = current_oldest_record_number + current_total_records - 1
|
177
|
+
old_end = read_start + read_num - 1
|
178
|
+
|
179
|
+
if current_oldest_record_number < read_start
|
180
|
+
# may be a record number rotated.
|
181
|
+
current_end += 0xFFFFFFFF
|
182
|
+
end
|
183
|
+
|
184
|
+
if current_end < old_end
|
185
|
+
# something occured.
|
186
|
+
@pos_storage.put(ch, [current_oldest_record_number, current_total_records])
|
187
|
+
return
|
188
|
+
end
|
189
|
+
|
190
|
+
winlogs = el.read(Win32::EventLog::SEEK_READ | Win32::EventLog::FORWARDS_READ, old_end + 1)
|
191
|
+
receive_lines(ch, winlogs)
|
192
|
+
@pos_storage.put(ch, [read_start, read_num + winlogs.size])
|
193
|
+
ensure
|
194
|
+
if el
|
195
|
+
el.close
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
GROUP_DELIMITER = "\r\n\r\n".freeze
|
200
|
+
RECORD_DELIMITER = "\r\n\t".freeze
|
201
|
+
FIELD_DELIMITER = "\t\t".freeze
|
202
|
+
NONE_FIELD_DELIMITER = "\t".freeze
|
203
|
+
|
204
|
+
def parse_desc(record)
|
205
|
+
desc = record.delete('description'.freeze)
|
206
|
+
return if desc.nil?
|
207
|
+
|
208
|
+
elems = desc.split(GROUP_DELIMITER)
|
209
|
+
record['description_title'] = elems.shift
|
210
|
+
elems.each { |elem|
|
211
|
+
parent_key = nil
|
212
|
+
elem.split(RECORD_DELIMITER).each { |r|
|
213
|
+
key, value = if r.index(FIELD_DELIMITER)
|
214
|
+
r.split(FIELD_DELIMITER)
|
215
|
+
else
|
216
|
+
r.split(NONE_FIELD_DELIMITER)
|
217
|
+
end
|
218
|
+
key.chop! # remove ':' from key
|
219
|
+
if value.nil?
|
220
|
+
parent_key = to_key(key)
|
221
|
+
else
|
222
|
+
# parsed value sometimes contain unexpected "\t". So remove it.
|
223
|
+
value.strip!
|
224
|
+
if parent_key.nil?
|
225
|
+
record[to_key(key)] = value
|
226
|
+
else
|
227
|
+
k = "#{parent_key}.#{to_key(key)}"
|
228
|
+
record[k] = value
|
229
|
+
end
|
230
|
+
end
|
231
|
+
}
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
def to_key(key)
|
236
|
+
key.downcase!
|
237
|
+
key.gsub!(' '.freeze, '_'.freeze)
|
238
|
+
key
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|