fluent-plugin-google-cloud 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/CONTRIBUTING ADDED
@@ -0,0 +1,24 @@
1
+ Want to contribute? Great! First, read this page (including the small print at the end).
2
+
3
+ ### Before you contribute
4
+ Before we can use your code, you must sign the
5
+ [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1)
6
+ (CLA), which you can do online. The CLA is necessary mainly because you own the
7
+ copyright to your changes, even after your contribution becomes part of our
8
+ codebase, so we need your permission to use and distribute your code. We also
9
+ need to be sure of various other things—for instance that you'll tell us if you
10
+ know that your code infringes on other people's patents. You don't have to sign
11
+ the CLA until after you've submitted your code for review and a member has
12
+ approved it, but you must do it before we can put your code into our codebase.
13
+ Before you start working on a larger contribution, you should get in touch with
14
+ us first through the issue tracker with your idea so that we can help out and
15
+ possibly guide you. Coordinating up front makes it much easier to avoid
16
+ frustration later on.
17
+
18
+ ### Code reviews
19
+ All submissions, including submissions by project members, require review. We
20
+ use Github pull requests for this purpose.
21
+
22
+ ### The small print
23
+ Contributions made by corporations are covered by a different agreement than
24
+ the one above, the Software Grant and Corporate Contributor License Agreement.
data/Gemfile.lock CHANGED
@@ -1,16 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.2.2)
4
+ fluent-plugin-google-cloud (0.2.3)
5
5
  fluentd (>= 0.10)
6
6
  google-api-client (>= 0.8)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (3.2.21)
12
- i18n (~> 0.6, >= 0.6.4)
13
- multi_json (~> 1.0)
11
+ activesupport (4.2.1)
12
+ i18n (~> 0.7)
13
+ json (~> 1.7, >= 1.7.7)
14
+ minitest (~> 5.1)
15
+ thread_safe (~> 0.3, >= 0.3.4)
16
+ tzinfo (~> 1.1)
14
17
  addressable (2.3.8)
15
18
  autoparse (0.3.3)
16
19
  addressable (>= 2.3.1)
@@ -22,7 +25,7 @@ GEM
22
25
  extlib (0.9.16)
23
26
  faraday (0.9.1)
24
27
  multipart-post (>= 1.2, < 3)
25
- fluentd (0.12.7)
28
+ fluentd (0.12.8)
26
29
  cool.io (>= 1.2.2, < 2.0.0)
27
30
  http_parser.rb (>= 0.5.1, < 0.7.0)
28
31
  json (>= 1.4.3)
@@ -32,8 +35,8 @@ GEM
32
35
  tzinfo (>= 1.0.0)
33
36
  tzinfo-data (>= 1.0.0)
34
37
  yajl-ruby (~> 1.0)
35
- google-api-client (0.8.4)
36
- activesupport (~> 3.2)
38
+ google-api-client (0.8.6)
39
+ activesupport (>= 3.2)
37
40
  addressable (~> 2.3)
38
41
  autoparse (~> 0.3)
39
42
  extlib (~> 0.9)
@@ -43,11 +46,11 @@ GEM
43
46
  multi_json (~> 1.10)
44
47
  retriable (~> 1.4)
45
48
  signet (~> 0.6)
46
- googleauth (0.4.0)
49
+ googleauth (0.4.1)
47
50
  faraday (~> 0.9)
48
51
  jwt (~> 1.4)
49
- logging (~> 1.8)
50
- memoist (~> 0.11)
52
+ logging (~> 2.0)
53
+ memoist (~> 0.12)
51
54
  multi_json (= 1.11)
52
55
  signet (~> 0.6)
53
56
  http_parser.rb (0.6.0)
@@ -57,10 +60,11 @@ GEM
57
60
  launchy (2.4.3)
58
61
  addressable (~> 2.3)
59
62
  little-plugger (1.1.3)
60
- logging (1.8.2)
61
- little-plugger (>= 1.1.3)
62
- multi_json (>= 1.8.4)
63
- memoist (0.11.0)
63
+ logging (2.0.0)
64
+ little-plugger (~> 1.1)
65
+ multi_json (~> 1.10)
66
+ memoist (0.12.0)
67
+ minitest (5.6.1)
64
68
  msgpack (0.5.11)
65
69
  multi_json (1.11.0)
