fluent-plugin-google-cloud 0.6.13 → 0.6.14

Sign up to get free protection for your applications and to get access to all the features.
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