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

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 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