66
70
  multipart-post (2.0.0)
@@ -78,7 +82,7 @@ GEM
78
82
  thread_safe (0.3.5)
79
83
  tzinfo (1.2.2)
80
84
  thread_safe (~> 0.1)
81
- tzinfo-data (1.2015.2)
85
+ tzinfo-data (1.2015.4)
82
86
  tzinfo (>= 1.0.0)
83
87
  webmock (1.21.0)
84
88
  addressable (>= 2.3.6)
@@ -4,7 +4,7 @@ Gem::Specification.new do |gem|
4
4
  gem.summary = %q{Fluentd plugin to stream logs to the Google Cloud Platform's logging API}
5
5
  gem.homepage = 'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
6
6
  gem.license = 'Apache 2.0'
7
- gem.version = '0.2.2'
7
+ gem.version = '0.2.3'
8
8
  gem.authors = ['Todd Derr', 'Alex Robinson']
9
9
  gem.email = ['salty@google.com']
10
10
 
@@ -86,6 +86,8 @@ module Fluent
86
86
 
87
87
  init_api_client()
88
88
 
89
+ @successful_call = false
90
+
89
91
  # Grab metadata about the Google Compute Engine instance that we're on.
90
92
  @project_id = fetch_metadata('project/project-id')
91
93
  fully_qualified_zone = fetch_metadata('instance/zone')
@@ -152,6 +154,7 @@ module Fluent
152
154
  if (record.has_key?('timeNanos'))
153
155
  ts_secs = (record['timeNanos'] / 1000000000).to_i
154
156
  ts_nanos = record['timeNanos'] % 1000000000
157
+ record.delete('timeNanos')
155
158
  else
156
159
  timestamp = Time.at(time)
157
160
  ts_secs = timestamp.tv_sec
@@ -167,11 +170,20 @@ module Fluent
167
170
  'nanos' => ts_nanos
168
171
  },
169
172
  },
170
- 'textPayload' => record['message']
171
- # TODO: default severity?
172
173
  }
173
174
  if record.has_key?('severity')
174
- entry['metadata']['severity'] = record['severity']
175
+ entry['metadata']['severity'] = parse_severity(record['severity'])
176
+ record.delete('severity')
177
+ else
178
+ entry['metadata']['severity'] = 'DEFAULT'
179
+ end
180
+
181
+ # use textPayload if the only remainaing key is 'message',
182
+ # otherwise use a struct.
183
+ if (record.size == 1 && record.has_key?('message'))
184
+ entry['textPayload'] = record['message']
185
+ else
186
+ entry['structPayload'] = record
175
187
  end
176
188
  write_log_entries_request['entries'].push(entry)
177
189
  end
@@ -191,6 +203,12 @@ module Fluent
191
203
  :authenticated => true
192
204
  })
193
205
  client.execute!(request)
206
+ # Let the user explicitly know when the first call succeeded,
207
+ # to aid with verification and troubleshooting.
208
+ if (!@successful_call)
209
+ @successful_call = true
210
+ $log.info "Successfully sent to Google Cloud Logging API."
211
+ end
194
212
  # Allow most exceptions to propagate, which will cause fluentd to
195
213
  # retry (with backoff), but in some cases we catch the error and
196
214
  # drop the request (we will emit a log message in those cases).
@@ -205,7 +223,6 @@ module Fluent
205
223
  rescue JSON::GeneratorError => error
206
224
  # This happens if the request contains illegal characters;
207
225
  # do not retry it because it will fail repeatedly.
208
- dropped = write_log_entries_request['entries'].length
209
226
  log_write_failure(write_log_entries_request, error)
210
227
  end
211
228
  end
@@ -237,10 +254,72 @@ module Fluent
237
254
  end
238
255
  end
239
256
 
