fluent-plugin-google-cloud 0.6.13 → 0.6.14

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: 9d51deb1fd15ce338a6165a33e853704f7244051
4
- data.tar.gz: 111bc6001f81947c1fd46e50a1d138487d864bc6
3
+ metadata.gz: f370253a680adefd979f61cee26d87eedb36e3f8
4
+ data.tar.gz: bfd12ef42e11ce1cd51bee4bf356f9014ad07a67
5
5
  SHA512:
6
- metadata.gz: 7490512b67ea3bcdd2dcf60c12e29f8a2aa2f7232532a5d7f5a3e878c15913122bb7ee03b8c82b352d64b3224a90d900af3e2db94415ab694d5dce08eddd0193
7
- data.tar.gz: 63e06d74b7148efc7c2ec31272ac15a999b921556ac08ef8f46807b110c22de0609f9a17573bb850a8ab2d6da7f88f90db2a1f6d9adb0cb64afd9ad70f07930b
6
+ metadata.gz: 671183d082caca469f7ded93eb737d07658beed0cce472806efeac2f303e810bd86049461a5ec5daef1d6d68436355bc4af81056ca0ee0c30a10f2a0047d86b9
7
+ data.tar.gz: d99f68afa553f93a280d0c648a24565c16bc1449af6d045cb31efc666e98c6c2d3406c1cf6879d77957a0513906fb87eb53a67c6133d37df523b81652aba87d9
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.6.13)
4
+ fluent-plugin-google-cloud (0.6.14)
5
5
  fluentd (~> 0.10)
6
- google-api-client (~> 0.14)
7
- google-cloud-logging (~> 1.2.3)
6
+ google-api-client (~> 0.17)
7
+ google-cloud-logging (~> 1.3, >= 1.3.2)
8
8
  googleapis-common-protos (~> 1.3)
9
- googleauth (~> 0.5)
10
- grpc (~> 1.2.5)
9
+ googleauth (~> 0.6)
10
+ grpc (~> 1.0)
11
11
  json (~> 1.8)
12
12
 
13
13
  GEM
@@ -15,13 +15,13 @@ GEM
15
15
  specs:
16
16
  addressable (2.5.2)
17
17
  public_suffix (>= 2.0.2, < 4.0)
18
- ast (2.3.0)
18
+ ast (2.4.0)
19
19
  cool.io (1.5.3)
20
20
  crack (0.4.3)
21
21
  safe_yaml (~> 1.0.0)
22
22
  declarative (0.0.10)
23
23
  declarative-option (0.1.0)
24
- faraday (0.13.1)
24
+ faraday (0.14.0)
25
25
  multipart-post (>= 1.2, < 3)
26
26
  fluentd (0.14.25)
27
27
  cool.io (>= 1.4.5, < 2.0.0)
@@ -34,7 +34,7 @@ GEM
34
34
  tzinfo (~> 1.0)
35
35
  tzinfo-data (~> 1.0)
36
36
  yajl-ruby (~> 1.0)
37
- google-api-client (0.19.3)
37
+ google-api-client (0.19.4)
38
38
  addressable (~> 2.5, >= 2.5.1)
39
39
  googleauth (>= 0.5, < 0.7.0)
40
40
  httpclient (>= 2.8.1, < 3.0)
@@ -45,15 +45,15 @@ GEM
45
45
  google-cloud-env (~> 1.0)
46
46
  google-cloud-env (1.0.1)
47
47
  faraday (~> 0.11)
48
- google-cloud-logging (1.2.3)
49
- google-cloud-core (~> 1.0)
50
- google-gax (~> 0.8.0)
48
+ google-cloud-logging (1.4.0)
49
+ google-cloud-core (~> 1.1)
50
+ google-gax (~> 1.0)
51
51
  stackdriver-core (~> 1.2)
52
- google-gax (0.8.10)
52
+ google-gax (1.0.1)
53
53
  google-protobuf (~> 3.2)
54
- googleapis-common-protos (~> 1.3.5)
55
- googleauth (~> 0.5.1)
56
- grpc (~> 1.0)
54
+ googleapis-common-protos (>= 1.3.5, < 2.0)
55
+ googleauth (~> 0.6.2)
56
+ grpc (>= 1.7.2, < 2.0)
57
57
  rly (~> 0.2.3)
58
58
  google-protobuf (3.5.1.1)
59
59
  googleapis-common-protos (1.3.7)
@@ -62,22 +62,23 @@ GEM
62
62
  grpc (~> 1.0)
63
63
  googleapis-common-protos-types (1.0.1)
64
64
  google-protobuf (~> 3.0)
65
- googleauth (0.5.3)
65
+ googleauth (0.6.2)
66
66
  faraday (~> 0.12)
67
- jwt (~> 1.4)
67
+ jwt (>= 1.4, < 3.0)
68
68
  logging (~> 2.0)
69
69
  memoist (~> 0.12)
70
70
  multi_json (~> 1.11)
71
71
  os (~> 0.9)
72
72
  signet (~> 0.7)
73
- grpc (1.2.5)
73
+ grpc (1.8.3)
74
74
  google-protobuf (~> 3.1)
75
- googleauth (~> 0.5.1)
75
+ googleapis-common-protos-types (~> 1.0.0)
76
+ googleauth (>= 0.5.1, < 0.7)
76
77
  hashdiff (0.3.7)
77
78
  http_parser.rb (0.6.0)
78
79
  httpclient (2.8.3)
79
80
  json (1.8.6)
80
- jwt (1.5.6)
81
+ jwt (2.1.0)
81
82
  little-plugger (1.1.4)
82
83
  logging (2.2.2)
83
84
  little-plugger (~> 1.1)
