fluent-plugin-google-cloud 0.4.17 → 0.5.0
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/Gemfile.lock +1 -1
- data/README.rdoc +1 -1
- data/Rakefile +11 -1
- data/fluent-plugin-google-cloud.gemspec +4 -6
- data/lib/fluent/plugin/out_google_cloud.rb +119 -154
- data/test/plugin/test_out_google_cloud.rb +24 -87
- metadata +6 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1989b8ce8911575be17949674d1d8dfb137dbeda
|
4
|
+
data.tar.gz: f5ac2f7ca47a5c61dfc7a32a9254b6de841a2182
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9de731cc6f29055be014d6894f0b4930c7c6b5d836c149789d0ae06fe7e14a09687307485b559b6cf77335770a1f84003945468ed9b863d11c49a079142dad1a
|
7
|
+
data.tar.gz: aa07feea223b293e68da01fcf7e35facef3aa3af7ce3a8ba014304479e3521129809f004bb94a342ac6a00a2ee83bb0fe4132a2caae87ecdfcc4691cf6adbb5b
|
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -38,7 +38,7 @@ for authorization - for additional information see
|
|
38
38
|
{here}[https://cloud.google.com/logging/docs/agent/authorization].
|
39
39
|
|
40
40
|
<em>The previously documented parameters auth_method, private_key_email,
|
41
|
-
and private_key_path are
|
41
|
+
and private_key_path are removed, and can no longer be used.</em>
|
42
42
|
|
43
43
|
== Copyright
|
44
44
|
|
data/Rakefile
CHANGED
@@ -16,7 +16,17 @@ Rake::TestTask.new(:test) do |test|
|
|
16
16
|
test.verbose = true
|
17
17
|
end
|
18
18
|
|
19
|
+
# Building the gem will use the local file mode, so ensure it's world-readable.
|
20
|
+
# https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud/issues/53
|
21
|
+
desc 'Check plugin file permissions'
|
22
|
+
task :check_perms do
|
23
|
+
plugin = 'lib/fluent/plugin/out_google_cloud.rb'
|
24
|
+
mode = File.stat(plugin).mode & 0777
|
25
|
+
fail "Unexpected mode #{mode.to_s(8)} for #{plugin}" unless
|
26
|
+
mode & 0444 == 0444
|
27
|
+
end
|
28
|
+
|
19
29
|
desc 'Run unit tests and RuboCop to check for style violations'
|
20
|
-
task all: [:test, :rubocop]
|
30
|
+
task all: [:test, :rubocop, :check_perms]
|
21
31
|
|
22
32
|
task default: :all
|
@@ -10,25 +10,23 @@ eos
|
|
10
10
|
gem.homepage = \
|
11
11
|
'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
|
12
12
|
gem.license = 'Apache-2.0'
|
13
|
-
gem.version = '0.
|
13
|
+
gem.version = '0.5.0'
|
14
14
|
gem.authors = ['Todd Derr', 'Alex Robinson']
|
15
15
|
gem.email = ['salty@google.com']
|
16
|
+
gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
|
16
17
|
|
17
18
|
gem.files = Dir['**/*'].keep_if { |file| File.file?(file) }
|
18
19
|
gem.test_files = gem.files.grep(/^(test)/)
|
19
20
|
gem.require_paths = ['lib']
|
20
21
|
|
21
22
|
gem.add_runtime_dependency 'fluentd', '~> 0.10'
|
22
|
-
gem.add_runtime_dependency 'google-api-client', '
|
23
|
+
gem.add_runtime_dependency 'google-api-client', '> 0.9'
|
23
24
|
gem.add_runtime_dependency 'googleauth', '~> 0.4'
|
24
25
|
gem.add_runtime_dependency 'json', '~> 1.8'
|
25
|
-
# workaround for jwt 1.5.3 breaking ruby 1.9 support (included by googleauth)
|
26
|
-
# see https://github.com/jwt/ruby-jwt/issues/132
|
27
|
-
gem.add_runtime_dependency 'jwt', '< 1.5.3'
|
28
26
|
|
29
27
|
gem.add_development_dependency 'mocha', '~> 1.1'
|
30
28
|
gem.add_development_dependency 'rake', '~> 10.3'
|
31
|
-
gem.add_development_dependency 'rubocop', '= 0.
|
29
|
+
gem.add_development_dependency 'rubocop', '= 0.35.0'
|
32
30
|
gem.add_development_dependency 'webmock', '~> 1.17'
|
33
31
|
gem.add_development_dependency 'test-unit', '~> 3.0'
|
34
32
|
end
|
@@ -11,14 +11,13 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
require 'cgi'
|
15
|
-
require 'google/api_client'
|
16
|
-
require 'google/api_client/auth/compute_service_account'
|
17
|
-
require 'googleauth'
|
18
14
|
require 'json'
|
19
15
|
require 'open-uri'
|
20
16
|
require 'socket'
|
21
17
|
require 'yaml'
|
18
|
+
require 'google/apis'
|
19
|
+
require 'google/apis/logging_v1beta3'
|
20
|
+
require 'googleauth'
|
22
21
|
|
23
22
|
module Fluent
|
24
23
|
# fluentd output plugin for the Google Cloud Logging API
|
@@ -26,7 +25,7 @@ module Fluent
|
|
26
25
|
Fluent::Plugin.register_output('google_cloud', self)
|
27
26
|
|
28
27
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
29
|
-
PLUGIN_VERSION = '0.
|
28
|
+
PLUGIN_VERSION = '0.5.0'
|
30
29
|
|
31
30
|
# Constants for service names.
|
32
31
|
APPENGINE_SERVICE = 'appengine.googleapis.com'
|
@@ -42,28 +41,9 @@ module Fluent
|
|
42
41
|
# Address of the metadata service.
|
43
42
|
METADATA_SERVICE_ADDR = '169.254.169.254'
|
44
43
|
|
45
|
-
# Fields allowed in httpRequest object
|
46
|
-
HTTP_REQUEST_FIELDS = %w(requestMethod requestUrl requestSize status
|
47
|
-
responseSize userAgent remoteIp referer
|
48
|
-
cacheHit validatedWithOriginServer)
|
49
|
-
|
50
44
|
# Disable this warning to conform to fluentd config_param conventions.
|
51
45
|
# rubocop:disable Style/HashSyntax
|
52
46
|
|
53
|
-
# DEPRECATED: auth_method (and support for 'private_key') is deprecated in
|
54
|
-
# favor of Google Application Default Credentials as documented at:
|
55
|
-
# https://developers.google.com/identity/protocols/application-default-credentials
|
56
|
-
# 'private_key' is still accepted to support existing users; any other
|
57
|
-
# value is ignored.
|
58
|
-
config_param :auth_method, :string, :default => nil
|
59
|
-
|
60
|
-
# DEPRECATED: Parameters necessary to use the private_key auth_method.
|
61
|
-
config_param :private_key_email, :string, :default => nil
|
62
|
-
config_param :private_key_path, :string, :default => nil
|
63
|
-
config_param :private_key_passphrase, :string,
|
64
|
-
:default => 'notasecret',
|
65
|
-
:secret => true
|
66
|
-
|
67
47
|
# Specify project/instance metadata.
|
68
48
|
#
|
69
49
|
# project_id, zone, and vm_id are required to have valid values, which
|
@@ -117,6 +97,15 @@ module Fluent
|
|
117
97
|
# }
|
118
98
|
config_param :label_map, :hash, :default => nil
|
119
99
|
|
100
|
+
# DEPRECATED: The following parameters, if present in the config
|
101
|
+
# indicate that the plugin configuration must be updated.
|
102
|
+
config_param :auth_method, :string, :default => nil
|
103
|
+
config_param :private_key_email, :string, :default => nil
|
104
|
+
config_param :private_key_path, :string, :default => nil
|
105
|
+
config_param :private_key_passphrase, :string,
|
106
|
+
:default => nil,
|
107
|
+
:secret => true
|
108
|
+
|
120
109
|
# rubocop:enable Style/HashSyntax
|
121
110
|
|
122
111
|
# TODO: Add a log_name config option rather than just using the tag?
|
@@ -141,21 +130,19 @@ module Fluent
|
|
141
130
|
def configure(conf)
|
142
131
|
super
|
143
132
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
133
|
+
# Alert on old authentication configuration.
|
134
|
+
unless @auth_method.nil? && @private_key_email.nil? &&
|
135
|
+
@private_key_path.nil? && @private_key_passphrase.nil?
|
136
|
+
extra = []
|
137
|
+
extra << 'auth_method' unless @auth_method.nil?
|
138
|
+
extra << 'private_key_email' unless @private_key_email.nil?
|
139
|
+
extra << 'private_key_path' unless @private_key_path.nil?
|
140
|
+
extra << 'private_key_passphrase' unless @private_key_passphrase.nil?
|
141
|
+
|
142
|
+
fail Fluent::ConfigError,
|
143
|
+
"#{PLUGIN_NAME} no longer supports auth_method.\n" \
|
144
|
+
'Please remove configuration parameters: ' +
|
145
|
+
extra.join(' ')
|
159
146
|
end
|
160
147
|
|
161
148
|
# TODO: Send instance tags as labels as well?
|
@@ -350,14 +337,13 @@ module Fluent
|
|
350
337
|
arr.each do |time, record|
|
351
338
|
next unless record.is_a?(Hash)
|
352
339
|
|
353
|
-
entry =
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
}
|
340
|
+
entry = Google::Apis::LoggingV1beta3::LogEntry.new(
|
341
|
+
metadata: Google::Apis::LoggingV1beta3::LogEntryMetadata.new(
|
342
|
+
service_name: @service_name,
|
343
|
+
project_id: @project_id,
|
344
|
+
zone: @zone,
|
345
|
+
labels: {}
|
346
|
+
))
|
361
347
|
|
362
348
|
if @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
|
363
349
|
@cloudfunctions_log_match =
|
@@ -365,7 +351,7 @@ module Fluent
|
|
365
351
|
end
|
366
352
|
if @service_name == CONTAINER_SERVICE
|
367
353
|
# Move the stdout/stderr annotation from the record into a label
|
368
|
-
field_to_label(record, 'stream', entry
|
354
|
+
field_to_label(record, 'stream', entry.metadata.labels,
|
369
355
|
"#{CONTAINER_SERVICE}/stream")
|
370
356
|
# If the record has been annotated by the kubernetes_metadata_filter
|
371
357
|
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
@@ -403,87 +389,75 @@ module Fluent
|
|
403
389
|
# and do not send that field as part of the payload.
|
404
390
|
if @label_map
|
405
391
|
@label_map.each do |field, label|
|
406
|
-
field_to_label(record, field, entry
|
392
|
+
field_to_label(record, field, entry.metadata.labels, label)
|
407
393
|
end
|
408
394
|
end
|
409
395
|
|
410
396
|
if @service_name == CLOUDFUNCTIONS_SERVICE &&
|
411
397
|
@cloudfunctions_log_match &&
|
412
398
|
@cloudfunctions_log_match['execution_id']
|
413
|
-
entry
|
399
|
+
entry.metadata.labels['execution_id'] =
|
414
400
|
@cloudfunctions_log_match['execution_id']
|
415
401
|
end
|
416
402
|
|
417
403
|
set_payload(record, entry, is_container_json)
|
418
|
-
|
419
|
-
# Remove the labels metadata if we didn't populate it with anything.
|
420
|
-
if entry['metadata']['labels'].empty?
|
421
|
-
entry['metadata'].delete('labels')
|
422
|
-
end
|
404
|
+
entry.metadata.labels = nil if entry.metadata.labels.empty?
|
423
405
|
|
424
406
|
entries.push(entry)
|
425
407
|
end
|
426
408
|
# Don't send an empty request if we rejected all the entries.
|
427
409
|
next if entries.empty?
|
428
410
|
|
429
|
-
log_name =
|
430
|
-
url = 'https://logging.googleapis.com/v1beta3/projects/' \
|
431
|
-
"#{@project_id}/logs/#{log_name}/entries:write"
|
411
|
+
log_name = log_name(tag, labels)
|
432
412
|
|
433
413
|
begin
|
414
|
+
# Does the actual write to the cloud logging api.
|
415
|
+
# The URI of the write is constructed by the Google::Api request;
|
416
|
+
# it is equivalent to this URL:
|
417
|
+
# 'https://logging.googleapis.com/v1beta3/projects/' \
|
418
|
+
# "#{@project_id}/logs/#{log_name}/entries:write"
|
419
|
+
|
434
420
|
client = api_client
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
client.execute!(request)
|
421
|
+
|
422
|
+
write_request = \
|
423
|
+
Google::Apis::LoggingV1beta3::WriteLogEntriesRequest.new(
|
424
|
+
common_labels: labels,
|
425
|
+
entries: entries)
|
426
|
+
|
427
|
+
# TODO: RequestOptions
|
428
|
+
client.write_log_entries(@project_id, log_name, write_request)
|
429
|
+
|
445
430
|
# Let the user explicitly know when the first call succeeded,
|
446
431
|
# to aid with verification and troubleshooting.
|
447
432
|
unless @successful_call
|
448
433
|
@successful_call = true
|
449
434
|
@log.info 'Successfully sent to Google Cloud Logging API.'
|
450
435
|
end
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
436
|
+
|
437
|
+
rescue Google::Apis::ServerError => error
|
438
|
+
# Server error, so retry via re-raising the error.
|
439
|
+
raise error
|
440
|
+
|
441
|
+
rescue Google::Apis::AuthorizationError => error
|
442
|
+
# Authorization error.
|
443
|
+
# These are usually solved via a `gcloud auth` call, or by modifying
|
444
|
+
# the permissions on the Google Cloud project.
|
445
|
+
dropped = entries.length
|
446
|
+
@log.warn "Dropping #{dropped} log message(s)",
|
447
|
+
error_class: error.class.to_s, error: error.to_s
|
448
|
+
|
449
|
+
rescue Google::Apis::ClientError => error
|
455
450
|
# Most ClientErrors indicate a problem with the request itself and
|
456
|
-
# should not be retried
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
rescue JSON::GeneratorError => error
|
461
|
-
# This happens if the request contains illegal characters;
|
462
|
-
# do not retry it because it will fail repeatedly.
|
463
|
-
log_write_failure(entries.length, error)
|
451
|
+
# should not be retried.
|
452
|
+
dropped = entries.length
|
453
|
+
@log.warn "Dropping #{dropped} log message(s)",
|
454
|
+
error_class: error.class.to_s, error: error.to_s
|
464
455
|
end
|
465
456
|
end
|
466
457
|
end
|
467
458
|
|
468
459
|
private
|
469
460
|
|
470
|
-
RETRIABLE_CLIENT_ERRORS = Set.new [
|
471
|
-
'Invalid Credentials',
|
472
|
-
'Request had invalid credentials.',
|
473
|
-
'The caller does not have permission',
|
474
|
-
'Project has not enabled the API. Please use Google Developers ' \
|
475
|
-
'Console to activate the API for your project.',
|
476
|
-
'Unable to fetch access token (no scopes configured?)']
|
477
|
-
|
478
|
-
def retriable_client_error?(error)
|
479
|
-
RETRIABLE_CLIENT_ERRORS.include?(error.message)
|
480
|
-
end
|
481
|
-
|
482
|
-
def log_write_failure(dropped, error)
|
483
|
-
@log.warn "Dropping #{dropped} log message(s)",
|
484
|
-
error_class: error.class.to_s, error: error.to_s
|
485
|
-
end
|
486
|
-
|
487
461
|
def parse_json_or_nil(input)
|
488
462
|
# Only here to please rubocop...
|
489
463
|
return nil if input.nil?
|
@@ -512,11 +486,11 @@ module Fluent
|
|
512
486
|
|
513
487
|
begin
|
514
488
|
open('http://' + METADATA_SERVICE_ADDR) do |f|
|
515
|
-
if
|
489
|
+
if f.meta['metadata-flavor'] == 'Google'
|
516
490
|
@log.info 'Detected GCE platform'
|
517
491
|
return Platform::GCE
|
518
492
|
end
|
519
|
-
if
|
493
|
+
if f.meta['server'] == 'EC2ws'
|
520
494
|
@log.info 'Detected EC2 platform'
|
521
495
|
return Platform::EC2
|
522
496
|
end
|
@@ -554,7 +528,6 @@ module Fluent
|
|
554
528
|
# Determine the project ID from the credentials, if possible.
|
555
529
|
# Returns the project ID (as a string) on success, or nil on failure.
|
556
530
|
def self.project_id
|
557
|
-
return nil if @auth_method == 'private_key'
|
558
531
|
creds = Google::Auth.get_application_default(LOGGING_SCOPE)
|
559
532
|
if creds.issuer
|
560
533
|
id = extract_project_id(creds.issuer)
|
@@ -610,16 +583,14 @@ module Fluent
|
|
610
583
|
record.delete('timestamp')
|
611
584
|
elsif record.key?('timestampSeconds') &&
|
612
585
|
record.key?('timestampNanos')
|
613
|
-
ts_secs = record
|
614
|
-
ts_nanos = record
|
615
|
-
record.delete('timestampSeconds')
|
616
|
-
record.delete('timestampNanos')
|
586
|
+
ts_secs = record.delete('timestampSeconds')
|
587
|
+
ts_nanos = record.delete('timestampNanos')
|
617
588
|
elsif record.key?('timeNanos')
|
618
589
|
# This is deprecated since the precision is insufficient.
|
619
590
|
# Use timestampSeconds/timestampNanos instead
|
620
|
-
|
621
|
-
|
622
|
-
|
591
|
+
nanos = record.delete('timeNanos')
|
592
|
+
ts_secs = (nanos / 1_000_000_000).to_i
|
593
|
+
ts_nanos = nanos % 1_000_000_000
|
623
594
|
unless @timenanos_warning
|
624
595
|
# Warn the user this is deprecated, but only once to avoid spam.
|
625
596
|
@timenanos_warning = true
|
@@ -636,47 +607,51 @@ module Fluent
|
|
636
607
|
ts_secs = timestamp.tv_sec
|
637
608
|
ts_nanos = timestamp.tv_nsec
|
638
609
|
end
|
639
|
-
entry
|
640
|
-
|
641
|
-
|
610
|
+
entry.metadata.timestamp = {
|
611
|
+
seconds: ts_secs,
|
612
|
+
nanos: ts_nanos
|
642
613
|
}
|
643
614
|
end
|
644
615
|
|
645
616
|
def set_severity(record, entry)
|
646
617
|
if @service_name == CLOUDFUNCTIONS_SERVICE
|
647
618
|
if @cloudfunctions_log_match && @cloudfunctions_log_match['severity']
|
648
|
-
entry
|
619
|
+
entry.metadata.severity =
|
649
620
|
parse_severity(@cloudfunctions_log_match['severity'])
|
650
621
|
elsif record.key?('stream') && record['stream'] == 'stdout'
|
651
|
-
entry
|
622
|
+
entry.metadata.severity = 'INFO'
|
652
623
|
record.delete('stream')
|
653
624
|
elsif record.key?('stream') && record['stream'] == 'stderr'
|
654
|
-
entry
|
625
|
+
entry.metadata.severity = 'ERROR'
|
655
626
|
record.delete('stream')
|
656
627
|
else
|
657
|
-
entry
|
628
|
+
entry.metadata.severity = 'DEFAULT'
|
658
629
|
end
|
659
630
|
elsif record.key?('severity')
|
660
|
-
entry
|
631
|
+
entry.metadata.severity = parse_severity(record['severity'])
|
661
632
|
record.delete('severity')
|
662
633
|
else
|
663
|
-
entry
|
634
|
+
entry.metadata.severity = 'DEFAULT'
|
664
635
|
end
|
665
636
|
end
|
666
637
|
|
667
638
|
def set_http_request(record, entry)
|
668
|
-
return unless record['httpRequest'].is_a?(Hash)
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
639
|
+
return nil unless record['httpRequest'].is_a?(Hash)
|
640
|
+
input = record['httpRequest']
|
641
|
+
output = Google::Apis::LoggingV1beta3::HttpRequest.new
|
642
|
+
output.request_method = input.delete('requestMethod')
|
643
|
+
output.request_url = input.delete('requestUrl')
|
644
|
+
output.request_size = input.delete('requestSize')
|
645
|
+
output.status = input.delete('status')
|
646
|
+
output.response_size = input.delete('responseSize')
|
647
|
+
output.user_agent = input.delete('userAgent')
|
648
|
+
output.remote_ip = input.delete('remoteIp')
|
649
|
+
output.referer = input.delete('referer')
|
650
|
+
output.cache_hit = input.delete('cacheHit')
|
651
|
+
output.validated_with_origin_server = \
|
652
|
+
input.delete('validatedWithOriginServer')
|
653
|
+
record.delete('httpRequest') if input.empty?
|
654
|
+
entry.http_request = output
|
680
655
|
end
|
681
656
|
|
682
657
|
# Values permitted by the API for 'severity' (which is an enum).
|
@@ -748,13 +723,13 @@ module Fluent
|
|
748
723
|
def handle_container_metadata(record, entry)
|
749
724
|
fields = %w(namespace_id namespace_name pod_id pod_name container_name)
|
750
725
|
fields.each do |field|
|
751
|
-
field_to_label(record['kubernetes'], field, entry
|
726
|
+
field_to_label(record['kubernetes'], field, entry.metadata.labels,
|
752
727
|
"#{CONTAINER_SERVICE}/#{field}")
|
753
728
|
end
|
754
729
|
# Prepend label/ to all user-defined labels' keys.
|
755
730
|
if record['kubernetes'].key?('labels')
|
756
731
|
record['kubernetes']['labels'].each do |key, value|
|
757
|
-
entry
|
732
|
+
entry.metadata.labels["label/#{key}"] = value
|
758
733
|
end
|
759
734
|
end
|
760
735
|
# We've explicitly consumed all the fields we care about -- don't litter
|
@@ -777,20 +752,20 @@ module Fluent
|
|
777
752
|
# 3. This is an unstructured Container log and the 'log' key is available
|
778
753
|
# 4. The only remaining key is 'message'
|
779
754
|
if @service_name == CLOUDFUNCTIONS_SERVICE && @cloudfunctions_log_match
|
780
|
-
entry
|
755
|
+
entry.text_payload = @cloudfunctions_log_match['text']
|
781
756
|
elsif @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
|
782
|
-
entry
|
757
|
+
entry.text_payload = record['log']
|
783
758
|
elsif @service_name == CONTAINER_SERVICE && record.key?('log') &&
|
784
759
|
!is_container_json
|
785
|
-
entry
|
760
|
+
entry.text_payload = record['log']
|
786
761
|
elsif record.size == 1 && record.key?('message')
|
787
|
-
entry
|
762
|
+
entry.text_payload = record['message']
|
788
763
|
else
|
789
|
-
entry
|
764
|
+
entry.struct_payload = record
|
790
765
|
end
|
791
766
|
end
|
792
767
|
|
793
|
-
def log_name(tag,
|
768
|
+
def log_name(tag, common_labels)
|
794
769
|
if @service_name == CLOUDFUNCTIONS_SERVICE
|
795
770
|
return 'cloud-functions'
|
796
771
|
elsif @running_on_managed_vm
|
@@ -800,30 +775,20 @@ module Fluent
|
|
800
775
|
# For Kubernetes logs, use just the container name as the log name
|
801
776
|
# if we have it.
|
802
777
|
container_name_key = "#{CONTAINER_SERVICE}/container_name"
|
803
|
-
if
|
804
|
-
return
|
778
|
+
if common_labels && common_labels.key?(container_name_key)
|
779
|
+
return common_labels[container_name_key]
|
805
780
|
end
|
806
781
|
end
|
807
782
|
tag
|
808
783
|
end
|
809
784
|
|
810
785
|
def init_api_client
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
key = Google::APIClient::PKCS12.load_key(@private_key_path,
|
818
|
-
@private_key_passphrase)
|
819
|
-
jwt_asserter = Google::APIClient::JWTAsserter.new(
|
820
|
-
@private_key_email, LOGGING_SCOPE, key)
|
821
|
-
@client.authorization = jwt_asserter.to_authorization
|
822
|
-
@client.authorization.expiry = 3600 # 3600s is the max allowed value
|
823
|
-
else
|
824
|
-
@client.authorization = Google::Auth.get_application_default(
|
825
|
-
LOGGING_SCOPE)
|
826
|
-
end
|
786
|
+
# TODO: Use a non-default ClientOptions object.
|
787
|
+
Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
|
788
|
+
Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
|
789
|
+
@client = Google::Apis::LoggingV1beta3::LoggingService.new
|
790
|
+
@client.authorization = Google::Auth.get_application_default(
|
791
|
+
LOGGING_SCOPE)
|
827
792
|
end
|
828
793
|
|
829
794
|
def api_client
|
@@ -16,6 +16,7 @@ require 'helper'
|
|
16
16
|
require 'json'
|
17
17
|
require 'mocha/test_unit'
|
18
18
|
require 'webmock/test_unit'
|
19
|
+
require 'google/apis'
|
19
20
|
|
20
21
|
# Unit tests for Google Cloud Logging plugin
|
21
22
|
class GoogleCloudOutputTest < Test::Unit::TestCase
|
@@ -117,9 +118,9 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
117
118
|
|
118
119
|
# rubocop:disable Metrics/LineLength
|
119
120
|
PRIVATE_KEY_CONFIG = %(
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
auth_method private_key
|
122
|
+
private_key_email 271661262351-ft99kc9kjro9rrihq3k2n3s2inbplu0q@developer.gserviceaccount.com
|
123
|
+
private_key_path test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
|
123
124
|
)
|
124
125
|
# rubocop:enable Metrics/LineLength
|
125
126
|
|
@@ -138,14 +139,6 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
138
139
|
vm_name #{CUSTOM_HOSTNAME}
|
139
140
|
)
|
140
141
|
|
141
|
-
CONFIG_MISSING_PRIVATE_KEY_PATH = %(
|
142
|
-
auth_method private_key
|
143
|
-
private_key_email nobody@example.com
|
144
|
-
)
|
145
|
-
CONFIG_MISSING_PRIVATE_KEY_EMAIL = %(
|
146
|
-
auth_method private_key
|
147
|
-
private_key_path /fake/path/to/key
|
148
|
-
)
|
149
142
|
CONFIG_MISSING_METADATA_PROJECT_ID = %(
|
150
143
|
zone #{CUSTOM_ZONE}
|
151
144
|
vm_id #{CUSTOM_VM_ID}
|
@@ -331,43 +324,28 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
331
324
|
def test_configure_service_account_application_default
|
332
325
|
setup_gce_metadata_stubs
|
333
326
|
d = create_driver(APPLICATION_DEFAULT_CONFIG)
|
334
|
-
|
327
|
+
assert_equal HOSTNAME, d.instance.vm_name
|
335
328
|
end
|
336
329
|
|
337
330
|
def test_configure_service_account_private_key
|
331
|
+
# Using out-of-date config method.
|
338
332
|
setup_gce_metadata_stubs
|
339
|
-
d = create_driver(PRIVATE_KEY_CONFIG)
|
340
|
-
assert_equal 'private_key', d.instance.auth_method
|
341
|
-
end
|
342
|
-
|
343
|
-
def test_configure_custom_metadata
|
344
|
-
setup_no_metadata_service_stubs
|
345
|
-
d = create_driver(CUSTOM_METADATA_CONFIG)
|
346
|
-
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
347
|
-
assert_equal CUSTOM_ZONE, d.instance.zone
|
348
|
-
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
349
|
-
end
|
350
|
-
|
351
|
-
def test_configure_invalid_private_key_missing_path
|
352
333
|
exception_count = 0
|
353
334
|
begin
|
354
|
-
_d = create_driver(
|
335
|
+
_d = create_driver(PRIVATE_KEY_CONFIG)
|
355
336
|
rescue Fluent::ConfigError => error
|
356
|
-
assert error.message.include? '
|
337
|
+
assert error.message.include? 'Please remove configuration parameters'
|
357
338
|
exception_count += 1
|
358
339
|
end
|
359
340
|
assert_equal 1, exception_count
|
360
341
|
end
|
361
342
|
|
362
|
-
def
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
exception_count += 1
|
369
|
-
end
|
370
|
-
assert_equal 1, exception_count
|
343
|
+
def test_configure_custom_metadata
|
344
|
+
setup_no_metadata_service_stubs
|
345
|
+
d = create_driver(CUSTOM_METADATA_CONFIG)
|
346
|
+
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
347
|
+
assert_equal CUSTOM_ZONE, d.instance.zone
|
348
|
+
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
371
349
|
end
|
372
350
|
|
373
351
|
def test_configure_invalid_metadata_missing_project_id_no_metadata_service
|
@@ -569,15 +547,6 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
569
547
|
assert_equal 1, exception_count
|
570
548
|
end
|
571
549
|
|
572
|
-
def test_one_log_private_key
|
573
|
-
setup_gce_metadata_stubs
|
574
|
-
setup_logging_stubs
|
575
|
-
d = create_driver(PRIVATE_KEY_CONFIG)
|
576
|
-
d.emit('message' => log_entry(0))
|
577
|
-
d.run
|
578
|
-
verify_log_entries(1, COMPUTE_PARAMS)
|
579
|
-
end
|
580
|
-
|
581
550
|
def test_one_log_custom_metadata
|
582
551
|
# don't set up any metadata stubs, so the test will fail if we try to
|
583
552
|
# fetch metadata (and explicitly check this as well).
|
@@ -795,7 +764,7 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
795
764
|
assert @logs_sent.empty?
|
796
765
|
end
|
797
766
|
|
798
|
-
def
|
767
|
+
def test_client_400
|
799
768
|
setup_gce_metadata_stubs
|
800
769
|
# The API Client should not retry this and the plugin should consume
|
801
770
|
# the exception.
|
@@ -807,43 +776,19 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
807
776
|
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 1)
|
808
777
|
end
|
809
778
|
|
810
|
-
#
|
811
|
-
def
|
779
|
+
# All credentials errors resolve to a 401.
|
780
|
+
def test_client_401
|
812
781
|
setup_gce_metadata_stubs
|
813
782
|
stub_request(:post, uri_for_log(COMPUTE_PARAMS))
|
814
|
-
.to_return(status: 401, body:
|
783
|
+
.to_return(status: 401, body: 'Unauthorized')
|
815
784
|
d = create_driver
|
816
785
|
d.emit('message' => log_entry(0))
|
817
|
-
exception_count = 0
|
818
786
|
begin
|
819
787
|
d.run
|
820
|
-
rescue Google::
|
821
|
-
assert_equal
|
822
|
-
exception_count += 1
|
788
|
+
rescue Google::Apis::AuthorizationError => error
|
789
|
+
assert_equal 'Unauthorized', error.message
|
823
790
|
end
|
824
791
|
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 2)
|
825
|
-
assert_equal 1, exception_count
|
826
|
-
end
|
827
|
-
|
828
|
-
def test_client_error_invalid_credentials
|
829
|
-
client_error_helper('Invalid Credentials')
|
830
|
-
end
|
831
|
-
|
832
|
-
def test_client_error_caller_does_not_have_permission
|
833
|
-
client_error_helper('The caller does not have permission')
|
834
|
-
end
|
835
|
-
|
836
|
-
def test_client_error_request_had_invalid_credentials
|
837
|
-
client_error_helper('Request had invalid credentials.')
|
838
|
-
end
|
839
|
-
|
840
|
-
def test_client_error_project_has_not_enabled_the_api
|
841
|
-
client_error_helper('Project has not enabled the API. Please use ' \
|
842
|
-
'Google Developers Console to activate the API for your project.')
|
843
|
-
end
|
844
|
-
|
845
|
-
def test_client_error_unable_to_fetch_accesss_token
|
846
|
-
client_error_helper('Unable to fetch access token (no scopes configured?)')
|
847
792
|
end
|
848
793
|
|
849
794
|
def test_server_error
|
@@ -857,11 +802,11 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
857
802
|
exception_count = 0
|
858
803
|
begin
|
859
804
|
d.run
|
860
|
-
rescue Google::
|
861
|
-
assert_equal 'Server
|
805
|
+
rescue Google::Apis::ServerError => error
|
806
|
+
assert_equal 'Server error', error.message
|
862
807
|
exception_count += 1
|
863
808
|
end
|
864
|
-
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times:
|
809
|
+
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 1)
|
865
810
|
assert_equal 1, exception_count
|
866
811
|
end
|
867
812
|
|
@@ -1266,14 +1211,6 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
1266
1211
|
status: 200,
|
1267
1212
|
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1268
1213
|
'Content-Type' => 'application/json' })
|
1269
|
-
|
1270
|
-
# Used for 'private_key' auth.
|
1271
|
-
stub_request(:post, 'https://accounts.google.com/o/oauth2/token')
|
1272
|
-
.with(body: hash_including(grant_type: AUTH_GRANT_TYPE))
|
1273
|
-
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1274
|
-
status: 200,
|
1275
|
-
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1276
|
-
'Content-Type' => 'application/json' })
|
1277
1214
|
end
|
1278
1215
|
|
1279
1216
|
def setup_managed_vm_metadata_stubs
|
@@ -1376,7 +1313,7 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
1376
1313
|
assert entry.key?(payload_type), 'Entry did not contain expected ' \
|
1377
1314
|
"#{payload_type} key: " + entry.to_s
|
1378
1315
|
# Check the payload for textPayload, otherwise it's up to the caller.
|
1379
|
-
if
|
1316
|
+
if payload_type == 'textPayload'
|
1380
1317
|
assert_equal "test log entry #{i}", entry['textPayload'], batch
|
1381
1318
|
end
|
1382
1319
|
end
|
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.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Todd Derr
|
@@ -29,20 +29,14 @@ dependencies:
|
|
29
29
|
name: google-api-client
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- - "
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: 0.8.6
|
35
|
-
- - "<"
|
32
|
+
- - ">"
|
36
33
|
- !ruby/object:Gem::Version
|
37
34
|
version: '0.9'
|
38
35
|
type: :runtime
|
39
36
|
prerelease: false
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
41
38
|
requirements:
|
42
|
-
- - "
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
version: 0.8.6
|
45
|
-
- - "<"
|
39
|
+
- - ">"
|
46
40
|
- !ruby/object:Gem::Version
|
47
41
|
version: '0.9'
|
48
42
|
- !ruby/object:Gem::Dependency
|
@@ -73,20 +67,6 @@ dependencies:
|
|
73
67
|
- - "~>"
|
74
68
|
- !ruby/object:Gem::Version
|
75
69
|
version: '1.8'
|
76
|
-
- !ruby/object:Gem::Dependency
|
77
|
-
name: jwt
|
78
|
-
requirement: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "<"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 1.5.3
|
83
|
-
type: :runtime
|
84
|
-
prerelease: false
|
85
|
-
version_requirements: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "<"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 1.5.3
|
90
70
|
- !ruby/object:Gem::Dependency
|
91
71
|
name: mocha
|
92
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,14 +101,14 @@ dependencies:
|
|
121
101
|
requirements:
|
122
102
|
- - '='
|
123
103
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
104
|
+
version: 0.35.0
|
125
105
|
type: :development
|
126
106
|
prerelease: false
|
127
107
|
version_requirements: !ruby/object:Gem::Requirement
|
128
108
|
requirements:
|
129
109
|
- - '='
|
130
110
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
111
|
+
version: 0.35.0
|
132
112
|
- !ruby/object:Gem::Dependency
|
133
113
|
name: webmock
|
134
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -194,7 +174,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
174
|
requirements:
|
195
175
|
- - ">="
|
196
176
|
- !ruby/object:Gem::Version
|
197
|
-
version: '0'
|
177
|
+
version: '2.0'
|
198
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
179
|
requirements:
|
200
180
|
- - ">="
|