257
+ # Values permitted by the API for 'severity' (which is an enum).
258
+ VALID_SEVERITIES = Set.new [
259
+ 'DEFAULT', 'DEBUG', 'INFO', 'NOTICE', 'WARNING', 'ERROR', 'CRITICAL',
260
+ 'ALERT', 'EMERGENCY']
261
+
262
+ # Translates other severity strings to one of the valid values above.
263
+ SEVERITY_TRANSLATIONS = {
264
+ # log4j levels (both current and obsolete).
265
+ 'WARN' => 'WARNING',
266
+ 'FATAL' => 'CRITICAL',
267
+ 'TRACE' => 'DEBUG',
268
+ 'TRACE_INT' => 'DEBUG',
269
+ 'FINE' => 'DEBUG',
270
+ 'FINER' => 'DEBUG',
271
+ 'FINEST' => 'DEBUG',
272
+ # single-letter levels. Note E->ERROR and D->DEBUG.
273
+ 'D' => 'DEBUG',
274
+ 'I' => 'INFO',
275
+ 'N' => 'NOTICE',
276
+ 'W' => 'WARNING',
277
+ 'E' => 'ERROR',
278
+ 'C' => 'CRITICAL',
279
+ 'A' => 'ALERT',
280
+ # other misc. translations.
281
+ 'ERR' => 'ERROR',
282
+ }
283
+
284
+ def parse_severity(severity_str)
285
+ # The API is case insensitive, but uppercase to make things simpler.
286
+ severity = severity_str.upcase.strip
287
+
288
+ # If the severity is already valid, just return it.
289
+ if (VALID_SEVERITIES.include?(severity))
290
+ return severity
291
+ end
292
+
293
+ # If the severity is an integer (string) return it as an integer,
294
+ # truncated to the closest valid value (multiples of 100 between 0-800).
295
+ if (/\A\d+\z/.match(severity))
296
+ begin
297
+ numeric_severity = (severity.to_i / 100) * 100
298
+ if (numeric_severity < 0)
299
+ return 0
300
+ elsif (numeric_severity > 800)
301
+ return 800
302
+ else
303
+ return numeric_severity
304
+ end
305
+ rescue
306
+ return 'DEFAULT'
307
+ end
308
+ end
309
+
310
+ # Try to translate the severity.
311
+ if (SEVERITY_TRANSLATIONS.has_key?(severity))
312
+ return SEVERITY_TRANSLATIONS[severity]
313
+ end
314
+
315
+ # If all else fails, use 'DEFAULT'.
316
+ return 'DEFAULT'
317
+ end
318
+
240
319
  def init_api_client
241
320
  @client = Google::APIClient.new(
242
321
  :application_name => 'Fluentd Google Cloud Logging plugin',
243
- :application_version => '0.2.2',
322
+ :application_version => '0.2.3',
244
323
  :retries => 1)
245
324
 
246
325
  if @auth_method == 'private_key'
@@ -119,19 +119,19 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
119
119
  def test_configure_invalid_configs
120
120
  begin
121
121
  d = create_driver(INVALID_CONFIG1)
122
- assert_false
122
+ assert false
123
123
  rescue Fluent::ConfigError => error
124
124
  assert error.message.include? 'private_key_path'
125
125
  end
126
126
  begin
127
127
  d = create_driver(INVALID_CONFIG2)
128
- assert_false
128
+ assert false
129
129
  rescue Fluent::ConfigError => error
130
130
  assert error.message.include? 'private_key_email'
131
131
  end
132
132
  begin
133
133
  d = create_driver(INVALID_CONFIG3)
134
- assert_false
134
+ assert false
135
135
  rescue Fluent::ConfigError => error
136
136
  assert error.message.include? 'auth_method'
137
137
  end
@@ -166,6 +166,19 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
166
166
  verify_log_entries(1, COMPUTE_PARAMS)
167
167
  end
168
168
 
169
+ def test_struct_payload_log
170
+ setup_logging_stubs
171
+ d = create_driver(PRIVATE_KEY_CONFIG)
172
+ d.emit({'msg' => log_entry(0), 'tag2' => 'test', 'data' => 5000})
173
+ d.run
174
+ verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
175
+ assert_equal 3, entry['structPayload'].size, entry
176
+ assert_equal "test log entry 0", entry['structPayload']['msg'], entry
177
+ assert_equal 'test', entry['structPayload']['tag2'], entry
178
+ assert_equal 5000, entry['structPayload']['data'], entry
179
+ end
180
+ end
181
+
169
182
  def test_timestamps
170
183
  setup_logging_stubs
171
184
  d = create_driver(PRIVATE_KEY_CONFIG)
@@ -194,6 +207,27 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
194
207
  end
195
208
  end
196
209
 
