fluent-plugin-elb-access-log 0.3.7.pre.beta1 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4c4c5dcff53f3ac390c49257c2dccc74e827a06
4
- data.tar.gz: e62f5e6f828379ef785829e4ec5fef39b2bb451c
3
+ metadata.gz: c576eb75af304916ce519a79377d8fe14204a8fd
4
+ data.tar.gz: 659724f105a175477d751d33924a4eb862c9af8e
5
5
  SHA512:
6
- metadata.gz: c197cd9d6a3c4b25a463b433dfad6ee3b814d90969290a6f1cfb4ee4af784294e18cc596287c966102e43448b97273bc539aeb770e500e44c4d5f2d31d56ff65
7
- data.tar.gz: 0c69eeca3fffbd7f58629f9fdbb58d1a4596556c8d8e919e7fbe39d0d2bc2671531f7a4cf39ede26260bbc56cc48f09555f553579c5e3f2c8e29aea66d71239b
6
+ metadata.gz: 9b4f3229cc5bcded62629cf3e818f013edd7e7d6faab21c1bbb46f1b029bfc6c45bcabb7ebec72e5c0f544417a662ee0b19ebe85a7f3df3bd3b62430ef64942a
7
+ data.tar.gz: 157d4fa797fc74bb6c78e2ba09c36279fc44b743a342cbeba17e8cdd7161ef2f3e3d4ad28a54ea8f343629f33b8c6a2de1f2f7ab3a030d5f11031818f0165cfb
data/README.md CHANGED
@@ -4,6 +4,7 @@ Fluentd input plugin for [AWS ELB Access Logs](http://docs.aws.amazon.com/Elasti
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/fluent-plugin-elb-access-log.svg)](http://badge.fury.io/rb/fluent-plugin-elb-access-log)
6
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
+ [![Coverage Status](https://coveralls.io/repos/github/winebarrel/fluent-plugin-elb-access-log/badge.svg?branch=master)](https://coveralls.io/github/winebarrel/fluent-plugin-elb-access-log?branch=master)
7
8
 
8
9
  ## Installation
9
10
 
@@ -46,10 +47,15 @@ Or install it yourself as:
46
47
  #history_length 100
47
48
  #sampling_interval 1
48
49
  #debug false
50
+ #elb_type clb # or alb
49
51
  </source>
50
52
  ```
51
53
 
52
- ```javascript
54
+ ## Outout
55
+
56
+ ### CLB
57
+
58
+ ```json
53
59
  // elb.access_log:
54
60
  {
55
61
  "timestamp":"2015-05-24T08:25:36.229576Z",
@@ -82,9 +88,41 @@ Or install it yourself as:
82
88
  }
83
89
  ```
84
90
 
85
- # Difference with [fluent-plugin-elb-log](https://github.com/shinsaka/fluent-plugin-elb-log)
91
+ ### ALB
86
92
 
87
- * Use AWS SDK for Ruby V2.
88
- * It is possible to change the record tag.
89
- * List objects with prefix.
90
- * Perse request line URI.
93
+ ```json
94
+ {
95
+ "type": "https",
96
+ "timestamp": "2015-05-24T19:55:36.000000Z",
97
+ "elb": "hoge",
98
+ "client_port": 57673,
99
+ "target_port": 80,
100
+ "request_processing_time": 5.3e-05,
101
+ "target_processing_time": 0.000913,
102
+ "response_processing_time": 3.6e-05,
103
+ "elb_status_code": 200,
104
+ "target_status_code": 200,
105
+ "received_bytes": 0,
106
+ "sent_bytes": 3,
107
+ "request": "GET http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1",
108
+ "user_agent": "curl/7.30.0",
109
+ "ssl_cipher": "ssl_cipher",
110
+ "ssl_protocol": "ssl_protocol",
111
+ "target_group_arn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/app/xxx",
112
+ "trace_id": "Root=xxx",
113
+ "domain_name": "-",
114
+ "chosen_cert_arn": "arn:aws:acm:ap-northeast-1:123456789012:certificate/xxx",
115
+ "client": "14.14.124.20",
116
+ "target": "10.0.199.184",
117
+ "request.method": "GET",
118
+ "request.uri": "http://hoge-1876938939.ap-northeast-1.elb.amazonaws.com:80/",
119
+ "request.http_version": "HTTP/1.1",
120
+ "request.uri.scheme": "http",
121
+ "request.uri.user": null,
122
+ "request.uri.host": "hoge-1876938939.ap-northeast-1.elb.amazonaws.com",
123
+ "request.uri.port": 80,
124
+ "request.uri.path": "/",
125
+ "request.uri.query": null,
126
+ "request.uri.fragment": null
127
+ }
128
+ ```
@@ -19,11 +19,13 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'fluentd'
22
- spec.add_dependency 'aws-sdk', '~> 2.1.2'
22
+ spec.add_dependency 'aws-sdk-s3', '~> 1.8'
23
23
  spec.add_dependency 'addressable'
24
24
  spec.add_development_dependency 'bundler'
25
25
  spec.add_development_dependency 'rake'
26
26
  spec.add_development_dependency 'rspec', '>= 3.0.0'
27
27
  spec.add_development_dependency 'timecop'
28
28
  spec.add_development_dependency 'test-unit', '>= 3.1.0'
29
+ spec.add_development_dependency 'rspec-match_table', '>= 0.1.1'
30
+ spec.add_development_dependency 'coveralls'
29
31
  end
@@ -6,25 +6,52 @@ class Fluent::ElbAccessLogInput < Fluent::Input
6
6
 
7
7
  USER_AGENT_SUFFIX = "fluent-plugin-elb-access-log/#{FluentPluginElbAccessLog::VERSION}"
8
8
 
9
- # http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format
10
9
  ACCESS_LOG_FIELDS = {
11
- 'timestamp' => nil,
12
- 'elb' => nil,
13
- 'client_port' => nil,
14
- 'backend_port' => nil,
15
- 'request_processing_time' => :to_f,
16
- 'backend_processing_time' => :to_f,
17
- 'response_processing_time' => :to_f,
18
- 'elb_status_code' => :to_i,
19
- 'backend_status_code' => :to_i,
20
- 'received_bytes' => :to_i,
21
- 'sent_bytes' => :to_i,
22
- 'request' => nil,
23
- 'user_agent' => nil,
24
- 'ssl_cipher' => nil,
25
- 'ssl_protocol' => nil,
10
+ # http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/access-log-collection.html
11
+ 'clb' => {
12
+ 'timestamp' => nil,
13
+ 'elb' => nil,
14
+ 'client_port' => nil,
15
+ 'backend_port' => nil,
16
+ 'request_processing_time' => :to_f,
17
+ 'backend_processing_time' => :to_f,
18
+ 'response_processing_time' => :to_f,
19
+ 'elb_status_code' => :to_i,
20
+ 'backend_status_code' => :to_i,
21
+ 'received_bytes' => :to_i,
22
+ 'sent_bytes' => :to_i,
23
+ 'request' => nil,
24
+ 'user_agent' => nil,
25
+ 'ssl_cipher' => nil,
26
+ 'ssl_protocol' => nil,
27
+ },
28
+ # http://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html
29
+ 'alb' => {
30
+ 'type' => nil,
31
+ 'timestamp' => nil,
32
+ 'elb' => nil,
33
+ 'client_port' => nil,
34
+ 'target_port' => nil,
35
+ 'request_processing_time' => :to_f,
36
+ 'target_processing_time' => :to_f,
37
+ 'response_processing_time' => :to_f,
38
+ 'elb_status_code' => :to_i,
39
+ 'target_status_code' => :to_i,
40
+ 'received_bytes' => :to_i,
41
+ 'sent_bytes' => :to_i,
42
+ 'request' => nil,
43
+ 'user_agent' => nil,
44
+ 'ssl_cipher' => nil,
45
+ 'ssl_protocol' => nil,
46
+ 'target_group_arn' => nil,
47
+ 'trace_id' => nil,
48
+ 'domain_name' => nil,
49
+ 'chosen_cert_arn' => nil,
50
+ },
26
51
  }
27
52
 
53
+ ELB_TYPES = %(clb alb)
54
+
28
55
  unless method_defined?(:log)
29
56
  define_method('log') { $log }
30
57
  end
@@ -33,24 +60,25 @@ class Fluent::ElbAccessLogInput < Fluent::Input
33
60
  define_method('router') { Fluent::Engine }
34
61
  end
35
62
 
36
- config_param :aws_key_id, :string, :default => nil, :secret => true
37
- config_param :aws_sec_key, :string, :default => nil, :secret => true
38
- config_param :profile, :string, :default => nil
39
- config_param :credentials_path, :string, :default => nil
40
- config_param :http_proxy, :string, :default => nil
63
+ config_param :elb_type, :string, default: 'clb'
64
+ config_param :aws_key_id, :string, default: nil, secret: true
65
+ config_param :aws_sec_key, :string, default: nil, secret: true
66
+ config_param :profile, :string, default: nil
67
+ config_param :credentials_path, :string, default: nil
68
+ config_param :http_proxy, :string, default: nil
41
69
  config_param :account_id, :string
42
70
  config_param :region, :string
43
71
  config_param :s3_bucket, :string
44
- config_param :s3_prefix, :string, :default => nil
45
- config_param :tag, :string, :default => 'elb.access_log'
46
- config_param :tsfile_path, :string, :default => '/var/tmp/fluent-plugin-elb-access-log.ts'
47
- config_param :histfile_path, :string, :default => '/var/tmp/fluent-plugin-elb-access-log.history'
48
- config_param :interval, :time, :default => 300
49
- config_param :start_datetime, :string, :default => nil
50
- config_param :buffer_sec, :time, :default => 600
51
- config_param :history_length, :integer, :default => 100
52
- config_param :sampling_interval, :integer, :default => 1
53
- config_param :debug, :bool, :default => false
72
+ config_param :s3_prefix, :string, default: nil
73
+ config_param :tag, :string, default: 'elb.access_log'
74
+ config_param :tsfile_path, :string, default: '/var/tmp/fluent-plugin-elb-access-log.ts'
75
+ config_param :histfile_path, :string, default: '/var/tmp/fluent-plugin-elb-access-log.history'
76
+ config_param :interval, :time, default: 300
77
+ config_param :start_datetime, :string, default: nil
78
+ config_param :buffer_sec, :time, default: 600
79
+ config_param :history_length, :integer, default: 100
80
+ config_param :sampling_interval, :integer, default: 1
81
+ config_param :debug, :bool, default: false
54
82
 
55
83
  def initialize
56
84
  super
@@ -59,12 +87,17 @@ class Fluent::ElbAccessLogInput < Fluent::Input
59
87
  require 'logger'
60
88
  require 'time'
61
89
  require 'addressable/uri'
62
- require 'aws-sdk'
90
+ require 'aws-sdk-s3'
91
+ require 'zlib'
63
92
  end
64
93
 
65
94
  def configure(conf)
66
95
  super
67
96
 
97
+ unless ELB_TYPES.include?(@elb_type)
98
+ raise raise Fluent::ConfigError, "Invalid ELB type: #{@elb_type}"
99
+ end
100
+
68
101
  FileUtils.touch(@tsfile_path)
69
102
  FileUtils.touch(@histfile_path)
70
103
  tsfile_start_datetime = parse_tsfile
@@ -113,6 +146,7 @@ class Fluent::ElbAccessLogInput < Fluent::Input
113
146
  def shutdown
114
147
  @loop.stop
115
148
  @thread.join
149
+ super
116
150
  end
117
151
 
118
152
  private
@@ -128,17 +162,28 @@ class Fluent::ElbAccessLogInput < Fluent::Input
128
162
  last_timestamp = timestamp
129
163
 
130
164
  prefixes(timestamp).each do |prefix|
131
- client.list_objects(:bucket => @s3_bucket, :prefix => prefix).each do |page|
165
+ client.list_objects(bucket: @s3_bucket, prefix: prefix).each do |page|
132
166
  page.contents.each do |obj|
133
167
  account_id, logfile_const, region, elb_name, logfile_datetime, ip, logfile_suffix = obj.key.split('_', 7)
134
168
  logfile_datetime = Time.parse(logfile_datetime)
135
169
 
136
- if logfile_suffix !~ /\.log\z/ or logfile_datetime <= (timestamp - @buffer_sec)
170
+ if logfile_suffix !~ /\.log(\.gz)?\z/ or logfile_datetime <= (timestamp - @buffer_sec)
137
171
  next
138
172
  end
139
173
 
140
174
  unless @history.include?(obj.key)
141
175
  access_log = client.get_object(bucket: @s3_bucket, key: obj.key).body.string
176
+
177
+ if obj.key.end_with?('.gz')
178
+ begin
179
+ inflated = Zlib::Inflate.inflate(access_log)
180
+ access_log = inflated
181
+ rescue Zlib::Error => e
182
+ @log.warn("#{e.message}: #{access_log.inspect.slice(0, 64)}")
183
+ next
184
+ end
185
+ end
186
+
142
187
  emit_access_log(access_log)
143
188
  last_timestamp = logfile_datetime
144
189
  @history.push(obj.key)
@@ -164,40 +209,71 @@ class Fluent::ElbAccessLogInput < Fluent::Input
164
209
  access_log = sampling(access_log)
165
210
  end
166
211
 
212
+ records = parse_log(access_log)
213
+
214
+ records.each do |record|
215
+ time = Time.parse(record['timestamp'])
216
+ router.emit(@tag, time.to_i, record)
217
+ end
218
+ end
219
+
220
+ def parse_log(access_log)
167
221
  parsed_access_log = []
168
222
 
169
223
  access_log.split("\n").each do |line|
170
- line = parse_line(line)
224
+ case @elb_type
225
+ when 'clb'
226
+ line = parse_clb_line(line)
227
+ when 'alb'
228
+ line = parse_alb_line(line)
229
+ else
230
+ # It is a bug if an exception is thrown
231
+ raise 'must not happen'
232
+ end
233
+
171
234
  parsed_access_log << line if line
172
235
  end
173
236
 
237
+ records = []
238
+ access_log_fields = ACCESS_LOG_FIELDS.fetch(@elb_type)
239
+
174
240
  parsed_access_log.each do |row|
175
- record = Hash[ACCESS_LOG_FIELDS.keys.zip(row)]
241
+ begin
242
+ record = Hash[access_log_fields.keys.zip(row)]
176
243
 
177
- ACCESS_LOG_FIELDS.each do |name, conv|
178
- record[name] = record[name].send(conv) if conv
179
- end
244
+ access_log_fields.each do |name, conv|
245
+ record[name] = record[name].send(conv) if conv
246
+ end
180
247
 
181
- split_address_port!(record, 'client')
182
- split_address_port!(record, 'backend')
248
+ split_address_port!(record, 'client')
183
249
 
184
- parse_request!(record)
250
+ case @elb_type
251
+ when 'clb'
252
+ split_address_port!(record, 'backend')
253
+ when 'alb'
254
+ split_address_port!(record, 'target')
255
+ else
256
+ # It is a bug if an exception is thrown
257
+ raise 'must not happen'
258
+ end
185
259
 
186
- begin
187
- time = Time.strptime(record['timestamp'].to_s, '%FT%T.%L %z')
188
- router.emit(@tag, time.to_i, record)
260
+ parse_request!(record)
261
+
262
+ records << record
189
263
  rescue ArgumentError => e
190
264
  @log.warn("#{e.message}: #{row}")
191
265
  @log.warn('A record that has bad timestamp is not emitted.')
192
266
  end
193
267
  end
268
+
269
+ records
194
270
  end
195
271
 
196
- def parse_line(line)
272
+ def parse_clb_line(line)
197
273
  parsed = nil
198
274
 
199
275
  begin
200
- parsed = CSV.parse_line(line, :col_sep => ' ')
276
+ parsed = CSV.parse_line(line, col_sep: ' ')
201
277
  rescue => e
202
278
  begin
203
279
  parsed = line.split(' ', 12)
@@ -207,9 +283,9 @@ class Fluent::ElbAccessLogInput < Fluent::Input
207
283
  parsed[11].sub!(/\A"/, '')
208
284
  parsed[11].sub!(/"(.*)\z/, '')
209
285
 
210
- user_agent, ssl_cipher, ssl_protocol = $1.strip.split(' ', 3)
211
- user_agent.sub!(/\A"/, '').sub!(/"\z/, '') if user_agent
212
- parsed[12] = user_agent
286
+ user_agent, ssl_cipher, ssl_protocol = rsplit($1.strip, ' ', 3)
287
+
288
+ parsed[12] = unquote(user_agent)
213
289
  parsed[13] = ssl_cipher
214
290
  parsed[14] = ssl_protocol
215
291
  rescue => e2
@@ -220,6 +296,37 @@ class Fluent::ElbAccessLogInput < Fluent::Input
220
296
  parsed
221
297
  end
222
298
 
299
+ def parse_alb_line(line)
300
+ parsed = nil
301
+
302
+ begin
303
+ parsed = CSV.parse_line(line, col_sep: ' ')
304
+ rescue => e
305
+ begin
306
+ parsed = line.split(' ', 13)
307
+
308
+ # request
309
+ parsed[12] ||= ''
310
+ parsed[12].sub!(/\A"/, '')
311
+ parsed[12].sub!(/"(.*)\z/, '')
312
+
313
+ user_agent, ssl_cipher, ssl_protocol, target_group_arn, trace_id, domain_name, chosen_cert_arn = rsplit($1.strip, ' ', 7)
314
+
315
+ parsed[13] = unquote(user_agent)
316
+ parsed[14] = ssl_cipher
317
+ parsed[15] = ssl_protocol
318
+ parsed[16] = target_group_arn
319
+ parsed[17] = unquote(trace_id)
320
+ parsed[18] = unquote(domain_name)
321
+ parsed[19] = unquote(chosen_cert_arn)
322
+ rescue => e2
323
+ @log.warn("#{e.message}: #{line}")
324
+ end
325
+ end
326
+
327
+ parsed
328
+ end
329
+
223
330
  def sampling(access_log)
224
331
  access_log.split("\n").each_with_index.select {|row, i| (i % @sampling_interval).zero? }.map {|row, i| row }.join("\n")
225
332
  end
@@ -279,7 +386,7 @@ class Fluent::ElbAccessLogInput < Fluent::Input
279
386
  def client
280
387
  return @client if @client
281
388
 
282
- options = {:user_agent_suffix => USER_AGENT_SUFFIX}
389
+ options = {user_agent_suffix: USER_AGENT_SUFFIX}
283
390
  options[:region] = @region if @region
284
391
  options[:http_proxy] = @http_proxy if @http_proxy
285
392
 
@@ -287,7 +394,7 @@ class Fluent::ElbAccessLogInput < Fluent::Input
287
394
  options[:access_key_id] = @aws_key_id
288
395
  options[:secret_access_key] = @aws_sec_key
289
396
  elsif @profile
290
- credentials_opts = {:profile_name => @profile}
397
+ credentials_opts = {profile_name: @profile}
291
398
  credentials_opts[:path] = @credentials_path if @credentials_path
292
399
  credentials = Aws::SharedCredentials.new(credentials_opts)
293
400
  options[:credentials] = credentials
@@ -302,6 +409,26 @@ class Fluent::ElbAccessLogInput < Fluent::Input
302
409
  @client = Aws::S3::Client.new(options)
303
410
  end
304
411
 
412
+ def rsplit(str, sep, n)
413
+ str = str.dup
414
+ substrs = []
415
+
416
+ (n - 1).times do
417
+ pos = str.rindex(sep)
418
+ next unless pos
419
+ substr = str.slice!(pos..-1).slice(sep.length..-1)
420
+ substrs << substr
421
+ end
422
+
423
+ substrs << str
424
+ substrs.reverse
425
+ end
426
+
427
+ def unquote(str)
428
+ return nil if (str || '').empty?
429
+ str.sub(/\A"/, '').sub(/"\z/, '')
430
+ end
431
+
305
432
  class TimerWatcher < Coolio::TimerWatcher
306
433
  def initialize(interval, repeat, log, &callback)
307
434
  @callback = callback
@@ -1,3 +1,3 @@
1
1
  module FluentPluginElbAccessLog
2
- VERSION = '0.3.7-beta1'
2
+ VERSION = '0.3.7'
3
3
  end