fluent-plugin-elb-access-log 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6df223556ac6d0e817804ee03aa63403bc3bd33f
4
+ data.tar.gz: e25d57033fe58bde5b24d13bfff67b7ae848e684
5
+ SHA512:
6
+ metadata.gz: 0b2b2c56a9ae17ec2452c36f32a1297809b0e51cedbc901757e0af089ae13b78e5f767f18ec68b77f330a803822a379276dfcd8089e540ab3faab3333c87aab4
7
+ data.tar.gz: 1f98ee986f5de353aeceb3e8e1baf196aa1322c17887f5f42df69a96aa9e4b68cb357f47524aa9f44b25078e5b160bfd0b2988b4f07cafeb5846b8198956bfe0
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --colour
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script:
5
+ - bundle install
6
+ - bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-elb-access-log.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Genki Sugawara
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.
@@ -0,0 +1,75 @@
1
+ # fluent-plugin-elb-access-log
2
+
3
+ Fluentd input plugin for [AWS ELB Access Logs](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html).
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/fluent-plugin-elb-access-log.png)](http://badge.fury.io/rb/fluent-plugin-elb-access-log)
6
+ [![Build Status](https://travis-ci.org/winebarrel/fluent-plugin-elb-access-log.svg?branch=master)](https://travis-ci.org/winebarrel/fluent-plugin-elb-access-log)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'fluent-plugin-elb-access-log'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install fluent-plugin-elb-access-log
23
+
24
+ ## Configuration
25
+
26
+ ```apache
27
+ <source>
28
+ type elb_access_log
29
+ #aws_key_id YOUR_ACCESS_KEY_ID
30
+ #aws_sec_key YOUR_SECRET_ACCESS_KEY
31
+ #profile PROFILE_NAME
32
+ #credentials_path path/to/credentials_file
33
+
34
+ account_id 123456789012 # required
35
+ s3_bucket BUCKET_NAME # required
36
+ s3_region us-west-1 # required
37
+ #s3_prefix PREFIX
38
+
39
+ #tag elb.access_log
40
+ #tsfile_path /var/tmp/fluent-plugin-elb-access-log.ts
41
+ #interval 300
42
+ #start_datetime 2015/05/24 17:00
43
+ #debug false
44
+ </source>
45
+ ```
46
+
47
+ ```javascript
48
+ // elb.access_log:
49
+ {
50
+ "timestamp":"2015-05-24T08:25:36.229576Z",
51
+ "elb":"hoge",
52
+ "client_port":"14.14.124.20:52232",
53
+ "backend_port":"10.0.199.184:80",
54
+ "request_processing_time":5.5e-05,
55
+ "backend_processing_time":0.000893,
56
+ "response_processing_time":5.7e-05,
57
+ "elb_status_code":200,
58
+ "backend_status_code":200,
59
+ "received_bytes":0,
60
+ "sent_bytes":3,
61
+ "request":"GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
62
+ "request.method":"GET",
63
+ "request.uri":"http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
64
+ "request.http_version":"HTTP/1.1",
65
+ "request.uri.scheme":"http",
66
+ "request.uri.userinfo":null,
67
+ "request.uri.host":"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
68
+ "request.uri.port":80,
69
+ "request.uri.registry":null,
70
+ "request.uri.path":"/",
71
+ "request.uri.opaque":null,
72
+ "request.uri.query":null,
73
+ "request.uri.fragment":null
74
+ }
75
+ ```
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
@@ -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
+ require 'fluent_plugin_elb_access_log/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'fluent-plugin-elb-access-log'
8
+ spec.version = FluentPluginElbAccessLog::VERSION
9
+ spec.authors = ['Genki Sugawara']
10
+ spec.email = ['sugawara@cookpad.com']
11
+ spec.summary = %q{Fluentd input plugin for AWS ELB Access Logs.}
12
+ spec.description = %q{Fluentd input plugin for AWS ELB Access Logs.}
13
+ spec.homepage = 'https://github.com/winebarrel/fluent-plugin-elb-access-log'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'fluentd'
22
+ spec.add_dependency 'aws-sdk', '~> 2'
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
26
+ spec.add_development_dependency 'timecop'
27
+ end
@@ -0,0 +1,213 @@
1
+ require 'fluent_plugin_elb_access_log/version'
2
+
3
+ class Fluent::ElbAccessLogInput < Fluent::Input
4
+ Fluent::Plugin.register_input('elb_access_log', self)
5
+
6
+ USER_AGENT_SUFFIX = "fluent-plugin-elb-access-log/#{FluentPluginElbAccessLog::VERSION}"
7
+
8
+ # http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format
9
+ ACCESS_LOG_FIELDS = {
10
+ 'timestamp' => nil,
11
+ 'elb' => nil,
12
+ 'client_port' => nil,
13
+ 'backend_port' => nil,
14
+ 'request_processing_time' => :to_f,
15
+ 'backend_processing_time' => :to_f,
16
+ 'response_processing_time' => :to_f,
17
+ 'elb_status_code' => :to_i,
18
+ 'backend_status_code' => :to_i,
19
+ 'received_bytes' => :to_i,
20
+ 'sent_bytes' => :to_i,
21
+ 'request' => nil,
22
+ }
23
+
24
+ unless method_defined?(:log)
25
+ define_method('log') { $log }
26
+ end
27
+
28
+ unless method_defined?(:router)
29
+ define_method('router') { Fluent::Engine }
30
+ end
31
+
32
+ config_param :aws_key_id, :string, :default => nil
33
+ config_param :aws_sec_key, :string, :default => nil
34
+ config_param :profile, :string, :default => nil
35
+ config_param :credentials_path, :string, :default => nil
36
+ config_param :account_id, :string
37
+ config_param :s3_bucket, :string
38
+ config_param :s3_region, :string
39
+ config_param :s3_prefix, :string, :default => nil
40
+ config_param :tag, :string, :default => 'elb.access_log'
41
+ config_param :tsfile_path, :string, :default => '/var/tmp/fluent-plugin-elb-access-log.ts'
42
+ config_param :interval, :time, :default => 300
43
+ config_param :start_datetime, :string, :default => nil
44
+ config_param :debug, :bool, :default => false
45
+
46
+ def initialize
47
+ super
48
+ require 'csv'
49
+ require 'fileutils'
50
+ require 'logger'
51
+ require 'time'
52
+ require 'uri'
53
+ require 'aws-sdk'
54
+ end
55
+
56
+ def configure(conf)
57
+ super
58
+
59
+ FileUtils.touch(@tsfile_path)
60
+
61
+ if @start_datetime
62
+ @start_datetime = Time.parse(@start_datetime).utc
63
+ else
64
+ @start_datetime = Time.parse(File.read(@tsfile_path)).utc rescue Time.now.utc
65
+ end
66
+ end
67
+
68
+ def start
69
+ super
70
+
71
+ # Load client
72
+ client
73
+
74
+ @loop = Coolio::Loop.new
75
+ timestamp = @start_datetime
76
+
77
+ timer = TimerWatcher.new(@interval, true, log) do
78
+ new_timestamp = fetch(timestamp)
79
+
80
+ if timestamp != new_timestamp
81
+ save_timestamp(new_timestamp)
82
+ timestamp = new_timestamp
83
+ end
84
+ end
85
+
86
+ @loop.attach(timer)
87
+ @thread = Thread.new(&method(:run))
88
+ end
89
+
90
+ def shutdown
91
+ @loop.stop
92
+ @thread.join
93
+ end
94
+
95
+ private
96
+
97
+ def run
98
+ @loop.run
99
+ rescue => e
100
+ log.error(e.message)
101
+ log.error_backtrace(e.backtrace)
102
+ end
103
+
104
+ def fetch(timestamp)
105
+ last_timestamp = timestamp
106
+
107
+ prefixes(timestamp).each do |prefix|
108
+ client.list_objects(:bucket => @s3_bucket, :prefix => prefix).each do |page|
109
+ page.contents.each do |obj|
110
+ account_id, logfile_const, region, elb_name, logfile_datetime, ip, logfile_suffix = obj.key.split('_', 7)
111
+ logfile_datetime = Time.parse(logfile_datetime)
112
+
113
+ if logfile_suffix !~ /\.log\z/ or logfile_datetime <= timestamp
114
+ next
115
+ end
116
+
117
+ access_log = client.get_object(bucket: @s3_bucket, key: obj.key).first.body.string
118
+ emit_access_log(access_log)
119
+ last_timestamp = logfile_datetime
120
+ end
121
+ end
122
+ end
123
+
124
+ last_timestamp
125
+ end
126
+
127
+ def prefixes(timestamp)
128
+ base_prefix = "AWSLogs/#{@account_id}/elasticloadbalancing/#{@s3_region}/"
129
+ base_prefix = "#{@s3_prefix}/#{base_prefix}" if @s3_prefix
130
+
131
+ [timestamp - 86400, timestamp, timestamp + 86400].map do |date|
132
+ base_prefix + date.strftime('%Y/%m/%d/')
133
+ end
134
+ end
135
+
136
+ def emit_access_log(access_log)
137
+ access_log = CSV.parse(access_log, :col_sep => ' ')
138
+
139
+ access_log.each do |row|
140
+ record = Hash[ACCESS_LOG_FIELDS.keys.zip(row)]
141
+
142
+ ACCESS_LOG_FIELDS.each do |name, conv|
143
+ record[name] = record[name].send(conv) if conv
144
+ end
145
+
146
+ parse_request!(record)
147
+
148
+ time = Time.parse(record['timestamp'])
149
+ router.emit(@tag, time, record)
150
+ end
151
+ end
152
+
153
+ def parse_request!(record)
154
+ request = record['request']
155
+ method, uri, http_version = request.split(' ', 3)
156
+
157
+ record['request.method'] = method
158
+ record['request.uri'] = uri
159
+ record['request.http_version'] = http_version
160
+
161
+ uri = URI.parse(uri)
162
+
163
+ [:scheme ,:userinfo, :host, :port, :registry, :path, :opaque, :query, :fragment].each do |key|
164
+ record["request.uri.#{key}"] = uri.send(key)
165
+ end
166
+ end
167
+
168
+ def save_timestamp(timestamp)
169
+ open(@tsfile_path, 'w') do |tsfile|
170
+ tsfile << timestamp.to_s
171
+ end
172
+ end
173
+
174
+ def client
175
+ return @client if @client
176
+
177
+ options = {:user_agent_suffix => USER_AGENT_SUFFIX}
178
+ options[:region] = @region if @region
179
+
180
+ if @aws_key_id and @aws_sec_key
181
+ options[:access_key_id] = @aws_key_id
182
+ options[:secret_access_key] = @aws_sec_key
183
+ elsif @profile
184
+ credentials_opts = {:profile_name => @profile}
185
+ credentials_opts[:path] = @credentials_path if @credentials_path
186
+ credentials = Aws::SharedCredentials.new(credentials_opts)
187
+ options[:credentials] = credentials
188
+ end
189
+
190
+ if @debug
191
+ options[:logger] = Logger.new(log.out)
192
+ options[:log_level] = :debug
193
+ #options[:http_wire_trace] = true
194
+ end
195
+
196
+ @client = Aws::S3::Client.new(options)
197
+ end
198
+
199
+ class TimerWatcher < Coolio::TimerWatcher
200
+ def initialize(interval, repeat, log, &callback)
201
+ @callback = callback
202
+ @log = log
203
+ super(interval, repeat)
204
+ end
205
+
206
+ def on_timer
207
+ @callback.call
208
+ rescue => e
209
+ @log.error(e.message)
210
+ @log.error_backtrace(e.backtrace)
211
+ end
212
+ end # TimerWatcher
213
+ end # Fluent::ElbAccessLogInput
@@ -0,0 +1,3 @@
1
+ module FluentPluginElbAccessLog
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,279 @@
1
+ describe Fluent::ElbAccessLogInput do
2
+ let(:account_id) { '123456789012' }
3
+ let(:s3_bucket) { 'my-bucket' }
4
+ let(:s3_region) { 'us-west-1' }
5
+ let(:driver) { create_driver(fluentd_conf) }
6
+ let(:client){ Aws::S3::Client.new(stub_responses: true) }
7
+
8
+ let(:fluentd_conf) do
9
+ {
10
+ account_id: account_id,
11
+ s3_bucket: s3_bucket,
12
+ s3_region: s3_region,
13
+ start_datetime: (today - 1).to_s,
14
+ }
15
+ end
16
+
17
+ let(:today) { Time.parse('2015/05/24 18:30 UTC') }
18
+ let(:yesterday) { today - 86400 }
19
+ let(:tomorrow) { today + 86400 }
20
+
21
+ let(:today_prefix) { "AWSLogs/#{account_id}/elasticloadbalancing/#{s3_region}/#{today.strftime('%Y/%m/%d')}/" }
22
+ let(:yesterday_prefix) { "AWSLogs/#{account_id}/elasticloadbalancing/#{s3_region}/#{yesterday.strftime('%Y/%m/%d')}/" }
23
+ let(:tomorrow_prefix) { "AWSLogs/#{account_id}/elasticloadbalancing/#{s3_region}/#{tomorrow.strftime('%Y/%m/%d')}/" }
24
+
25
+ let(:today_object_key) { "#{today_prefix}#{account_id}_elasticloadbalancing_ap-northeast-1_hoge_#{today.iso8601}_52.68.51.1_8hSqR3o4.log" }
26
+ let(:yesterday_object_key) { "#{yesterday_prefix}#{account_id}_elasticloadbalancing_ap-northeast-1_hoge_#{yesterday.iso8601}_52.68.51.1_8hSqR3o4.log" }
27
+ let(:tomorrow_object_key) { "#{tomorrow_prefix}#{account_id}_elasticloadbalancing_ap-northeast-1_hoge_#{tomorrow.iso8601}_52.68.51.1_8hSqR3o4.log" }
28
+
29
+ before do
30
+ Timecop.freeze(today)
31
+ allow_any_instance_of(Fluent::ElbAccessLogInput).to receive(:client) { client }
32
+ end
33
+
34
+ after do
35
+ Timecop.return
36
+ end
37
+
38
+ subject { x = driver.emits; x }
39
+
40
+ context 'when access log does not exist' do
41
+ before do
42
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: yesterday_prefix) { [] }
43
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: today_prefix) { [] }
44
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: tomorrow_prefix) { [] }
45
+ expect(driver.instance).to_not receive(:save_timestamp).with(today)
46
+
47
+ driver.run
48
+ end
49
+
50
+ it { is_expected.to be_empty }
51
+ end
52
+
53
+ context 'when access log exists' do
54
+ let(:today_access_log) do
55
+ <<-EOS
56
+ 2015-05-24T19:55:36.000000Z hoge 14.14.124.20:57673 10.0.199.184:80 0.000053 0.000913 0.000036 200 200 0 3 "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1" "curl/7.30.0" - -
57
+ 2015-05-24T19:55:36.000000Z hoge 14.14.124.20:57673 10.0.199.184:80 0.000053 0.000913 0.000036 200 200 0 3 "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1" "curl/7.30.0" - -
58
+ EOS
59
+ end
60
+
61
+ let(:tomorrow_access_log) do
62
+ <<-EOS
63
+ 2015-05-25T19:55:36.000000Z hoge 14.14.124.20:57673 10.0.199.184:80 0.000053 0.000913 0.000036 200 200 0 3 "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1" "curl/7.30.0" - -
64
+ 2015-05-25T19:55:36.000000Z hoge 14.14.124.20:57673 10.0.199.184:80 0.000053 0.000913 0.000036 200 200 0 3 "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1" "curl/7.30.0" - -
65
+ EOS
66
+ end
67
+
68
+ before do
69
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: yesterday_prefix) do
70
+ [double('yesterday_objects', contents: [double('yesterday_object', key: yesterday_object_key)])]
71
+ end
72
+
73
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: today_prefix) do
74
+ [double('today_objects', contents: [double('today_object', key: today_object_key)])]
75
+ end
76
+
77
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: tomorrow_prefix) do
78
+ [double('tomorrow_objects', contents: [double('tomorrow_object', key: tomorrow_object_key)])]
79
+ end
80
+
81
+ expect(client).to receive(:get_object).with(bucket: s3_bucket, key: today_object_key) do
82
+ [double('today_s3_object', body: StringIO.new(today_access_log))]
83
+ end
84
+
85
+ expect(client).to receive(:get_object).with(bucket: s3_bucket, key: tomorrow_object_key) do
86
+ [double('tomorrow_s3_object', body: StringIO.new(tomorrow_access_log))]
87
+ end
88
+
89
+ expect(driver.instance).to receive(:save_timestamp).with(tomorrow)
90
+
91
+ driver.run
92
+ end
93
+
94
+ let(:expected_emits) do
95
+ [["elb.access_log",
96
+ Time.parse('2015-05-24 19:55:36 UTC'),
97
+ {"timestamp"=>"2015-05-24T19:55:36.000000Z",
98
+ "elb"=>"hoge",
99
+ "client_port"=>"14.14.124.20:57673",
100
+ "backend_port"=>"10.0.199.184:80",
101
+ "request_processing_time"=>5.3e-05,
102
+ "backend_processing_time"=>0.000913,
103
+ "response_processing_time"=>3.6e-05,
104
+ "elb_status_code"=>200,
105
+ "backend_status_code"=>200,
106
+ "received_bytes"=>0,
107
+ "sent_bytes"=>3,
108
+ "request"=>
109
+ "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
110
+ "request.method"=>"GET",
111
+ "request.uri"=>
112
+ "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
113
+ "request.http_version"=>"HTTP/1.1",
114
+ "request.uri.scheme"=>"http",
115
+ "request.uri.userinfo"=>nil,
116
+ "request.uri.host"=>"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
117
+ "request.uri.port"=>80,
118
+ "request.uri.registry"=>nil,
119
+ "request.uri.path"=>"/",
120
+ "request.uri.opaque"=>nil,
121
+ "request.uri.query"=>nil,
122
+ "request.uri.fragment"=>nil}],
123
+ ["elb.access_log",
124
+ Time.parse('2015-05-24 19:55:36 UTC'),
125
+ {"timestamp"=>"2015-05-24T19:55:36.000000Z",
126
+ "elb"=>"hoge",
127
+ "client_port"=>"14.14.124.20:57673",
128
+ "backend_port"=>"10.0.199.184:80",
129
+ "request_processing_time"=>5.3e-05,
130
+ "backend_processing_time"=>0.000913,
131
+ "response_processing_time"=>3.6e-05,
132
+ "elb_status_code"=>200,
133
+ "backend_status_code"=>200,
134
+ "received_bytes"=>0,
135
+ "sent_bytes"=>3,
136
+ "request"=>
137
+ "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
138
+ "request.method"=>"GET",
139
+ "request.uri"=>
140
+ "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
141
+ "request.http_version"=>"HTTP/1.1",
142
+ "request.uri.scheme"=>"http",
143
+ "request.uri.userinfo"=>nil,
144
+ "request.uri.host"=>"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
145
+ "request.uri.port"=>80,
146
+ "request.uri.registry"=>nil,
147
+ "request.uri.path"=>"/",
148
+ "request.uri.opaque"=>nil,
149
+ "request.uri.query"=>nil,
150
+ "request.uri.fragment"=>nil}],
151
+ ["elb.access_log",
152
+ Time.parse('2015-05-25 19:55:36 UTC'),
153
+ {"timestamp"=>"2015-05-25T19:55:36.000000Z",
154
+ "elb"=>"hoge",
155
+ "client_port"=>"14.14.124.20:57673",
156
+ "backend_port"=>"10.0.199.184:80",
157
+ "request_processing_time"=>5.3e-05,
158
+ "backend_processing_time"=>0.000913,
159
+ "response_processing_time"=>3.6e-05,
160
+ "elb_status_code"=>200,
161
+ "backend_status_code"=>200,
162
+ "received_bytes"=>0,
163
+ "sent_bytes"=>3,
164
+ "request"=>
165
+ "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
166
+ "request.method"=>"GET",
167
+ "request.uri"=>
168
+ "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
169
+ "request.http_version"=>"HTTP/1.1",
170
+ "request.uri.scheme"=>"http",
171
+ "request.uri.userinfo"=>nil,
172
+ "request.uri.host"=>"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
173
+ "request.uri.port"=>80,
174
+ "request.uri.registry"=>nil,
175
+ "request.uri.path"=>"/",
176
+ "request.uri.opaque"=>nil,
177
+ "request.uri.query"=>nil,
178
+ "request.uri.fragment"=>nil}],
179
+ ["elb.access_log",
180
+ Time.parse('2015-05-25 19:55:36 UTC'),
181
+ {"timestamp"=>"2015-05-25T19:55:36.000000Z",
182
+ "elb"=>"hoge",
183
+ "client_port"=>"14.14.124.20:57673",
184
+ "backend_port"=>"10.0.199.184:80",
185
+ "request_processing_time"=>5.3e-05,
186
+ "backend_processing_time"=>0.000913,
187
+ "response_processing_time"=>3.6e-05,
188
+ "elb_status_code"=>200,
189
+ "backend_status_code"=>200,
190
+ "received_bytes"=>0,
191
+ "sent_bytes"=>3,
192
+ "request"=>
193
+ "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
194
+ "request.method"=>"GET",
195
+ "request.uri"=>
196
+ "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
197
+ "request.http_version"=>"HTTP/1.1",
198
+ "request.uri.scheme"=>"http",
199
+ "request.uri.userinfo"=>nil,
200
+ "request.uri.host"=>"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
201
+ "request.uri.port"=>80,
202
+ "request.uri.registry"=>nil,
203
+ "request.uri.path"=>"/",
204
+ "request.uri.opaque"=>nil,
205
+ "request.uri.query"=>nil,
206
+ "request.uri.fragment"=>nil}]]
207
+ end
208
+
209
+ it { is_expected.to eq expected_emits }
210
+ end
211
+
212
+ context 'when access log exists (with tag option)' do
213
+ let(:today_access_log) do
214
+ <<-EOS
215
+ 2015-05-24T19:55:36.000000Z hoge 14.14.124.20:57673 10.0.199.184:80 0.000053 0.000913 0.000036 200 200 0 3 "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1" "curl/7.30.0" - -
216
+ EOS
217
+ end
218
+
219
+ let(:fluentd_conf) do
220
+ {
221
+ account_id: account_id,
222
+ s3_bucket: s3_bucket,
223
+ s3_region: s3_region,
224
+ start_datetime: (today - 1).to_s,
225
+ tag: 'any.tag'
226
+ }
227
+ end
228
+
229
+ before do
230
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: yesterday_prefix) { [] }
231
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: tomorrow_prefix) { [] }
232
+
233
+ expect(client).to receive(:list_objects).with(bucket: s3_bucket, prefix: today_prefix) do
234
+ [double('today_objects', contents: [double('today_object', key: today_object_key)])]
235
+ end
236
+
237
+ expect(client).to receive(:get_object).with(bucket: s3_bucket, key: today_object_key) do
238
+ [double('today_s3_object', body: StringIO.new(today_access_log))]
239
+ end
240
+
241
+ expect(driver.instance).to receive(:save_timestamp).with(today)
242
+
243
+ driver.run
244
+ end
245
+
246
+ let(:expected_emits) do
247
+ [["any.tag",
248
+ Time.parse('2015-05-24 19:55:36 UTC'),
249
+ {"timestamp"=>"2015-05-24T19:55:36.000000Z",
250
+ "elb"=>"hoge",
251
+ "client_port"=>"14.14.124.20:57673",
252
+ "backend_port"=>"10.0.199.184:80",
253
+ "request_processing_time"=>5.3e-05,
254
+ "backend_processing_time"=>0.000913,
255
+ "response_processing_time"=>3.6e-05,
256
+ "elb_status_code"=>200,
257
+ "backend_status_code"=>200,
258
+ "received_bytes"=>0,
259
+ "sent_bytes"=>3,
260
+ "request"=>
261
+ "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
262
+ "request.method"=>"GET",
263
+ "request.uri"=>
264
+ "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
265
+ "request.http_version"=>"HTTP/1.1",
266
+ "request.uri.scheme"=>"http",
267
+ "request.uri.userinfo"=>nil,
268
+ "request.uri.host"=>"hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
269
+ "request.uri.port"=>80,
270
+ "request.uri.registry"=>nil,
271
+ "request.uri.path"=>"/",
272
+ "request.uri.opaque"=>nil,
273
+ "request.uri.query"=>nil,
274
+ "request.uri.fragment"=>nil}]]
275
+ end
276
+
277
+ it { is_expected.to eq expected_emits }
278
+ end
279
+ end
@@ -0,0 +1,40 @@
1
+ require 'fluent/test'
2
+ require 'fluent/plugin/in_elb_access_log'
3
+
4
+ require 'aws-sdk'
5
+ require 'time'
6
+ require 'timecop'
7
+
8
+ # Disable Test::Unit
9
+ module Test::Unit::RunCount; def run(*); end; end
10
+
11
+ RSpec.configure do |config|
12
+ config.before(:all) do
13
+ Fluent::Test.setup
14
+ end
15
+ end
16
+
17
+ def create_driver(options = {})
18
+ options = {
19
+ interval: 0,
20
+ }.merge(options)
21
+
22
+ account_id = options.fetch(:account_id) || '123456789012'
23
+ s3_bucket = options.fetch(:s3_bucket) || 'my-bucket'
24
+ s3_region = options.fetch(:s3_region) || 'us-west-1'
25
+
26
+ additional_options = options.select {|k, v| v }.map {|key, value|
27
+ "#{key} #{value}"
28
+ }.join("\n")
29
+
30
+ fluentd_conf = <<-EOS
31
+ type elb_access_log
32
+ account_id #{account_id}
33
+ s3_bucket #{s3_bucket}
34
+ s3_region #{s3_region}
35
+ #{additional_options}
36
+ EOS
37
+
38
+ tag = options[:tag] || 'test.default'
39
+ Fluent::Test::OutputTestDriver.new(Fluent::ElbAccessLogInput, tag).configure(fluentd_conf)
40
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-elb-access-log
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Genki Sugawara
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
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: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Fluentd input plugin for AWS ELB Access Logs.
98
+ email:
99
+ - sugawara@cookpad.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .travis.yml
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - fluent-plugin-elb-access-log.gemspec
112
+ - lib/fluent/plugin/in_elb_access_log.rb
113
+ - lib/fluent_plugin_elb_access_log/version.rb
114
+ - spec/in_elb_access_log_spec.rb
115
+ - spec/spec_helper.rb
116
+ homepage: https://github.com/winebarrel/fluent-plugin-elb-access-log
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.0.14
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Fluentd input plugin for AWS ELB Access Logs.
140
+ test_files:
141
+ - spec/in_elb_access_log_spec.rb
142
+ - spec/spec_helper.rb