@@ -134,7 +135,7 @@ GEM
134
135
  thread_safe (0.3.6)
135
136
  tzinfo (1.2.4)
136
137
  thread_safe (~> 0.1)
137
- tzinfo-data (1.2017.3)
138
+ tzinfo-data (1.2018.3)
138
139
  tzinfo (>= 1.0.0)
139
140
  uber (0.1.0)
140
141
  unicode-display_width (1.3.0)
@@ -10,7 +10,7 @@ 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.6.13'
13
+ gem.version = '0.6.14'
14
14
  gem.authors = ['Todd Derr', 'Alex Robinson']
15
15
  gem.email = ['salty@google.com']
16
16
  gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
@@ -21,10 +21,10 @@ eos
21
21
 
22
22
  gem.add_runtime_dependency 'fluentd', '~> 0.10'
23
23
  gem.add_runtime_dependency 'googleapis-common-protos', '~> 1.3'
24
- gem.add_runtime_dependency 'google-api-client', '~> 0.14'
25
- gem.add_runtime_dependency 'google-cloud-logging', '~> 1.2.3'
26
- gem.add_runtime_dependency 'googleauth', '~> 0.5'
27
- gem.add_runtime_dependency 'grpc', '~> 1.2.5'
24
+ gem.add_runtime_dependency 'google-api-client', '~> 0.17'
25
+ gem.add_runtime_dependency 'google-cloud-logging', '~> 1.3', '>= 1.3.2'
26
+ gem.add_runtime_dependency 'googleauth', '~> 0.6'
27
+ gem.add_runtime_dependency 'grpc', '~> 1.0'
28
28
  gem.add_runtime_dependency 'json', '~> 1.8'
29
29
 
30
30
  gem.add_development_dependency 'mocha', '~> 1.1'
@@ -120,6 +120,7 @@ module Fluent
120
120
  DEFAULT_SOURCE_LOCATION_KEY =
121
121
  'logging.googleapis.com/sourceLocation'.freeze
122
122
  DEFAULT_TRACE_KEY = 'logging.googleapis.com/trace'.freeze
123
+ DEFAULT_SPAN_ID_KEY = 'logging.googleapis.com/spanId'.freeze
123
124
 
124
125
  DEFAULT_METADATA_AGENT_URL =
125
126
  'http://local-metadata-agent.stackdriver.com:8000'.freeze
@@ -127,6 +128,8 @@ module Fluent
127
128
 
128
129
  # Internal constants.
129
130
  module InternalConstants
131
+ DEFAULT_LOGGING_API_URL = 'https://logging.googleapis.com'.freeze
132
+
130
133
  # The label name of local_resource_id in the json payload. When a record
131
134
  # has this field in the payload, we will use the value to retrieve
132
135
  # monitored resource from Stackdriver Metadata agent.
@@ -195,7 +198,7 @@ module Fluent
195
198
  Fluent::Plugin.register_output('google_cloud', self)
196
199
 
197
200
  PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'.freeze
198
- PLUGIN_VERSION = '0.6.13'.freeze
201
+ PLUGIN_VERSION = '0.6.14'.freeze
199
202
 
200
203
  # Name of the the Google cloud logging write scope.
201
204
  LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
@@ -219,6 +222,9 @@ module Fluent
219
222
  # Whether to attempt to obtain metadata from the local metadata service.
220
223
  # It is safe to specify 'true' even on platforms with no metadata service.
221
224
  config_param :use_metadata_service, :bool, :default => true
225
+ # A compatibility option to enable the legacy behavior of setting the AWS
226
+ # location to the availability zone rather than the region.
227
+ config_param :use_aws_availability_zone, :bool, :default => true
222
228
  # These parameters override any values obtained from the metadata service.
223
229
  config_param :project_id, :string, :default => nil
224
230
  config_param :zone, :string, :default => nil
@@ -232,6 +238,7 @@ module Fluent
232
238
  config_param :source_location_key, :string, :default =>
233
239
  DEFAULT_SOURCE_LOCATION_KEY
234
240
  config_param :trace_key, :string, :default => DEFAULT_TRACE_KEY
241
+ config_param :span_id_key, :string, :default => DEFAULT_SPAN_ID_KEY
235
242
 
236
243
  # Whether to try to detect if the record is a text log entry with JSON
237
244
  # content that needs to be parsed.
@@ -323,6 +330,12 @@ module Fluent
323
330
  :default => nil,
324
331
  :secret => true
325
332
 
333
+ # The URL of Stackdriver Logging API. Right now this only works with the
334
+ # gRPC path (use_grpc = true). An unsecured channel is used if the URL
335
+ # scheme is 'http' instead of 'https'. One common use case of this config is
336
+ # to provide a mocked / stubbed Logging API, e.g., http://localhost:52000.
337
+ config_param :logging_api_url, :string, :default => DEFAULT_LOGGING_API_URL
338
+
326
339
  # Whether to collect metrics about the plugin usage. The mechanism for
327
340
  # collecting and exposing metrics is controlled by the monitoring_type
328
341
  # parameter.
@@ -368,11 +381,11 @@ module Fluent
368
381
 
369
382
  # TODO(qingling128): Remove this warning after the support is added. Also
370
383
  # remove the comment in the description of this configuration.
371
- if @partial_success && @use_grpc
372
- @log.warn 'Detected partial_success enabled while use_grpc is also' \
373
- ' enabled. The support for partial success in the gRPC path' \
374
- ' is to be added in the near future. For now the ' \
375
- ' partial_success flag will be ignored.'
384
+ unless @logging_api_url == DEFAULT_LOGGING_API_URL || @use_grpc
385
+ @log.warn 'Detected customized logging_api_url while use_grpc is not' \
386
+ ' enabled. Customized logging_api_url for the non-gRPC path' \
387
+ ' is not supported. The logging_api_url option will be' \
388
+ ' ignored.'
376
389
  end
