fluent-plugin-elb-access-log 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.
@@ -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