fluentd-tcp-capturer 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7ffa5c092d3847b323da8e1e6a06f07673266c6d
4
+ data.tar.gz: 6d98befca1fd50bd1ac59b8ed5aad4c508b30675
5
+ SHA512:
6
+ metadata.gz: 727f0e018fc9dd4ac3d251a9cb5fec1ccd4b97a9329ec8214cfbb08f9e2d458b625b025072c38a55018828506b96b3ed631f7c999c0b5d574c9111deda753caf
7
+ data.tar.gz: 0386bd0aafe7da24a6e34729acf5e3366f6962900ece230865b7c59c713333279396cda34802bc91fc212d88bd59d989b6173bff00b60b8e4f7ec727f4f4e57f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capturing-message-fluentd.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Fluentd TCP capturer
2
+
3
+ `fluentd-tcp-capturer` is a tool to inspect/dump/handle message to Fluentd TCP input, to:
4
+
5
+ - debug a message to fluentd from somewhere
6
+ - try other configuration on other fluentd node
7
+
8
+ without changing Fluentd configuration.
9
+
10
+ ## Installation
11
+
12
+ ```shell
13
+ $ gem install 'fluentd-tcp-capturer'
14
+ ```
15
+
16
+ Then command `fm-cap` becomes available.
17
+
18
+
19
+ ## Usage
20
+
21
+ ```shell
22
+ Usage: fm-cap [options]
23
+ -d, --device DEVICE Device name [default: eth0]
24
+ -p, --port PORT Fluentd port to capture [default: 24224]
25
+ --forward-host HOST If set, message will be forwarded to other Fluentd host
26
+ --forward-port PORT Fluentd port to forward message (used when --forward-host is set)
27
+ --debug Set loglevel DEBUG
28
+ ```
29
+
30
+ ### Dump mode
31
+
32
+ This mode captures tcp packet to Fluentd, dump it in the terminal.
33
+
34
+ ```shell
35
+ # TODO
36
+ $ sudo fm-cap
37
+ I, [2017-03-03T22:41:31.141436 #14088] INFO -- : Start capturing lo0/port=24224
38
+ 2017-03-03 13:41:34 +0000 | tag=test.20170303224134 msg={"name"=>"John", "age"=>15}
39
+ 2017-03-03 13:41:46 +0000 | tag=test.20170303224145 msg={"name"=>"Michel", "age"=>16}
40
+ ```
41
+
42
+ You can specify other network device, also port number of Fluentd.
43
+
44
+ ```shell
45
+ $ sudo fm-cap -d lo0
46
+ $ sudo fm-cap -p 4567
47
+ ```
48
+
49
+ ### Transfer mode
50
+
51
+ This mode captures tcp packet, transfer it to other Fluentd tcp input.
52
+
53
+ ```shell
54
+ $ sudo fm-cap --forward-host other-fluentd-node --forward-port 4567
55
+ I, [2017-03-03T22:46:31.878876 #14564] INFO -- : Start capturing lo0/port=24224
56
+ I, [2017-03-03T22:46:34.577661 #14564] INFO -- : Forwarded message to other-fluentd-node:4567
57
+ I, [2017-03-03T22:46:41.460288 #14564] INFO -- : Forwarded message to other-fluentd-node:4567
58
+ I, [2017-03-03T22:46:42.461110 #14564] INFO -- : Forwarded message to other-fluentd-node:4567
59
+ ```
60
+
61
+ ## TODO
62
+
63
+ - Support timezone in the dumpped message.
64
+ - Dump message over embed Fluend.
65
+ - Support other protocol, e.g. UDP
66
+ - Tests ...
67
+
68
+ ## Patch
69
+
70
+ Welcome
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/fm-cap ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ require "optparse"
3
+ require "capture"
4
+ require "logger"
5
+
6
+ options = {
7
+ device: "eth0",
8
+ port: 24224,
9
+ forward_host: nil,
10
+ forward_port: 0,
11
+ output_file_path: nil
12
+ }
13
+
14
+ logger = Logger.new(STDOUT)
15
+ logger.level = Logger::INFO
16
+
17
+
18
+ OptionParser.new do |opts|
19
+ opts.on("-d DEVICE", "--device DEVICE", "Device name [default: eth0]") do |v|
20
+ options[:device] = v
21
+ end
22
+ opts.on("-p PORT", "--port PORT", "Fluentd port to capture [default: 24224]") do |v|
23
+ options[:port] = v.to_i
24
+ end
25
+ opts.on("--forward-host HOST", "If set, message will be forwarded to other Fluentd host") do |v|
26
+ options[:forward_host] = v
27
+ end
28
+ opts.on("--forward-port PORT", "Fluentd port to forward message (used when --forward-host is set)") do |v|
29
+ options[:forward_port] = v.to_i
30
+ end
31
+ opts.on("--debug", "Set loglevel DEBUG") do |v|
32
+ logger.level = Logger::DEBUG
33
+ end
34
+ # TODO
35
+ # opts.on("-o VALUE", "--output VALUE", "Output file path to dump message from Fluentd (used when --forward-host is set)") do |v|
36
+ # options[:output_file_path] = v
37
+ # end
38
+ # TODO max_bytes, timeout
39
+ opts.parse!(ARGV)
40
+ end
41
+
42
+
43
+ exec = Capturing::Exec.new(device: options[:device], port: options[:port], logger: logger)
44
+ runner = if options[:forward_host].nil?
45
+ Capturing::PrintRunner.new(exec, writer: STDOUT)
46
+ else
47
+ # if options[:output_file_path].nil?
48
+ # STDERR.write "-o/--output must be set"
49
+ # end
50
+ Capturing::ForwardRunner.new(exec, host: options[:forward_host], port: options[:forward_port],
51
+ output: options[:output_file_path])
52
+ end
53
+
54
+
55
+ exec.logger.info "Start capturing #{options[:device]}/port=#{options[:port]}"
56
+ begin
57
+ runner.run!
58
+ rescue Interrupt
59
+ runner.destroy!
60
+ exit 0
61
+ rescue => e
62
+ exec.logger.error e
63
+ exit 1
64
+ end
@@ -0,0 +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 = "fluentd-tcp-capturer"
7
+ spec.version = "0.1.0"
8
+ spec.authors = ["takumakanari"]
9
+ spec.email = ["chemtrails.t@gmail.com"]
10
+
11
+ spec.summary = %q{Fluentd message capturer}
12
+ spec.description = %q{A tool to inspect/dump/handle message to Fluentd TCP input.}
13
+ spec.homepage = "https://github.com/takumakanari/fluentd-tcp-capturer"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "ruby-pcap"
23
+ spec.add_runtime_dependency "threadpool"
24
+ spec.add_runtime_dependency "fluentd", [">= 0.12", "< 2"]
25
+ spec.add_development_dependency "bundler", "~> 1.14"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
data/lib/capture.rb ADDED
@@ -0,0 +1,160 @@
1
+ require "pcap"
2
+ require "logger"
3
+
4
+ module Capturing
5
+
6
+ class Runner
7
+
8
+ def initialize(exec)
9
+ @exec = exec
10
+ @pcaplet = Pcap::Capture.open_live(exec.device, exec.max_bytes, true, exec.timeout)
11
+ @pcaplet.setfilter(Pcap::Filter.new("port #{exec.port} and tcp", @pcaplet))
12
+ end
13
+
14
+ def run!
15
+ @pcaplet.each_packet do |packet|
16
+ if packet.tcp? && packet.tcp_data_len > 0
17
+ @exec.logger.debug "Handle message packets: #{packet}"
18
+ begin
19
+ on_packet packet.tcp_data
20
+ rescue => e
21
+ @exec.logger.error e
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def on_packet(data)
28
+ raise NotImplementedError.new "on_packet(packet)"
29
+ end
30
+
31
+ def destroy!
32
+ @pcaplet.close
33
+ end
34
+ end
35
+
36
+ class PrintRunner < Runner
37
+ require "fluent/engine"
38
+ require "fluent/time"
39
+
40
+ def initialize(e, writer:)
41
+ super e
42
+ @writer = writer
43
+ @unpacker = Fluent::Engine.msgpack_factory.unpacker
44
+ @time_formatter = Fluent::TimeFormatter.new("%Y-%m-%d %H:%M:%S %z", false, nil) # TODO support format
45
+ end
46
+
47
+ def on_packet(data)
48
+ @unpacker.feed_each(data) do |msg|
49
+ tag, entries = msg
50
+ entries.each do |e|
51
+ @writer.write(format_message(tag, e))
52
+ end
53
+ end
54
+ end
55
+
56
+ def format_message(tag, entry)
57
+ time, record = entry
58
+ "#{@time_formatter.format(time)} | tag=#{tag} msg=#{record.inspect}\n"
59
+ end
60
+ end
61
+
62
+ class ForwardRunner < Runner
63
+ require "socket"
64
+ require "threadpool"
65
+
66
+ def initialize(e, host:, port:, output:)
67
+ super e
68
+ @host = host
69
+ @port = port
70
+ @output = output
71
+ @use_embed_fluentd = false # TODO support this mode
72
+
73
+ start_embed_fluentd if @use_embed_fluentd
74
+ @sock = new_socket_to_forward
75
+ @messaging_thread_pool = ThreadPool.new(4) # TODO configurable
76
+ end
77
+
78
+ def on_packet(data)
79
+ @messaging_thread_pool.process{ @sock.write data }
80
+ @exec.logger.info "Forwarded message to #{@host}:#{@port}"
81
+ end
82
+
83
+ private
84
+ def new_socket_to_forward
85
+ max_retry = 10
86
+ begin
87
+ TCPSocket.new(@host, @port)
88
+ rescue Errno::ECONNREFUSED
89
+ raise if max_retry == 0
90
+ sleep 1
91
+ max_retry -= 1
92
+ retry
93
+ end
94
+ end
95
+
96
+ def start_embed_fluentd
97
+ Thread.start {
98
+ Dir.mktmpdir do |dir|
99
+ conf = File.join(dir, "fluent.conf")
100
+ output_file = File.join(dir, 'output')
101
+ @exec.logger.info "Output message to '#{output_file}'" # TODO set file path
102
+ File.write(conf, <<-EOF)
103
+ <source>
104
+ @type forward
105
+ port #{@port}
106
+ </source>
107
+ <match **>
108
+ @type file
109
+ path #{@output}
110
+ buffer_type memory
111
+ append true
112
+ flush_interval 0s
113
+ </match>
114
+ EOF
115
+ FluentdEmbed.new(conf).boot
116
+ end
117
+ }
118
+ end
119
+ end
120
+
121
+ class Exec
122
+ attr_reader :device, :max_bytes, :timeout, :port
123
+
124
+ DEFAULT_LOGGER ||= Logger.new(STDOUT)
125
+
126
+ def initialize(device: "eth0", max_bytes: 1460, timeout: 1000, port: 24224, logger: nil)
127
+ @device = device
128
+ @max_bytes = max_bytes
129
+ @timeout = timeout
130
+ @port = port
131
+ @logger = logger
132
+ end
133
+
134
+ def logger
135
+ @logger.nil? ? DEFAULT_LOGGER : @logger
136
+ end
137
+ end
138
+
139
+ class FluentdEmbed
140
+ require "fluent/version"
141
+ require "fluent/supervisor"
142
+
143
+ def initialize(config_path)
144
+ @opts = {
145
+ config_path: config_path,
146
+ plugin_dirs: [],
147
+ log_level: 2,
148
+ libs: [],
149
+ suppress_repeated_stacktrace: true,
150
+ use_v1_config: true,
151
+ supervise: true,
152
+ standalone_worker: true
153
+ }
154
+ end
155
+
156
+ def boot
157
+ Fluent::Supervisor.new(@opts).run_worker
158
+ end
159
+ end
160
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluentd-tcp-capturer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - takumakanari
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-pcap
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
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: threadpool
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
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.12'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '2'
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0.12'
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '2'
61
+ - !ruby/object:Gem::Dependency
62
+ name: bundler
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.14'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.14'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '10.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '10.0'
89
+ description: A tool to inspect/dump/handle message to Fluentd TCP input.
90
+ email:
91
+ - chemtrails.t@gmail.com
92
+ executables:
93
+ - fm-cap
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".gitignore"
98
+ - Gemfile
99
+ - README.md
100
+ - Rakefile
101
+ - bin/fm-cap
102
+ - fluentd-tcp-capturer.gemspec
103
+ - lib/capture.rb
104
+ homepage: https://github.com/takumakanari/fluentd-tcp-capturer
105
+ licenses: []
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.5.1
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Fluentd message capturer
127
+ test_files: []