210
+ def test_severities
211
+ setup_logging_stubs
212
+ d = create_driver(PRIVATE_KEY_CONFIG)
213
+ expected_severity = []
214
+ emit_index = 0
215
+ # Array of pairs of [parsed_severity, expected_severity]
216
+ [['INFO', 'INFO'], ['warn', 'WARNING'], ['E', 'ERROR'],
217
+ ['BLAH', 'DEFAULT'], ['105', 100], ['', 'DEFAULT']].each do |sev|
218
+ d.emit({'message' => log_entry(emit_index), 'severity' => sev[0]})
219
+ expected_severity.push(sev[1])
220
+ emit_index += 1
221
+ end
222
+ d.run
223
+ verify_index = 0
224
+ verify_log_entries(emit_index, COMPUTE_PARAMS) do |entry|
225
+ assert_equal expected_severity[verify_index],
226
+ entry['metadata']['severity'], entry
227
+ verify_index += 1
228
+ end
229
+ end
230
+
197
231
  def test_multiple_logs
198
232
  setup_logging_stubs
199
233
  d = create_driver(PRIVATE_KEY_CONFIG)
@@ -299,6 +333,84 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
299
333
  end
300
334
  end
301
335
 
336
+ # Make parse_severity public so we can test it.
337
+ class Fluent::GoogleCloudOutput
338
+ public :parse_severity
339
+ end
340
+
341
+ def test_parse_severity
342
+ test_obj = Fluent::GoogleCloudOutput.new
343
+
344
+ # known severities should translate to themselves, regardless of case
345
+ ['DEFAULT', 'DEBUG', 'INFO', 'NOTICE', 'WARNING', 'ERROR', 'CRITICAL',
346
+ 'ALERT', 'EMERGENCY'].each do |severity|
347
+ assert_equal(severity, test_obj.parse_severity(severity))
348
+ assert_equal(severity, test_obj.parse_severity(severity.downcase))
349
+ assert_equal(severity, test_obj.parse_severity(severity.capitalize))
350
+ end
351
+
352
+ # numeric levels
353
+ assert_equal(0, test_obj.parse_severity('0'))
354
+ assert_equal(100, test_obj.parse_severity('100'))
355
+ assert_equal(200, test_obj.parse_severity('200'))
356
+ assert_equal(300, test_obj.parse_severity('300'))
357
+ assert_equal(400, test_obj.parse_severity('400'))
358
+ assert_equal(500, test_obj.parse_severity('500'))
359
+ assert_equal(600, test_obj.parse_severity('600'))
360
+ assert_equal(700, test_obj.parse_severity('700'))
361
+ assert_equal(800, test_obj.parse_severity('800'))
362
+
363
+ assert_equal(800, test_obj.parse_severity('900'))
364
+ assert_equal(0, test_obj.parse_severity('1'))
365
+ assert_equal(100, test_obj.parse_severity('105'))
366
+ assert_equal(400, test_obj.parse_severity('420'))
367
+ assert_equal(700, test_obj.parse_severity('799'))
368
+
369
+ assert_equal(100, test_obj.parse_severity('105 '))
370
+ assert_equal(100, test_obj.parse_severity(' 105'))
371
+ assert_equal(100, test_obj.parse_severity(' 105 '))
372
+
373
+ assert_equal('DEFAULT', test_obj.parse_severity('-100'))
374
+ assert_equal('DEFAULT', test_obj.parse_severity('105 100'))
375
+
376
+ # synonyms for existing log levels
377
+ assert_equal('ERROR', test_obj.parse_severity('ERR'))
378
+ assert_equal('WARNING', test_obj.parse_severity('WARN'))
379
+ assert_equal('CRITICAL', test_obj.parse_severity('FATAL'))
380
+ assert_equal('DEBUG', test_obj.parse_severity('TRACE'))
381
+ assert_equal('DEBUG', test_obj.parse_severity('TRACE_INT'))
382
+ assert_equal('DEBUG', test_obj.parse_severity('FINE'))
383
+ assert_equal('DEBUG', test_obj.parse_severity('FINER'))
384
+ assert_equal('DEBUG', test_obj.parse_severity('FINEST'))
385
+
386
+ # single letters.
387
+ assert_equal('DEBUG', test_obj.parse_severity('D'))
388
+ assert_equal('INFO', test_obj.parse_severity('I'))
389
+ assert_equal('NOTICE', test_obj.parse_severity('N'))
390
+ assert_equal('WARNING', test_obj.parse_severity('W'))
391
+ assert_equal('ERROR', test_obj.parse_severity('E'))
392
+ assert_equal('CRITICAL', test_obj.parse_severity('C'))
393
+ assert_equal('ALERT', test_obj.parse_severity('A'))
394
+ assert_equal('ERROR', test_obj.parse_severity('e'))
395
+
396
+ assert_equal('DEFAULT', test_obj.parse_severity('x'))
397
+ assert_equal('DEFAULT', test_obj.parse_severity('-'))
398
+
399
+ # leading/trailing whitespace should be stripped
400
+ assert_equal('ERROR', test_obj.parse_severity(' ERROR'))
401
+ assert_equal('ERROR', test_obj.parse_severity('ERROR '))
402
+ assert_equal('ERROR', test_obj.parse_severity(' ERROR '))
403
+ assert_equal('ERROR', test_obj.parse_severity("\t ERROR "))
404
+
405
+ # space in the middle should not be stripped.
406
+ assert_equal('DEFAULT', test_obj.parse_severity('ER ROR'))
407
+
408
+ # anything else should translate to 'DEFAULT'
409
+ assert_equal('DEFAULT', test_obj.parse_severity(''))
410
+ assert_equal('DEFAULT', test_obj.parse_severity('garbage'))
411
+ assert_equal('DEFAULT', test_obj.parse_severity('er'))
412
+ end
413
+
302
414
  private