377
390
 
378
391
  # If monitoring is enabled, register metrics in the default registry
@@ -397,7 +410,7 @@ module Fluent
397
410
  'The number of log entries that failed to be ingested by the'\
398
411
  ' Stackdriver output plugin due to a transient error and were'\
399
412
  ' retried')
400
- @ok_code = @use_grpc ? 0 : 200
413
+ @ok_code = @use_grpc ? GRPC::Core::StatusCodes::OK : 200
401
414
  end
402
415
 
403
416
  # Alert on old authentication configuration.
@@ -540,6 +553,9 @@ module Fluent
540
553
  fq_trace_id = record.delete(@trace_key)
541
554
  entry.trace = fq_trace_id if fq_trace_id
542
555
 
556
+ span_id = record.delete(@span_id_key)
557
+ entry.span_id = span_id if span_id
558
+
543
559
  set_log_entry_fields(record, entry)
544
560
  set_payload(entry_level_resource.type, record, entry, is_json)
545
561
 
@@ -634,7 +650,6 @@ module Fluent
634
650
  client = api_client
635
651
  entries_count = entries.length
636
652
  client.write_log_entries(
637
- # Ignore partial_success for gRPC path.
638
653
  entries,
639
654
  log_name: log_name,
640
655
  # Leave resource nil if it's nil.
@@ -646,7 +661,8 @@ module Fluent
646
661
  end,
647
662
  labels: labels.map do |k, v|
648
663
  [k.encode('utf-8'), convert_to_utf8(v)]
649
- end.to_h
664
+ end.to_h,
665
+ partial_success: @partial_success
650
666
  )
651
667
  increment_successful_requests_count
652
668
  increment_ingested_entries_count(entries_count)
@@ -683,14 +699,10 @@ module Fluent
683
699
  # Most client errors indicate a problem with the request itself and
684
700
  # should not be retried.
685
701
  when \
686
- # HTTP status 400 (Bad Request).
687
- GRPC::InvalidArgument,
688
702
  # HTTP status 401 (Unauthorized).
689
- # These are usually solved via a `gcloud auth` call, or by
690
- # modifying the permissions on the Google Cloud project.
703
+ # These are usually solved via a `gcloud auth` call, or by modifying
704
+ # the permissions on the Google Cloud project.
691
705
  GRPC::Unauthenticated,
692
- # HTTP status 403 (Forbidden).
693
- GRPC::PermissionDenied,
694
706
  # HTTP status 404 (Not Found).
695
707
  GRPC::NotFound,
696
708
  # HTTP status 409 (Conflict).
@@ -711,6 +723,33 @@ module Fluent
711
723
  @log.warn "Dropping #{entries_count} log message(s)",
712
724
  error: error.to_s, error_code: error.code.to_s
713
725
 
726
+ # If partial_success is enabled, valid entries should have be
727
+ # written even if some other entries fail due to InvalidArgument or
728
+ # PermissionDenied errors. Only invalid entries will be dropped.
729
+ when \
730
+ # HTTP status 400 (Bad Request).
731
+ GRPC::InvalidArgument,
732
+ # HTTP status 403 (Forbidden).
733
+ GRPC::PermissionDenied
734
+ error_details_map = construct_error_details_map_grpc(gax_error)
735
+ if error_details_map.empty?
736
+ increment_dropped_entries_count(entries_count, error.code)
737
+ @log.warn "Dropping #{entries_count} log message(s)",
738
+ error: error.to_s, error_code: error.code.to_s
739
+ else
740
+ error_details_map.each do |(error_code, error_message), indexes|
741
+ partial_errors_count = indexes.length
742
+ increment_dropped_entries_count(partial_errors_count,
743
+ error_code)
744
+ entries_count -= partial_errors_count
745
+ @log.warn "Dropping #{partial_errors_count} log message(s)",
746
+ error: error_message, error_code: error_code.to_s
747
+ end
748
+ # Consider partially successful requests successful.
749
+ increment_successful_requests_count
750
+ increment_ingested_entries_count(entries_count)
751
+ end
752
+
714
753
  else
715
754
  # Assume it's a problem with the request itself and don't retry.
716
755
  increment_failed_requests_count(error.code)
@@ -771,7 +810,7 @@ module Fluent
771
810
  increment_failed_requests_count(error.status_code)
772
811
  increment_dropped_entries_count(entries_count, error.status_code)
773
812
  @log.warn "Dropping #{entries_count} log message(s)",
774
- error_class: error.class.to_s, error: error.to_s
813
+ error: error.to_s, error_code: error.status_code.to_s
775
814
 
776
815
  rescue Google::Apis::ClientError => error
777
816
  # 4xx client errors. Most client errors indicate a problem with the
@@ -781,15 +820,15 @@ module Fluent
781
820
  increment_failed_requests_count(error.status_code)
782
821
  increment_dropped_entries_count(entries_count, error.status_code)
783
822
  @log.warn "Dropping #{entries_count} log message(s)",
784
- error_class: error.class.to_s, error: error.to_s
823
+ error: error.to_s, error_code: error.status_code.to_s
785
824
  else
786
825
  error_details_map.each do |(error_code, error_message), indexes|
