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 +4 -4
- data/README.md +44 -6
- data/fluent-plugin-elb-access-log.gemspec +3 -1
- data/lib/fluent/plugin/in_elb_access_log.rb +179 -52
- data/lib/fluent_plugin_elb_access_log/version.rb +1 -1
- data/spec/in_elb_access_log_alb_spec.rb +763 -0
- data/spec/{in_elb_access_log_spec.rb → in_elb_access_log_clb_spec.rb} +21 -10
- data/spec/in_elb_access_log_config_spec.rb +24 -0
- data/spec/spec_helper.rb +12 -1
- metadata +40 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c576eb75af304916ce519a79377d8fe14204a8fd
|
4
|
+
data.tar.gz: 659724f105a175477d751d33924a4eb862c9af8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](http://badge.fury.io/rb/fluent-plugin-elb-access-log)
|
6
6
|
[](https://travis-ci.org/winebarrel/fluent-plugin-elb-access-log)
|
7
|
+
[](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
|
-
|
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
|
-
|
91
|
+
### ALB
|
86
92
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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', '~>
|
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
|
-
|
12
|
-
'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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 :
|
37
|
-
config_param :
|
38
|
-
config_param :
|
39
|
-
config_param :
|
40
|
-
config_param :
|
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, :
|
45
|
-
config_param :tag, :string, :
|
46
|
-
config_param :tsfile_path, :string, :
|
47
|
-
config_param :histfile_path, :string, :
|
48
|
-
config_param :interval, :time, :
|
49
|
-
config_param :start_datetime, :string, :
|
50
|
-
config_param :buffer_sec, :time, :
|
51
|
-
config_param :history_length, :integer, :
|
52
|
-
config_param :sampling_interval, :integer, :
|
53
|
-
config_param :debug, :bool, :
|
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(:
|
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
|
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
|
-
|
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
|
-
|
241
|
+
begin
|
242
|
+
record = Hash[access_log_fields.keys.zip(row)]
|
176
243
|
|
177
|
-
|
178
|
-
|
179
|
-
|
244
|
+
access_log_fields.each do |name, conv|
|
245
|
+
record[name] = record[name].send(conv) if conv
|
246
|
+
end
|
180
247
|
|
181
|
-
|
182
|
-
split_address_port!(record, 'backend')
|
248
|
+
split_address_port!(record, 'client')
|
183
249
|
|
184
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
272
|
+
def parse_clb_line(line)
|
197
273
|
parsed = nil
|
198
274
|
|
199
275
|
begin
|
200
|
-
parsed = CSV.parse_line(line, :
|
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
|
211
|
-
|
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 = {:
|
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 = {:
|
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
|