fluent-plugin-winevtlog 0.0.2

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