787
- partial_error_count = indexes.length
788
- increment_dropped_entries_count(partial_error_count, error_code)
789
- entries_count -= partial_error_count
790
- @log.warn "Dropping #{partial_error_count} log message(s)",
791
- error_code: "google.rpc.Code[#{error_code}]",
792
- error: error_message
826
+ partial_errors_count = indexes.length
827
+ increment_dropped_entries_count(partial_errors_count, error_code)
828
+ entries_count -= partial_errors_count
829
+ @log.warn "Dropping #{partial_errors_count} log message(s)",
830
+ error: error_message,
831
+ error_code: "google.rpc.Code[#{error_code}]"
793
832
  end
794
833
  # Consider partially successful requests successful.
795
834
  increment_successful_requests_count
@@ -945,8 +984,13 @@ module Fluent
945
984
  # Response format: "projects/<number>/zones/<zone>"
946
985
  @zone ||= fetch_gce_metadata('instance/zone').rpartition('/')[2] if
947
986
  @platform == Platform::GCE
948
- @zone ||= 'aws:' + ec2_metadata['availabilityZone'] if
949
- @platform == Platform::EC2 && ec2_metadata.key?('availabilityZone')
987
+ aws_location_key = if @use_aws_availability_zone
988
+ 'availabilityZone'
989
+ else
990
+ 'region'
991
+ end
992
+ @zone ||= 'aws:' + ec2_metadata[aws_location_key] if
993
+ @platform == Platform::EC2 && ec2_metadata.key?(aws_location_key)
950
994
  rescue StandardError => e
951
995
  @log.error 'Failed to obtain location: ', error: e
952
996
  end
@@ -1828,13 +1872,24 @@ module Fluent
1828
1872
 
1829
1873
  def init_api_client
1830
1874
  if @use_grpc
1831
- ssl_creds = GRPC::Core::ChannelCredentials.new
1832
- authentication = Google::Auth.get_application_default
1833
- creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
1834
- creds = ssl_creds.compose(creds)
1875
+ uri = URI.parse(@logging_api_url)
1876
+ host = uri.host
1877
+ unless host
1878
+ raise Fluent::ConfigError,
1879
+ 'The logging_api_url option specifies an invalid URL:' \
1880
+ " #{@logging_api_url}."
1881
+ end
1882
+ if uri.scheme == 'https'
1883
+ ssl_creds = GRPC::Core::ChannelCredentials.new
1884
+ authentication = Google::Auth.get_application_default
1885
+ creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
1886
+ creds = ssl_creds.compose(creds)
1887
+ else
1888
+ creds = :this_channel_is_insecure
1889
+ end
1890
+ port = ":#{uri.port}" if uri.port
1835
1891
  @client = Google::Cloud::Logging::V2::LoggingServiceV2Client.new(
1836
- channel: GRPC::Core::Channel.new(
1837
- 'logging.googleapis.com', nil, creds))
1892
+ credentials: GRPC::Core::Channel.new("#{host}#{port}", nil, creds))
1838
1893
  else
1839
1894
  # TODO: Use a non-default ClientOptions object.
1840
1895
  Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
@@ -1885,8 +1940,8 @@ module Fluent
1885
1940
  end
1886
1941
  end
1887
1942
 
1888
- # Extract a map of error details from an potentially partially successful
1889
- # request. Return an empty map if @partial_success is not enabled.
1943
+ # Extract a map of error details from a potentially partially successful
1944
+ # REST request. Return an empty map if @partial_success is not enabled.
1890
1945
  #
1891
1946
  # The keys in this map are [error_code, error_message] pairs, and the values
1892
1947
  # are a list of stringified indexes of log entries that failed due to this
@@ -1989,6 +2044,65 @@ module Fluent
1989
2044
  {}
1990
2045
  end
1991
2046
 
2047
+ # Extract a map of error details from a potentially partially successful
2048
+ # gRPC request. Return an empty map if @partial_success is not enabled.
2049
+ #
2050
+ # The keys in this map are [error_code, error_message] pairs, and the values
2051
+ # are a list of indexes of log entries that failed due to this error.
2052
+ #
2053
+ # A sample error looks like:
2054
+ # <Google::Gax::RetryError:
2055
+ # message: 'GaxError Exception occurred in retry method that was not class
2056
+ # ified as transient, caused by 7:User not authorized.',
2057
+ # details: [
2058
+ # <Google::Logging::V2::WriteLogEntriesPartialErrors:
2059
+ # log_entry_errors: {
2060
+ # 0 => <Google::Rpc::Status:
2061
+ # code: 7,
2062
+ # message: "User not authorized.",
2063
+ # details: []>,
2064
+ # 1 => <Google::Rpc::Status:
2065
+ # code: 3,
2066
+ # message: "Log name contains illegal character :",
2067
+ # details: []>,
2068
+ # 3 => <Google::Rpc::Status:
2069
+ # code: 3,
2070
+ # message: "Log name contains illegal character :",
2071
+ # details: []>
2072
+ # }
2073
+ # >,
2074
+ # <Google::Rpc::DebugInfo:
2075
+ # stack_entries: [],
2076
+ # detail: "..."
2077
+ # >
2078
+ # ]
2079
+ # cause: <GRPC::PermissionDenied: 7:User not authorized.>
2080
+ # }
2081
+ #
2082
+ # The ultimate map that is constructed is:
2083
+ # {
2084
+ # [7, 'User not authorized.']: [0],
2085
+ # [3, 'Log name contains illegal character :']: [1, 3]
2086
+ # }
2087
+ def construct_error_details_map_grpc(gax_error)
2088
+ return {} unless @partial_success
2089
+ error_details_map = Hash.new { |h, k| h[k] = [] }
2090
+
2091
+ error_details = ensure_array(gax_error.status_details)
2092
+ raise JSON::ParserError, 'The error details are empty.' if
2093
+ error_details.empty?
2094
+ log_entry_errors = ensure_hash(error_details[0].log_entry_errors)
2095
+ log_entry_errors.each do |index, log_entry_error|
2096
+ error_key = [log_entry_error[:code], log_entry_error[:message]].freeze
2097
+ error_details_map[error_key] << index
2098
+ end
2099
+ error_details_map
2100
+ rescue JSON::ParserError => e
2101
+ @log.warn 'Failed to extract log entry errors from the error details:' \
2102
+ " #{gax_error.details.inspect}.", error: e
2103
+ {}
2104
+ end
2105
+
1992
2106
  def ensure_array(value)
