fluent-plugin-google-cloud 0.2.2 → 0.2.3

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