303
415
 
304
416
  def uri_for_log(config)
@@ -340,11 +452,16 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
340
452
  end
341
453
 
342
454
  # The caller can optionally provide a block which is called for each entry.
343
- def verify_log_entries(n, params)
455
+ def verify_log_entries(n, params, payload_type='textPayload')
344
456
  i = 0
345
457
  @logs_sent.each do |batch|
346
458
  batch['entries'].each do |entry|
347
- assert_equal "test log entry #{i}", entry['textPayload'], batch
459
+ assert entry.has_key?(payload_type)
460
+ if (payload_type == 'textPayload')
461
+ # Check the payload for textPayload, otherwise it is up to the caller.
462
+ assert_equal "test log entry #{i}", entry['textPayload'], batch
463
+ end
464
+
348
465
  assert_equal ZONE, entry['metadata']['zone']
349
466
  assert_equal params['service_name'], entry['metadata']['serviceName']
350
467
  check_labels entry, batch['commonLabels'], params['labels']
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-google-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-03-30 00:00:00.000000000 Z
13
+ date: 2015-05-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: fluentd
@@ -86,16 +86,17 @@ executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files: []
88
88
  files:
89
+ - lib/fluent/plugin/out_google_cloud.rb
90
+ - Gemfile.lock
91
+ - CONTRIBUTING
92
+ - README.rdoc
89
93
  - test/helper.rb
90
- - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
91
94
  - test/plugin/test_out_google_cloud.rb
95
+ - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
92
96
  - LICENSE
93
97
  - Rakefile
94
- - fluent-plugin-google-cloud.gemspec
95
- - lib/fluent/plugin/out_google_cloud.rb
96
- - Gemfile.lock
97
98
  - Gemfile
98
- - README.rdoc
99
+ - fluent-plugin-google-cloud.gemspec
99
100
  homepage: https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud
100
101
  licenses:
101
102
  - Apache 2.0
@@ -109,18 +110,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
110
  - - ! '>='
110
111
  - !ruby/object:Gem::Version
111
112
  version: '0'
112
- segments:
113
- - 0
114
- hash: 2434444153919230667
115
113
  required_rubygems_version: !ruby/object:Gem::Requirement
116
114
  none: false
117
115
  requirements:
118
116
  - - ! '>='
119
117
  - !ruby/object:Gem::Version
120
118
  version: '0'
121
- segments:
122
- - 0
123
- hash: 2434444153919230667
124
119
  requirements: []
125
120
  rubyforge_project:
126
121
  rubygems_version: 1.8.23
@@ -129,5 +124,5 @@ specification_version: 3
129
124
  summary: Fluentd plugin to stream logs to the Google Cloud Platform's logging API
130
125
  test_files:
131
126
  - test/helper.rb
132
- - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
133
127
  - test/plugin/test_out_google_cloud.rb
128
+ - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12