1993
2107
  Array.try_convert(value) || (raise JSON::ParserError, value.class.to_s)
1994
2108
  end
@@ -62,6 +62,17 @@ module BaseTest
62
62
  assert_equal 1, exception_count
63
63
  end
64
64
 
65
+ def test_configure_logging_api_url
66
+ setup_gce_metadata_stubs
67
+ {
68
+ APPLICATION_DEFAULT_CONFIG => DEFAULT_LOGGING_API_URL,
69
+ CUSTOM_LOGGING_API_URL_CONFIG => CUSTOM_LOGGING_API_URL
70
+ }.each do |(config, url)|
71
+ d = create_driver(config)
72
+ assert_equal url, d.instance.instance_variable_get(:@logging_api_url)
73
+ end
74
+ end
75
+
65
76
  def test_configure_custom_metadata
66
77
  setup_no_metadata_service_stubs
67
78
  d = create_driver(CUSTOM_METADATA_CONFIG)
@@ -163,7 +174,9 @@ module BaseTest
163
174
  CONFIG_EC2_PROJECT_ID =>
164
175
  ['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, EC2_VM_ID],
165
176
  CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID =>
166
- ['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, CUSTOM_VM_ID]
177
+ ['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, CUSTOM_VM_ID],
178
+ CONFIG_EC2_PROJECT_ID_USE_REGION =>
179
+ ['ec2', EC2_PROJECT_ID, EC2_PREFIXED_REGION, EC2_VM_ID]
167
180
  }.each_with_index do |(config, parts), index|
168
181
  send("setup_#{parts[0]}_metadata_stubs")
169
182
  d = create_driver(config)
@@ -257,7 +270,18 @@ module BaseTest
257
270
  d.emit('message' => log_entry(0))
258
271
  d.run
259
272
  end
260
- verify_log_entries(1, EC2_PARAMS)
273
+ verify_log_entries(1, EC2_ZONE_PARAMS)
274
+ end
275
+
276
+ def test_one_log_ec2_region
277
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
278
+ setup_ec2_metadata_stubs
279
+ setup_logging_stubs do
280
+ d = create_driver(CONFIG_EC2_PROJECT_ID_USE_REGION)
281
+ d.emit('message' => log_entry(0))
282
+ d.run
283
+ end
284
+ verify_log_entries(1, EC2_REGION_PARAMS)
261
285
  end
262
286
 
263
287
  def test_structured_payload_log
@@ -1154,55 +1178,14 @@ module BaseTest
1154
1178
  end
1155
1179
 
1156
1180
  def test_log_entry_trace_field
1157
- setup_gce_metadata_stubs
1158
- message = log_entry(0)
1159
- trace = 'projects/project-1/traces/1234567890abcdef1234567890abcdef'
1160
- [
1161
- {
1162
- # It leaves trace entry field nil if no trace value sent.
1163
- driver_config: APPLICATION_DEFAULT_CONFIG,
1164
- emitted_log: { 'msg' => message },
1165
- expected_fields: { 'msg' => message },
1166
- expected_trace_value: nil
1167
- },
1168
- {
1169
- # By default, it sets trace via Google-specific key.
1170
- driver_config: APPLICATION_DEFAULT_CONFIG,
1171
- emitted_log: { 'msg' => message, DEFAULT_TRACE_KEY => trace },
1172
- expected_fields: { 'msg' => message },
1173
- expected_trace_value: trace
1174
- },
1175
- {
1176
- # It allows setting the trace via a custom configured key.
1177
- driver_config: CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
1178
- emitted_log: { 'msg' => message, 'custom_trace_key' => trace },
1179
- expected_fields: { 'msg' => message },
1180
- expected_trace_value: trace
1181
- },
1182
- {
1183
- # It no longer sets trace by the default key if custom key specified.
1184
- driver_config: CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
1185
- emitted_log: { 'msg' => message, DEFAULT_TRACE_KEY => trace },
1186
- expected_fields: { 'msg' => message, DEFAULT_TRACE_KEY => trace },
1187
- expected_trace_value: nil
1188
- }
1189
- ].each do |input|
1190
- setup_logging_stubs do
1191
- @logs_sent = []
1192
- d = create_driver(input[:driver_config])
1193
- d.emit(input[:emitted_log])
1194
- d.run
1195
- end
1196
- verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
1197
- assert_equal input[:expected_trace_value], entry['trace'], input
1181
+ verify_field_key('trace', DEFAULT_TRACE_KEY, 'custom_trace_key',
1182
+ CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
1183
+ 'projects/proj1/traces/1234567890abcdef1234567890abcdef')
1184
+ end
1198
1185
 
1199
- fields = get_fields(entry['jsonPayload'])
1200
- assert_equal input[:expected_fields].size, fields.size, input
1201
- fields.each do |key, value|
1202
- assert_equal input[:expected_fields][key], get_string(value), input
1203
- end
1204
- end
1205
- end
1186
+ def test_log_entry_span_id_field
1187
+ verify_field_key('spanId', DEFAULT_SPAN_ID_KEY, 'custom_span_id_key',
1188
+ CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED, '000000000000004a')
1206
1189
  end
1207
1190
 
1208
1191
  # Metadata Agent related tests.
@@ -1765,6 +1748,57 @@ module BaseTest
1765
1748
  end
1766
1749
  end
1767
1750
 
1751
+ def verify_field_key(log_entry_field, default_key, custom_key,
1752
+ custom_key_config, sample_value)
1753
+ setup_gce_metadata_stubs
1754
+ message = log_entry(0)
1755
+ [
1756
+ {
1757
+ # It leaves log entry field nil if no keyed value sent.
1758
+ driver_config: APPLICATION_DEFAULT_CONFIG,
1759
+ emitted_log: { 'msg' => message },
1760
+ expected_payload: { 'msg' => message },
1761
+ expected_field_value: nil
1762
+ },
1763
+ {
1764
+ # By default, it sets log entry field via a default key.
1765
+ driver_config: APPLICATION_DEFAULT_CONFIG,
1766
+ emitted_log: { 'msg' => message, default_key => sample_value },
1767
+ expected_payload: { 'msg' => message },
1768
+ expected_field_value: sample_value
1769
+ },
1770
+ {
1771
+ # It allows setting the log entry field via a custom configured key.
1772
+ driver_config: custom_key_config,
1773
+ emitted_log: { 'msg' => message, custom_key => sample_value },
1774
+ expected_payload: { 'msg' => message },
1775
+ expected_field_value: sample_value
1776
+ },
1777
+ {
1778
+ # It doesn't set log entry field by default key if custom key specified.
1779
+ driver_config: custom_key_config,
1780
+ emitted_log: { 'msg' => message, default_key => sample_value },
1781
+ expected_payload: { 'msg' => message, default_key => sample_value },
1782
+ expected_field_value: nil
1783
+ }
1784
+ ].each do |input|
1785
+ setup_logging_stubs do
1786
+ @logs_sent = []
1787
+ d = create_driver(input[:driver_config])
1788
+ d.emit(input[:emitted_log])
1789
+ d.run
1790
+ end
1791
+ verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
1792
+ assert_equal input[:expected_field_value], entry[log_entry_field], input
1793
+ payload_fields = get_fields(entry['jsonPayload'])
1794
+ assert_equal input[:expected_payload].size, payload_fields.size, input
1795
+ payload_fields.each do |key, value|
1796
+ assert_equal input[:expected_payload][key], get_string(value), input
1797
+ end
1798
+ end
1799
+ end
1800
+ end
1801
+
1768
1802
  def http_request_message
1769
1803
  HTTP_REQUEST_MESSAGE
1770
1804
  end
@@ -20,6 +20,7 @@ module Constants
20
20
 
21
21
  # Generic attributes.
22
22
  HOSTNAME = Socket.gethostname
23
+ CUSTOM_LOGGING_API_URL = 'http://localhost:52000'.freeze
23
24
 
24
25
  # TODO(qingling128) Separate constants into different submodules.
25
26
  # Attributes used for the GCE metadata service.
@@ -43,6 +44,8 @@ module Constants
43
44
  EC2_PROJECT_ID = 'test-ec2-project-id'.freeze
44
45
  EC2_ZONE = 'us-west-2b'.freeze
45
46
  EC2_PREFIXED_ZONE = "aws:#{EC2_ZONE}".freeze
47
+ EC2_REGION = 'us-west-2'.freeze
48
+ EC2_PREFIXED_REGION = "aws:#{EC2_REGION}".freeze
46
49
  EC2_VM_ID = 'i-81c16767'.freeze
47
50
  EC2_ACCOUNT_ID = '123456789012'.freeze
48
51
 
@@ -50,6 +53,7 @@ module Constants
50
53
  EC2_IDENTITY_DOCUMENT = %({
51
54
  "accountId" : "#{EC2_ACCOUNT_ID}",
52
55
  "availabilityZone" : "#{EC2_ZONE}",
56
+ "region" : "#{EC2_REGION}",
53
57
  "instanceId" : "#{EC2_VM_ID}"
54
58
  }).freeze
55
59
 
@@ -138,6 +142,10 @@ module Constants
138
142
  APPLICATION_DEFAULT_CONFIG = %(
139
143
  ).freeze
140
144
 
145
+ CUSTOM_LOGGING_API_URL_CONFIG = %(
146
+ logging_api_url #{CUSTOM_LOGGING_API_URL}
147
+ ).freeze
148
+
141
149
  DETECT_JSON_CONFIG = %(
142
150
  detect_json true
143
151
  ).freeze
@@ -220,6 +228,11 @@ module Constants
220
228
  vm_id #{CUSTOM_VM_ID}
221
229
  ).freeze
222
230
 
231
+ CONFIG_EC2_PROJECT_ID_USE_REGION = %(
232
+ project_id #{EC2_PROJECT_ID}
233
+ use_aws_availability_zone false
234
+ ).freeze
235
+
223
236
  CONFIG_DATAFLOW = %(
224
237
  subservice_name "#{DATAFLOW_CONSTANTS[:service]}"
225
238
  labels {
@@ -244,6 +257,10 @@ module Constants
244
257
  trace_key custom_trace_key
245
258
  ).freeze
246
259
 
260
+ CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED = %(
261
+ span_id_key custom_span_id_key
262
+ ).freeze
263
+
247
264
  # Service configurations for various services.
248
265
 
249
266
  # GCE.
@@ -506,12 +523,12 @@ module Constants
506
523
  }
507
524
  }.freeze
