fluent-plugin-winevtlog 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f20f0df26f1be8901b84ec7cb3da2296af8be98b
4
+ data.tar.gz: 2bb0885a56495b74783de4ccc6c02e464ad37595
5
+ SHA512:
6
+ metadata.gz: a42c58a4c327a8e57e6d017162fc05b5aa4194f787505a22433a0744f0a005cffdb6ea2770a5e2b8ba89f0720b0ddced404cfa27d10ce103d7cf33cd66408e39
7
+ data.tar.gz: 89baa6e3dae8da65c5d66c29925c27039aa5132543573089f380eb376559a828047ad6d61fc16be0573253f17f3b5c767f9e3b9fc9978bfbfd0d470e1d717234
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /spec/reports/
8
+ pkg/*
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-winevtlog.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 okahashi117
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Fluent::Plugin::Winevtlog
2
+
3
+
data/Rakefile ADDED
@@ -0,0 +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
@@ -0,0 +1,24 @@
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-winevtlog"
7
+ spec.version = "0.0.2"
8
+ spec.authors = ["okahashi117"]
9
+ spec.email = ["naruki_okahashi@jbat.co.jp"]
10
+ spec.summary = %q{Input plugin to read windows event log.}
11
+ spec.description = %q{Input plugin to read windwos event log.}
12
+ spec.homepage = ""
13
+ spec.license = "Apache license"
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_runtime_dependency "fluentd"
23
+ spec.add_runtime_dependency "win32-eventlog"
24
+ end
@@ -0,0 +1,295 @@
1
+
2
+ require 'win32/eventlog'
3
+ include Win32
4
+
5
+ module Fluent
6
+ class WinEvtLog < Fluent::Input
7
+ Fluent::Plugin.register_input('winevtlog', self)
8
+
9
+ @@KEY_MAP = {"record_number" => :record_number,
10
+ "time_generated" => :time_generated,
11
+ "time_written" => :time_written,
12
+ "event_id" => :event_id,
13
+ "event_type" => :event_type,
14
+ "event_category" => :category,
15
+ "source_name" => :source,
16
+ "computer_name" => :computer,
17
+ "user" => :user,
18
+ "description" => :description}
19
+
20
+ config_param :tag, :string
21
+ config_param :read_interval, :time, :default => 2
22
+ config_param :pos_file, :string, :default => nil
23
+ config_param :category, :string, :default => 'Application'
24
+ config_param :keys, :string, :default => ''
25
+ config_param :read_from_head, :bool, :default => false
26
+
27
+ attr_reader :cats
28
+
29
+ def initialize
30
+ super
31
+ @cats = []
32
+ @keynames = []
33
+ @tails = {}
34
+ end
35
+
36
+ def configure(conf)
37
+ super
38
+ @cats = @category.split(',').map {|cat| cat.strip }.uniq
39
+ if @cats.empty?
40
+ raise ConfigError, "winevtlog: 'category' parameter is required on winevtlog input"
41
+ end
42
+ @keynames = @keys.split(',').map {|k| k.strip }.uniq
43
+ if @keynames.empty?
44
+ @keynames = @@KEY_MAP.keys
45
+ end
46
+ @tag = tag
47
+ @stop = false
48
+ end
49
+
50
+ def start
51
+ if @pos_file
52
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, DEFAULT_FILE_PERMISSION)
53
+ @pf_file.sync = true
54
+ @pf = PositionFile.parse(@pf_file)
55
+ end
56
+ @loop = Coolio::Loop.new
57
+ start_watchers(@cats)
58
+ @thread = Thread.new(&method(:run))
59
+ end
60
+
61
+ def shutdown
62
+ stop_watchers(@tails.keys, true)
63
+ @loop.stop rescue nil
64
+ @thread.join
65
+ @pf_file.close if @pf_file
66
+ end
67
+
68
+ def setup_wacther(cat, pe)
69
+ wlw = WindowsLogWatcher.new(cat, pe, &method(:receive_lines))
70
+ wlw.attach(@loop)
71
+ wlw
72
+ end
73
+
74
+ def start_watchers(cats)
75
+ cats.each { |cat|
76
+ pe = nil
77
+ if @pf
78
+ pe = @pf[cat]
79
+ if @read_from_head && pe.read_num.zero?
80
+ el = EventLog.open(cat)
81
+ pe.update(el.oldest_record_number-1,1)
82
+ el.close
83
+ end
84
+ end
85
+ @tails[cat] = setup_wacther(cat, pe)
86
+ }
87
+ end
88
+
89
+ def stop_watchers(cats, unwatched = false)
90
+ cats.each { |cat|
91
+ wlw = @tails.delete(cat)
92
+ if wlw
93
+ wlw.unwatched = unwatched
94
+ close_watcher(wlw)
95
+ end
96
+ }
97
+ end
98
+
99
+ def close_watcher(wlw)
100
+ wlw.close
101
+ # flush_buffer(wlw)
102
+ end
103
+
104
+ def run
105
+ @loop.run
106
+ rescue
107
+ $log.error "unexpected error", :error=>$!.to_s
108
+ $log.error_backtrace
109
+ end
110
+
111
+ def receive_lines(lines, pe)
112
+ return if lines.empty?
113
+ begin
114
+ for r in lines
115
+ h = Hash[@keynames.map {|k| [k, r.send(@@KEY_MAP[k])]}]
116
+ Engine.emit(@tag, Engine.now, h)
117
+ pe[1] +=1
118
+ end
119
+ rescue
120
+ $log.error "unexpected error", :error=>$!.to_s
121
+ $log.error_backtrace
122
+ end
123
+ end
124
+
125
+
126
+ class WindowsLogWatcher
127
+ def initialize(cat, pe, &receive_lines)
128
+ @cat = cat
129
+ @pe = pe || MemoryPositionEntry.new
130
+ @receive_lines = receive_lines
131
+ @timer_trigger = TimerWatcher.new(1, true, &method(:on_notify))
132
+ end
133
+
134
+ attr_reader :cat
135
+ attr_accessor :unwatched
136
+ attr_accessor :pe
137
+
138
+ def attach(loop)
139
+ @timer_trigger.attach(loop)
140
+ on_notify
141
+ end
142
+
143
+ def detach
144
+ @timer_trigger.detach if @timer_trigger.attached?
145
+ end
146
+
147
+ def close
148
+ detach
149
+ end
150
+
151
+ def on_notify
152
+ el = EventLog.open(@cat)
153
+ rl_sn = [el.oldest_record_number, el.total_records]
154
+ pe_sn = [@pe.read_start, @pe.read_num]
155
+ # if total_records is zero, oldest_record_number has no meaning.
156
+ if rl_sn[1] == 0
157
+ return
158
+ end
159
+
160
+ if pe_sn[0] == 0 && pe_sn[1] == 0
161
+ @pe.update(rl_sn[0], rl_sn[1])
162
+ return
163
+ end
164
+
165
+ cur_end = rl_sn[0] + rl_sn[1] -1
166
+ old_end = pe_sn[0] + pe_sn[1] -1
167
+
168
+ if (rl_sn[0] < pe_sn[0])
169
+ # may be a record number rotated.
170
+ cur_end += 0xFFFFFFFF
171
+ end
172
+
173
+ if (cur_end <= old_end)
174
+ # something occured.
175
+ @pe.update(rl_sn[0], rl_sn[1])
176
+ return
177
+ end
178
+
179
+ read_more = false
180
+ begin
181
+ numlines = cur_end - old_end
182
+ winlogs = el.read(Windows::Constants::EVENTLOG_SEEK_READ | Windows::Constants::EVENTLOG_FORWARDS_READ, old_end + 1)
183
+ @receive_lines.call(winlogs, pe_sn)
184
+ @pe.update(pe_sn[0], pe_sn[1])
185
+ old_end = pe_sn[0] + pe_sn[1] -1
186
+ end while read_more
187
+ el.close
188
+
189
+ end
190
+
191
+ class TimerWatcher < Coolio::TimerWatcher
192
+ def initialize(interval, repeat, &callback)
193
+ @callback = callback
194
+ super(interval, repeat)
195
+ end
196
+
197
+ def on_timer
198
+ @callback.call
199
+ rescue
200
+ # TODO log?
201
+ $log.error $!.to_s
202
+ $log.error_backtrace
203
+ end
204
+ end
205
+ end
206
+
207
+ class PositionFile
208
+ def initialize(file, map, last_pos)
209
+ @file = file
210
+ @map = map
211
+ @last_pos = last_pos
212
+ end
213
+
214
+ def [](cat)
215
+ if m = @map[cat]
216
+ return m
217
+ end
218
+ @file.pos = @last_pos
219
+ @file.write cat
220
+ @file.write "\t"
221
+ seek = @file.pos
222
+ @file.write "00000000\t00000000\n"
223
+ @last_pos = @file.pos
224
+ @map[cat] = FilePositionEntry.new(@file, seek)
225
+ end
226
+
227
+ # parsing file and rebuild mysself
228
+ def self.parse(file)
229
+ map = {}
230
+ file.pos = 0
231
+ file.each_line {|line|
232
+ # check and get a matched line as m
233
+ m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
234
+ next unless m
235
+ cat = m[1]
236
+ pos = m[2].to_i(16)
237
+ seek = file.pos - line.bytesize + cat.bytesize + 1
238
+ map[cat] = FilePositionEntry.new(file, seek)
239
+ }
240
+ new(file, map, file.pos)
241
+ end
242
+ end
243
+
244
+ class FilePositionEntry
245
+ START_SIZE = 8
246
+ NUM_OFFSET = 9
247
+ NUM_SIZE = 8
248
+ LN_OFFSET = 17
249
+ SIZE = 18
250
+
251
+ def initialize(file, seek)
252
+ @file = file
253
+ @seek = seek
254
+ end
255
+
256
+ def update(start, num)
257
+ @file.pos = @seek
258
+ @file.write "%08x\t%08x" % [start, num]
259
+ end
260
+
261
+ def read_start
262
+ @file.pos = @seek
263
+ raw = @file.read(START_SIZE)
264
+ raw ? raw.to_i(16) : 0
265
+ end
266
+
267
+ def read_num
268
+ @file.pos = @seek + NUM_OFFSET
269
+ raw = @file.read(NUM_SIZE)
270
+ raw ? raw.to_i(16) : 0
271
+ end
272
+ end
273
+
274
+ class MemoryPositionEntry
275
+ def initialize
276
+ @start = 0
277
+ @num = 0
278
+ end
279
+
280
+ def update(start, num)
281
+ @start = start
282
+ @num = num
283
+ end
284
+
285
+ def read_start
286
+ @start
287
+ end
288
+
289
+ def read_num
290
+ @num
291
+ end
292
+ end
293
+
294
+ end
295
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/plugin/in_winevtlog'
26
+
27
+ class Test::Unit::TestCase
28
+ end
@@ -0,0 +1,56 @@
1
+ require 'helper'
2
+
3
+ class WinEvtLogTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ ]
10
+ # CONFIG = %[
11
+ # path #{TMP_DIR}/out_file_test
12
+ # compress gz
13
+ # utc
14
+ # ]
15
+
16
+ def create_driver(conf = CONFIG, tag='test')
17
+ Fluent::Test::InputTestDriver.new(Fluent::WinEvtLog).configure(conf)
18
+ end
19
+
20
+ def test_configure
21
+ #### set configurations
22
+ # d = create_driver %[
23
+ # path test_path
24
+ # compress gz
25
+ # ]
26
+ #### check configurations
27
+ # assert_equal 'test_path', d.instance.path
28
+ # assert_equal :gz, d.instance.compress
29
+ end
30
+
31
+ def test_format
32
+ d = create_driver
33
+
34
+ # time = Time.parse("2011-01-02 13:14:15 UTC").to_i
35
+ # d.emit({"a"=>1}, time)
36
+ # d.emit({"a"=>2}, time)
37
+
38
+ # d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n]
39
+ # d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]
40
+
41
+ # d.run
42
+ end
43
+
44
+ def test_write
45
+ d = create_driver
46
+
47
+ # time = Time.parse("2011-01-02 13:14:15 UTC").to_i
48
+ # d.emit({"a"=>1}, time)
49
+ # d.emit({"a"=>2}, time)
50
+
51
+ # ### FileOutput#write returns path
52
+ # path = d.run
53
+ # expect_path = "#{TMP_DIR}/out_file_test._0.log.gz"
54
+ # assert_equal expect_path, path
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-winevtlog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - okahashi117
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fluentd
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: win32-eventlog
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Input plugin to read windwos event log.
70
+ email:
71
+ - naruki_okahashi@jbat.co.jp
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - fluent-plugin-winevtlog.gemspec
82
+ - lib/fluent/plugin/in_winevtlog.rb
83
+ - test/helper.rb
84
+ - test/plugin/test_in_winevtlog.rb
85
+ homepage: ''
86
+ licenses:
87
+ - Apache license
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.0.14
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Input plugin to read windows event log.
109
+ test_files:
110
+ - test/helper.rb
111
+ - test/plugin/test_in_winevtlog.rb