508
525
 
509
- EC2_PARAMS = {
526
+ EC2_REGION_PARAMS = {
510
527
  resource: {
511
528
  type: EC2_CONSTANTS[:resource_type],
512
529
  labels: {
513
530
  'instance_id' => EC2_VM_ID,
514
- 'region' => EC2_PREFIXED_ZONE,
531
+ 'region' => EC2_PREFIXED_REGION,
515
532
  'aws_account' => EC2_ACCOUNT_ID
516
533
  }
517
534
  },
@@ -522,6 +539,14 @@ module Constants
522
539
  }
523
540
  }.freeze
524
541
 
542
+ EC2_ZONE_PARAMS = EC2_REGION_PARAMS.merge(
543
+ resource: EC2_REGION_PARAMS[:resource].merge(
544
+ labels: EC2_REGION_PARAMS[:resource][:labels].merge(
545
+ 'region' => EC2_PREFIXED_ZONE
546
+ )
547
+ )
548
+ ).freeze
549
+
525
550
  HTTP_REQUEST_MESSAGE = {
526
551
  'requestMethod' => 'POST',
527
552
  'requestUrl' => 'http://example/',
@@ -651,4 +676,46 @@ module Constants
651
676
  ]
652
677
  }
653
678
  }.freeze
679
+
680
+ # rubocop:disable Style/StringLiterals
681
+ PARTIAL_SUCCESS_GRPC_METADATA = {
682
+ 'google.logging.v2.writelogentriespartialerrors-bin' =>
683
+ Google::Logging::V2::WriteLogEntriesPartialErrors.encode(
684
+ Google::Logging::V2::WriteLogEntriesPartialErrors.new(
685
+ log_entry_errors: {
686
+ 0 => Google::Rpc::Status.new(
687
+ code: GRPC::Core::StatusCodes::PERMISSION_DENIED,
688
+ message: "User not authorized.",
689
+ details: []),
690
+ 1 => Google::Rpc::Status.new(
691
+ code: GRPC::Core::StatusCodes::INVALID_ARGUMENT,
692
+ message: "Log name contains illegal character :",
693
+ details: []),
694
+ 3 => Google::Rpc::Status.new(
695
+ code: GRPC::Core::StatusCodes::INVALID_ARGUMENT,
696
+ message: "Log name contains illegal character :",
697
+ details: []) })),
698
+ 'google.rpc.debuginfo-bin' =>
699
+ "\x12\xA7\x03[ORIGINAL ERROR] generic::permission_denied: User not auth" \
700
+ "orized. [google.rpc.error_details_ext] { message: \"User not authorize" \
701
+ "d.\" details { type_url: \"type.googleapis.com/google.logging.v2.Write" \
702
+ "LogEntriesPartialErrors\" value: \"\\n\\034\\010\\000\\022\\030\\010\\" \
703
+ "007\\022\\024User not authorized.\\n-\\010\\001\\022)\\010\\003\\022%L" \
704
+ "og name contains illegal character :\\n-\\010\\002\\022)\\010\\003\\02" \
705
+ "2%Log name contains illegal character :\" } }",
706
+ 'grpc-status-details-bin' =>
707
+ "\b\a\x12\x14User not authorized.\x1A\xC2\x01\nBtype.googleapis.com/goo" \
708
+ "gle.logging.v2.WriteLogEntriesPartialErrors\x12|\n\x1C\b\x00\x12\x18\b" \
709
+ "\a\x12\x14User not authorized.\n-\b\x01\x12)\b\x03\x12%Log name contai" \
710
+ "ns illegal character :\n-\b\x02\x12)\b\x03\x12%Log name contains illeg" \
711
+ "al character :\x1A\xD7\x03\n(type.googleapis.com/google.rpc.DebugInfo" \
712
+ "\x12\xAA\x03\x12\xA7\x03[ORIGINAL ERROR] generic::permission_denied: U" \
713
+ "ser not authorized. [google.rpc.error_details_ext] { message: \"User n" \
714
+ "ot authorized.\" details { type_url: \"type.googleapis.com/google.logg" \
715
+ "ing.v2.WriteLogEntriesPartialErrors\" value: \"\\n\\034\\010\\000\\022" \
716
+ "\\030\\010\\007\\022\\024User not authorized.\\n-\\010\\001\\022)\\010" \
717
+ "\\003\\022%Log name contains illegal character :\\n-\\010\\002\\022)" \
718
+ "\\010\\003\\022%Log name contains illegal character :\" } }"
719
+ }.freeze
720
+ # rubocop:enable Style/StringLiterals
654
721
  end
@@ -51,6 +51,39 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
51
51
  end
52
52
  end
53
53
 
54
+ def test_partial_success
55
+ setup_gce_metadata_stubs
56
+ setup_prometheus
57
+ setup_logging_stubs(
58
+ true, GRPC::Core::StatusCodes::PERMISSION_DENIED,
59
+ 'User not authorized.', PARTIAL_SUCCESS_GRPC_METADATA) do
60
+ # The API Client should not retry this and the plugin should consume
61
+ # the exception.
62
+ d = create_driver(PROMETHEUS_ENABLE_CONFIG + PARTIAL_SUCCESS_CONFIG)
63
+ 4.times do |i|
64
+ d.emit('message' => log_entry(i.to_s))
65
+ end
66
+ d.run
67
+ assert_prometheus_metric_value(
68
+ :stackdriver_successful_requests_count, 1,
69
+ grpc: true, code: GRPC::Core::StatusCodes::OK)
70
+ assert_prometheus_metric_value(
71
+ :stackdriver_failed_requests_count, 0,
72
+ grpc: true, code: GRPC::Core::StatusCodes::PERMISSION_DENIED)
73
+ assert_prometheus_metric_value(
74
+ :stackdriver_ingested_entries_count, 1,
75
+ grpc: true, code: GRPC::Core::StatusCodes::OK)
76
+ assert_prometheus_metric_value(
77
+ :stackdriver_dropped_entries_count, 2,
78
+ grpc: true, code: GRPC::Core::StatusCodes::INVALID_ARGUMENT)
79
+ assert_prometheus_metric_value(
80
+ :stackdriver_dropped_entries_count, 1,
81
+ grpc: true, code: GRPC::Core::StatusCodes::PERMISSION_DENIED)
82
+ assert_prometheus_metric_value(
83
+ :stackdriver_retried_entries_count, 0, grpc: true)
84
+ end
85
+ end
86
+
54
87
  def test_server_error
55
88
  setup_gce_metadata_stubs
56
89
  {
@@ -246,11 +279,6 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
246
279
  end
247
280
 
248
281
  def api_client
249
- ssl_creds = GRPC::Core::ChannelCredentials.new
250
- authentication = Google::Auth.get_application_default
251
- creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
252
- ssl_creds.compose(creds)
253
-
254
282
  @grpc_stub
255
283
  end
256
284
  end
@@ -263,12 +291,17 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
263
291
  @requests_received = requests_received
264
292
  end
265
293
 
266
- def write_log_entries(entries, log_name: nil, resource: nil, labels: nil)
294
+ def write_log_entries(entries,
295
+ log_name: nil,
296
+ resource: nil,
297
+ labels: nil,
298
+ partial_success: nil)
267
299
  request = Google::Apis::LoggingV2::WriteLogEntriesRequest.new(
268
300
  log_name: log_name,
269
301
  resource: resource,
270
302
  labels: labels,
271
- entries: entries
303
+ entries: entries,
304
+ partial_success: partial_success
272
305
  )
273
306
  @requests_received << request
274
307
  WriteLogEntriesResponse.new
@@ -278,18 +311,23 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
278
311
  # GRPC logging mock that fails and returns server side or client side errors.
279
312
  class GRPCLoggingMockFailingService <
280
313
  Google::Cloud::Logging::V2::LoggingServiceV2Client
281
- def initialize(code, message, failed_attempts)
314
+ def initialize(code, message, metadata, failed_attempts)
282
315
  @code = code
283
316
  @message = message
317
+ @metadata = metadata
284
318
  @failed_attempts = failed_attempts
285
319
  super()
286
320
  end
287
321
 
288
322
  # rubocop:disable Lint/UnusedMethodArgument
289
- def write_log_entries(entries, log_name: nil, resource: nil, labels: nil)
323
+ def write_log_entries(entries,
324
+ log_name: nil,
325
+ resource: nil,
326
+ labels: nil,
327
+ partial_success: nil)
290
328
  @failed_attempts << 1
291
329
  begin
292
- raise GRPC::BadStatus.new_status_exception(@code, @message)
330
+ raise GRPC::BadStatus.new_status_exception(@code, @message, @metadata)
293
331
  rescue
294
332
  # Google::Gax::GaxError will wrap the latest thrown exception as @cause.
295
333
  raise Google::Gax::GaxError, @message
@@ -299,11 +337,14 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
299
337
  end
300
338
 
301
339
  # Set up grpc stubs to mock the external calls.
302
- def setup_logging_stubs(should_fail = false, code = nil, message = nil)
340
+ def setup_logging_stubs(should_fail = false,
341
+ code = nil,
342
+ message = nil,
343
+ metadata = {})
303
344
  if should_fail
304
345
  @failed_attempts = []
305
346
  @grpc_stub = GRPCLoggingMockFailingService.new(
306
- code, message, @failed_attempts)
347
+ code, message, metadata, @failed_attempts)
307
348
  else
308
349
  @requests_sent = []
309
350
  @grpc_stub = GRPCLoggingMockService.new(@requests_sent)
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.6.13
4
+ version: 0.6.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Derr
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-01-18 00:00:00.000000000 Z
12
+ date: 2018-01-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd
@@ -45,56 +45,62 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '0.14'
48
+ version: '0.17'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '0.14'
55
+ version: '0.17'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: google-cloud-logging
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: 1.2.3
62
+ version: '1.3'
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.2
63
66
  type: :runtime
64
67
  prerelease: false
65
68
  version_requirements: !ruby/object:Gem::Requirement
66
69
  requirements:
67
70
  - - "~>"
68
71
  - !ruby/object:Gem::Version
69
- version: 1.2.3
72
+ version: '1.3'
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.2
70
76
  - !ruby/object:Gem::Dependency
71
77
  name: googleauth
72
78
  requirement: !ruby/object:Gem::Requirement
73
79
  requirements:
74
80
  - - "~>"
75
81
  - !ruby/object:Gem::Version
76
- version: '0.5'
82
+ version: '0.6'
77
83
  type: :runtime
78
84
  prerelease: false
79
85
  version_requirements: !ruby/object:Gem::Requirement
80
86
  requirements:
81
87
  - - "~>"
82
88
  - !ruby/object:Gem::Version
83
- version: '0.5'
89
+ version: '0.6'
84
90
  - !ruby/object:Gem::Dependency
85
91
  name: grpc
86
92
  requirement: !ruby/object:Gem::Requirement
87
93
  requirements:
88
94
  - - "~>"
89
95
  - !ruby/object:Gem::Version
90
- version: 1.2.5
96
+ version: '1.0'
91
97
  type: :runtime
92
98
  prerelease: false
93
99
  version_requirements: !ruby/object:Gem::Requirement
94
100
  requirements:
95
101
  - - "~>"
96
102
  - !ruby/object:Gem::Version
97
- version: 1.2.5
103
+ version: '1.0'
98
104
  - !ruby/object:Gem::Dependency
99
105
  name: json
100
106
  requirement: !ruby/object:Gem::Requirement