fluent-plugin-google-cloud 0.6.8 → 0.6.9.pre.1
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 +30 -26
- data/fluent-plugin-google-cloud.gemspec +3 -3
- data/lib/fluent/plugin/out_google_cloud.rb +180 -21
- data/test/plugin/base_test.rb +63 -9
- data/test/plugin/constants.rb +44 -8
- data/test/plugin/test_out_google_cloud.rb +44 -1
- metadata +16 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88272d0a151ead26a57ac7b523602e566a2c6e79
|
|
4
|
+
data.tar.gz: 3161e4dd7387a67af1c725a5423165f42d85a448
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3e2e2acd6e6b7083bf806ad2cdda05f2bb0014cb751a2ee7777b05c21428e2c6256ff16764652b337700f8568df3e02b3e055e3ce8da8505f24e95220e50f6f
|
|
7
|
+
data.tar.gz: d9bed2291c45799ead9d6ef38d3f59bfb9e49f26c34b141e1f2f7db60e4bdc7623304d276b468d6816b02981b8439a39a5158d23b954bc319a93922db1b0e786
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
fluent-plugin-google-cloud (0.6.
|
|
4
|
+
fluent-plugin-google-cloud (0.6.9.pre.1)
|
|
5
5
|
fluentd (~> 0.10)
|
|
6
|
-
google-api-client (~> 0.
|
|
7
|
-
google-cloud-logging (
|
|
6
|
+
google-api-client (~> 0.14)
|
|
7
|
+
google-cloud-logging (~> 1.2, >= 1.2.3)
|
|
8
8
|
googleapis-common-protos (~> 1.3)
|
|
9
9
|
googleauth (~> 0.4, < 0.5.2)
|
|
10
10
|
grpc (~> 1.0, < 1.3)
|
|
@@ -21,6 +21,8 @@ GEM
|
|
|
21
21
|
cool.io (1.5.1)
|
|
22
22
|
crack (0.4.3)
|
|
23
23
|
safe_yaml (~> 1.0.0)
|
|
24
|
+
declarative (0.0.10)
|
|
25
|
+
declarative-option (0.1.0)
|
|
24
26
|
faraday (0.13.1)
|
|
25
27
|
multipart-post (>= 1.2, < 3)
|
|
26
28
|
fluentd (0.14.21)
|
|
@@ -34,22 +36,23 @@ GEM
|
|
|
34
36
|
tzinfo (~> 1.0)
|
|
35
37
|
tzinfo-data (~> 1.0)
|
|
36
38
|
yajl-ruby (~> 1.0)
|
|
37
|
-
google-api-client (0.
|
|
38
|
-
addressable (~> 2.
|
|
39
|
+
google-api-client (0.15.0)
|
|
40
|
+
addressable (~> 2.5, >= 2.5.1)
|
|
39
41
|
googleauth (~> 0.5)
|
|
40
|
-
httpclient (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
google-cloud-core (0.21.1)
|
|
42
|
+
httpclient (>= 2.8.1, < 3.0)
|
|
43
|
+
mime-types (~> 3.0)
|
|
44
|
+
representable (~> 3.0)
|
|
45
|
+
retriable (>= 2.0, < 4.0)
|
|
46
|
+
google-cloud-core (1.0.0)
|
|
47
|
+
google-cloud-env (~> 1.0)
|
|
47
48
|
googleauth (~> 0.5.1)
|
|
48
|
-
google-cloud-
|
|
49
|
-
|
|
49
|
+
google-cloud-env (1.0.1)
|
|
50
|
+
faraday (~> 0.11)
|
|
51
|
+
google-cloud-logging (1.2.3)
|
|
52
|
+
google-cloud-core (~> 1.0)
|
|
50
53
|
google-gax (~> 0.8.0)
|
|
51
|
-
stackdriver-core (~>
|
|
52
|
-
google-gax (0.8.
|
|
54
|
+
stackdriver-core (~> 1.2)
|
|
55
|
+
google-gax (0.8.10)
|
|
53
56
|
google-protobuf (~> 3.2)
|
|
54
57
|
googleapis-common-protos (~> 1.3.5)
|
|
55
58
|
googleauth (~> 0.5.1)
|
|
@@ -73,10 +76,9 @@ GEM
|
|
|
73
76
|
grpc (1.2.5)
|
|
74
77
|
google-protobuf (~> 3.1)
|
|
75
78
|
googleauth (~> 0.5.1)
|
|
76
|
-
hashdiff (0.3.
|
|
79
|
+
hashdiff (0.3.7)
|
|
77
80
|
http_parser.rb (0.6.0)
|
|
78
81
|
httpclient (2.8.3)
|
|
79
|
-
hurley (0.2)
|
|
80
82
|
json (1.8.6)
|
|
81
83
|
jwt (1.5.6)
|
|
82
84
|
little-plugger (1.1.4)
|
|
@@ -105,9 +107,11 @@ GEM
|
|
|
105
107
|
rainbow (2.2.2)
|
|
106
108
|
rake
|
|
107
109
|
rake (10.5.0)
|
|
108
|
-
representable (
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
representable (3.0.4)
|
|
111
|
+
declarative (< 0.1.0)
|
|
112
|
+
declarative-option (< 0.2.0)
|
|
113
|
+
uber (< 0.2.0)
|
|
114
|
+
retriable (3.1.1)
|
|
111
115
|
rly (0.2.3)
|
|
112
116
|
rubocop (0.35.1)
|
|
113
117
|
astrolabe (~> 1.3)
|
|
@@ -116,18 +120,18 @@ GEM
|
|
|
116
120
|
rainbow (>= 1.99.1, < 3.0)
|
|
117
121
|
ruby-progressbar (~> 1.7)
|
|
118
122
|
tins (<= 1.6.0)
|
|
119
|
-
ruby-progressbar (1.
|
|
123
|
+
ruby-progressbar (1.9.0)
|
|
120
124
|
ruby_dig (0.0.2)
|
|
121
125
|
safe_yaml (1.0.4)
|
|
122
126
|
serverengine (2.0.5)
|
|
123
127
|
sigdump (~> 0.2.2)
|
|
124
128
|
sigdump (0.2.4)
|
|
125
|
-
signet (0.
|
|
129
|
+
signet (0.8.1)
|
|
126
130
|
addressable (~> 2.3)
|
|
127
131
|
faraday (~> 0.9)
|
|
128
|
-
jwt (
|
|
132
|
+
jwt (>= 1.5, < 3.0)
|
|
129
133
|
multi_json (~> 1.10)
|
|
130
|
-
stackdriver-core (
|
|
134
|
+
stackdriver-core (1.2.0)
|
|
131
135
|
strptime (0.1.9)
|
|
132
136
|
test-unit (3.2.6)
|
|
133
137
|
power_assert
|
|
@@ -137,7 +141,7 @@ GEM
|
|
|
137
141
|
thread_safe (~> 0.1)
|
|
138
142
|
tzinfo-data (1.2017.2)
|
|
139
143
|
tzinfo (>= 1.0.0)
|
|
140
|
-
uber (0.0
|
|
144
|
+
uber (0.1.0)
|
|
141
145
|
webmock (2.3.2)
|
|
142
146
|
addressable (>= 2.3.6)
|
|
143
147
|
crack (>= 0.3.2)
|
|
@@ -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
|
+
gem.version = '0.6.9.pre.1'
|
|
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,8 +21,8 @@ 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.
|
|
25
|
-
gem.add_runtime_dependency 'google-cloud-logging', '
|
|
24
|
+
gem.add_runtime_dependency 'google-api-client', '~> 0.14'
|
|
25
|
+
gem.add_runtime_dependency 'google-cloud-logging', '~> 1.2', '>= 1.2.3'
|
|
26
26
|
gem.add_runtime_dependency 'googleauth', '~> 0.4', '< 0.5.2'
|
|
27
27
|
gem.add_runtime_dependency 'grpc', '~> 1.0', '< 1.3'
|
|
28
28
|
gem.add_runtime_dependency 'json', '~> 1.8'
|
|
@@ -11,6 +11,7 @@
|
|
|
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 'erb'
|
|
14
15
|
require 'grpc'
|
|
15
16
|
require 'json'
|
|
16
17
|
require 'open-uri'
|
|
@@ -18,7 +19,7 @@ require 'socket'
|
|
|
18
19
|
require 'time'
|
|
19
20
|
require 'yaml'
|
|
20
21
|
require 'google/apis'
|
|
21
|
-
require 'google/apis/
|
|
22
|
+
require 'google/apis/logging_v2'
|
|
22
23
|
require 'google/logging/v2/logging_pb'
|
|
23
24
|
require 'google/logging/v2/logging_services_pb'
|
|
24
25
|
require 'google/logging/v2/log_entry_pb'
|
|
@@ -67,7 +68,7 @@ module Fluent
|
|
|
67
68
|
DATAFLOW_CONSTANTS = {
|
|
68
69
|
service: 'dataflow.googleapis.com',
|
|
69
70
|
resource_type: 'dataflow_step',
|
|
70
|
-
|
|
71
|
+
extra_resource_labels: %w(region job_name job_id step_id)
|
|
71
72
|
}
|
|
72
73
|
DATAPROC_CONSTANTS = {
|
|
73
74
|
service: 'cluster.dataproc.googleapis.com',
|
|
@@ -81,7 +82,7 @@ module Fluent
|
|
|
81
82
|
ML_CONSTANTS = {
|
|
82
83
|
service: 'ml.googleapis.com',
|
|
83
84
|
resource_type: 'ml_job',
|
|
84
|
-
|
|
85
|
+
extra_resource_labels: %w(job_id task_name)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
# The map between a subservice name and a resource type.
|
|
@@ -112,7 +113,7 @@ module Fluent
|
|
|
112
113
|
'http://local-metadata-agent.stackdriver.com:8000'
|
|
113
114
|
end
|
|
114
115
|
|
|
115
|
-
#
|
|
116
|
+
# Internal constants.
|
|
116
117
|
module InternalConstants
|
|
117
118
|
# The label name of local_resource_id in the json payload. When a record
|
|
118
119
|
# has this field in the payload, we will use the value to retrieve
|
|
@@ -144,7 +145,7 @@ module Fluent
|
|
|
144
145
|
# The grpc version class name.
|
|
145
146
|
'Google::Logging::Type::HttpRequest',
|
|
146
147
|
# The non-grpc version class name.
|
|
147
|
-
'Google::Apis::
|
|
148
|
+
'Google::Apis::LoggingV2::HttpRequest'
|
|
148
149
|
],
|
|
149
150
|
'source_location' => [
|
|
150
151
|
'@source_location_key',
|
|
@@ -154,7 +155,7 @@ module Fluent
|
|
|
154
155
|
%w(line line parse_int)
|
|
155
156
|
],
|
|
156
157
|
'Google::Logging::V2::LogEntrySourceLocation',
|
|
157
|
-
'Google::Apis::
|
|
158
|
+
'Google::Apis::LoggingV2::LogEntrySourceLocation'
|
|
158
159
|
],
|
|
159
160
|
'operation' => [
|
|
160
161
|
'@operation_key',
|
|
@@ -165,9 +166,13 @@ module Fluent
|
|
|
165
166
|
%w(last last parse_bool)
|
|
166
167
|
],
|
|
167
168
|
'Google::Logging::V2::LogEntryOperation',
|
|
168
|
-
'Google::Apis::
|
|
169
|
+
'Google::Apis::LoggingV2::LogEntryOperation'
|
|
169
170
|
]
|
|
170
171
|
}
|
|
172
|
+
|
|
173
|
+
# The name of the WriteLogEntriesPartialErrors field in the error details.
|
|
174
|
+
PARTIAL_ERROR_FIELD =
|
|
175
|
+
'type.googleapis.com/google.logging.v2.WriteLogEntriesPartialErrors'
|
|
171
176
|
end
|
|
172
177
|
|
|
173
178
|
include self::ServiceConstants
|
|
@@ -177,7 +182,7 @@ module Fluent
|
|
|
177
182
|
Fluent::Plugin.register_output('google_cloud', self)
|
|
178
183
|
|
|
179
184
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
|
180
|
-
PLUGIN_VERSION = '0.6.
|
|
185
|
+
PLUGIN_VERSION = '0.6.9.pre.1'
|
|
181
186
|
|
|
182
187
|
# Name of the the Google cloud logging write scope.
|
|
183
188
|
LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
|
|
@@ -280,6 +285,12 @@ module Fluent
|
|
|
280
285
|
# Cloud Logging API.
|
|
281
286
|
config_param :use_grpc, :bool, :default => false
|
|
282
287
|
|
|
288
|
+
# Whether valid entries should be written even if some other entries fail
|
|
289
|
+
# due to INVALID_ARGUMENT or PERMISSION_DENIED errors when communicating to
|
|
290
|
+
# the Cloud Logging API. This is highly recommended. Right now this only
|
|
291
|
+
# works with the REST path (use_grpc = false).
|
|
292
|
+
config_param :partial_success, :bool, :default => false
|
|
293
|
+
|
|
283
294
|
# Whether to allow non-UTF-8 characters in user logs. If set to true, any
|
|
284
295
|
# non-UTF-8 character would be replaced by the string specified by
|
|
285
296
|
# 'non_utf8_replacement_string'. If set to false, any non-UTF-8 character
|
|
@@ -338,6 +349,15 @@ module Fluent
|
|
|
338
349
|
def configure(conf)
|
|
339
350
|
super
|
|
340
351
|
|
|
352
|
+
# TODO(qingling128): Remove this warning after the support is added. Also
|
|
353
|
+
# remove the comment in the description of this configuration.
|
|
354
|
+
if @partial_success && @use_grpc
|
|
355
|
+
@log.warn 'Detected partial_success enabled while use_grpc is also' \
|
|
356
|
+
' enabled. The support for partial success in the gRPC path' \
|
|
357
|
+
' is to be added in the near future. For now the ' \
|
|
358
|
+
' partial_success flag will be ignored.'
|
|
359
|
+
end
|
|
360
|
+
|
|
341
361
|
# If monitoring is enabled, register metrics in the default registry
|
|
342
362
|
# and store metric objects for future use.
|
|
343
363
|
if @enable_monitoring
|
|
@@ -519,7 +539,7 @@ module Fluent
|
|
|
519
539
|
# Remove the labels if we didn't populate them with anything.
|
|
520
540
|
entry_level_resource.labels = nil if
|
|
521
541
|
entry_level_resource.labels.empty?
|
|
522
|
-
entry = Google::Apis::
|
|
542
|
+
entry = Google::Apis::LoggingV2::LogEntry.new(
|
|
523
543
|
labels: entry_level_common_labels,
|
|
524
544
|
resource: entry_level_resource,
|
|
525
545
|
severity: severity,
|
|
@@ -622,20 +642,24 @@ module Fluent
|
|
|
622
642
|
else
|
|
623
643
|
begin
|
|
624
644
|
write_request = \
|
|
625
|
-
Google::Apis::
|
|
645
|
+
Google::Apis::LoggingV2::WriteLogEntriesRequest.new(
|
|
626
646
|
log_name: log_name,
|
|
627
647
|
resource: group_level_resource,
|
|
628
648
|
labels: group_level_common_labels,
|
|
649
|
+
partial_success: @partial_success,
|
|
629
650
|
entries: entries)
|
|
630
651
|
entries_count = entries.length
|
|
631
652
|
|
|
632
|
-
# TODO: RequestOptions
|
|
633
653
|
begin
|
|
634
|
-
client.write_entry_log_entries(
|
|
654
|
+
client.write_entry_log_entries(
|
|
655
|
+
write_request,
|
|
656
|
+
options: { api_format_version: '2' }
|
|
657
|
+
)
|
|
635
658
|
rescue Google::Apis::Error => error
|
|
636
659
|
increment_failed_requests_count(error.status_code)
|
|
637
660
|
raise error
|
|
638
661
|
end
|
|
662
|
+
|
|
639
663
|
increment_successful_requests_count
|
|
640
664
|
increment_ingested_entries_count(entries_count)
|
|
641
665
|
|
|
@@ -664,9 +688,20 @@ module Fluent
|
|
|
664
688
|
rescue Google::Apis::ClientError => error
|
|
665
689
|
# Most ClientErrors indicate a problem with the request itself and
|
|
666
690
|
# should not be retried.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
691
|
+
error_details_map = construct_error_details_map(error)
|
|
692
|
+
if error_details_map.empty?
|
|
693
|
+
increment_dropped_entries_count(entries_count)
|
|
694
|
+
@log.warn "Dropping #{entries_count} log message(s)",
|
|
695
|
+
error_class: error.class.to_s, error: error.to_s
|
|
696
|
+
else
|
|
697
|
+
error_details_map.each do |(error_code, error_message), indexes|
|
|
698
|
+
partial_error_count = indexes.length
|
|
699
|
+
increment_dropped_entries_count(partial_error_count)
|
|
700
|
+
@log.warn "Dropping #{partial_error_count} log message(s)",
|
|
701
|
+
error_code: "google.rpc.Code[#{error_code}]",
|
|
702
|
+
error: error_message
|
|
703
|
+
end
|
|
704
|
+
end
|
|
670
705
|
end
|
|
671
706
|
end
|
|
672
707
|
end
|
|
@@ -836,7 +871,7 @@ module Fluent
|
|
|
836
871
|
# Metadata Agent. Thus it should be equivalent to what Metadata Agent
|
|
837
872
|
# returns.
|
|
838
873
|
def determine_agent_level_monitored_resource_via_legacy
|
|
839
|
-
resource = Google::Apis::
|
|
874
|
+
resource = Google::Apis::LoggingV2::MonitoredResource.new(
|
|
840
875
|
labels: {})
|
|
841
876
|
resource.type = determine_agent_level_monitored_resource_type
|
|
842
877
|
resource.labels = determine_agent_level_monitored_resource_labels(
|
|
@@ -1090,6 +1125,18 @@ module Fluent
|
|
|
1090
1125
|
common_labels.delete("#{COMPUTE_CONSTANTS[:service]}/resource_name")
|
|
1091
1126
|
end
|
|
1092
1127
|
|
|
1128
|
+
# Cloud Dataflow and Cloud ML.
|
|
1129
|
+
# These labels can be set via the 'labels' option.
|
|
1130
|
+
# Report them as monitored resource labels instead of common labels.
|
|
1131
|
+
# e.g. "dataflow.googleapis.com/job_id" => "job_id"
|
|
1132
|
+
[DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
|
|
1133
|
+
next unless resource.type == service_constants[:resource_type]
|
|
1134
|
+
resource.labels.merge!(
|
|
1135
|
+
delete_and_extract_labels(
|
|
1136
|
+
common_labels, service_constants[:extra_resource_labels]
|
|
1137
|
+
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h))
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1093
1140
|
resource.freeze
|
|
1094
1141
|
resource.labels.freeze
|
|
1095
1142
|
common_labels.freeze
|
|
@@ -1157,14 +1204,14 @@ module Fluent
|
|
|
1157
1204
|
common_labels.merge!(delete_and_extract_labels(record, @label_map))
|
|
1158
1205
|
|
|
1159
1206
|
# Cloud Dataflow and Cloud ML.
|
|
1160
|
-
# These labels can be set via
|
|
1207
|
+
# These labels can be set via the 'labels' or 'label_map' options.
|
|
1161
1208
|
# Report them as monitored resource labels instead of common labels.
|
|
1162
1209
|
# e.g. "dataflow.googleapis.com/job_id" => "job_id"
|
|
1163
1210
|
[DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
|
|
1164
1211
|
next unless resource.type == service_constants[:resource_type]
|
|
1165
1212
|
resource.labels.merge!(
|
|
1166
1213
|
delete_and_extract_labels(
|
|
1167
|
-
common_labels, service_constants[:
|
|
1214
|
+
common_labels, service_constants[:extra_resource_labels]
|
|
1168
1215
|
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h))
|
|
1169
1216
|
end
|
|
1170
1217
|
|
|
@@ -1187,7 +1234,7 @@ module Fluent
|
|
|
1187
1234
|
# TODO(qingling128): Use Google::Api::MonitoredResource directly after we
|
|
1188
1235
|
# upgrade gRPC version to include the fix for the protobuf map
|
|
1189
1236
|
# corruption issue.
|
|
1190
|
-
Google::Apis::
|
|
1237
|
+
Google::Apis::LoggingV2::MonitoredResource.new(
|
|
1191
1238
|
type: resource.type,
|
|
1192
1239
|
labels: resource.labels.to_h
|
|
1193
1240
|
)
|
|
@@ -1658,7 +1705,7 @@ module Fluent
|
|
|
1658
1705
|
# TODO: Use a non-default ClientOptions object.
|
|
1659
1706
|
Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
|
|
1660
1707
|
Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
|
|
1661
|
-
@client = Google::Apis::
|
|
1708
|
+
@client = Google::Apis::LoggingV2::LoggingService.new
|
|
1662
1709
|
@client.authorization = Google::Auth.get_application_default(
|
|
1663
1710
|
LOGGING_SCOPE)
|
|
1664
1711
|
end
|
|
@@ -1710,6 +1757,118 @@ module Fluent
|
|
|
1710
1757
|
end
|
|
1711
1758
|
end
|
|
1712
1759
|
|
|
1760
|
+
# Extract a map of error details from an potentially partially successful
|
|
1761
|
+
# request. Return an empty map if @partial_success is not enabled.
|
|
1762
|
+
#
|
|
1763
|
+
# The keys in this map are [error_code, error_message] pairs, and the values
|
|
1764
|
+
# are a list of stringified indexes of log entries that failed due to this
|
|
1765
|
+
# error.
|
|
1766
|
+
#
|
|
1767
|
+
# A sample error.body looks like:
|
|
1768
|
+
# {
|
|
1769
|
+
# "error": {
|
|
1770
|
+
# "code": 403,
|
|
1771
|
+
# "message": "User not authorized.",
|
|
1772
|
+
# "status": "PERMISSION_DENIED",
|
|
1773
|
+
# "details": [
|
|
1774
|
+
# {
|
|
1775
|
+
# "@type": "type.googleapis.com/google.logging.v2.WriteLogEntriesPar
|
|
1776
|
+
# tialErrors",
|
|
1777
|
+
# "logEntryErrors": {
|
|
1778
|
+
# "0": {
|
|
1779
|
+
# "code": 7,
|
|
1780
|
+
# "message": "User not authorized."
|
|
1781
|
+
# },
|
|
1782
|
+
# "1": {
|
|
1783
|
+
# "code": 3,
|
|
1784
|
+
# "message": "Log name contains illegal character :"
|
|
1785
|
+
# },
|
|
1786
|
+
# "3": {
|
|
1787
|
+
# "code": 3,
|
|
1788
|
+
# "message": "Log name contains illegal character :"
|
|
1789
|
+
# }
|
|
1790
|
+
# }
|
|
1791
|
+
# },
|
|
1792
|
+
# {
|
|
1793
|
+
# "@type": "type.googleapis.com/google.rpc.DebugInfo",
|
|
1794
|
+
# "detail": ...
|
|
1795
|
+
# }
|
|
1796
|
+
# ]
|
|
1797
|
+
# }
|
|
1798
|
+
# }
|
|
1799
|
+
#
|
|
1800
|
+
# The root level "code", "message", and "status" simply match the root
|
|
1801
|
+
# cause of the first failed log entry. For example, if we switched the order
|
|
1802
|
+
# of the log entries, then we would get:
|
|
1803
|
+
# {
|
|
1804
|
+
# "error" : {
|
|
1805
|
+
# "code" : 400,
|
|
1806
|
+
# "message" : "Log name contains illegal character :",
|
|
1807
|
+
# "status" : "INVALID_ARGUMENT",
|
|
1808
|
+
# "details": ...
|
|
1809
|
+
# }
|
|
1810
|
+
# }
|
|
1811
|
+
# We will ignore it anyway and look at the details instead which includes
|
|
1812
|
+
# info for all failed log entries.
|
|
1813
|
+
#
|
|
1814
|
+
# In this example, the logEntryErrors that we care are:
|
|
1815
|
+
# {
|
|
1816
|
+
# "0": {
|
|
1817
|
+
# "code": 7,
|
|
1818
|
+
# "message": "User not authorized."
|
|
1819
|
+
# },
|
|
1820
|
+
# "1": {
|
|
1821
|
+
# "code": 3,
|
|
1822
|
+
# "message": "Log name contains illegal character :"
|
|
1823
|
+
# },
|
|
1824
|
+
# "3": {
|
|
1825
|
+
# "code": 3,
|
|
1826
|
+
# "message": "Log name contains illegal character :"
|
|
1827
|
+
# }
|
|
1828
|
+
# }
|
|
1829
|
+
#
|
|
1830
|
+
# The ultimate map that is constructed is:
|
|
1831
|
+
# {
|
|
1832
|
+
# [7, 'User not authorized.']: ['0'],
|
|
1833
|
+
# [3, 'Log name contains illegal character :']: ['1', '3']
|
|
1834
|
+
# }
|
|
1835
|
+
def construct_error_details_map(error)
|
|
1836
|
+
return {} unless @partial_success
|
|
1837
|
+
error_details_map = Hash.new { |h, k| h[k] = [] }
|
|
1838
|
+
|
|
1839
|
+
error_details = ensure_array(
|
|
1840
|
+
ensure_hash(ensure_hash(JSON.parse(error.body))['error'])['details'])
|
|
1841
|
+
partial_errors = error_details.detect(
|
|
1842
|
+
-> { fail JSON::ParserError, "No type #{PARTIAL_ERROR_FIELD}." }
|
|
1843
|
+
) do |error_detail|
|
|
1844
|
+
ensure_hash(error_detail)['@type'] == PARTIAL_ERROR_FIELD
|
|
1845
|
+
end
|
|
1846
|
+
log_entry_errors = ensure_hash(
|
|
1847
|
+
ensure_hash(partial_errors)['logEntryErrors'])
|
|
1848
|
+
log_entry_errors.each do |index, log_entry_error|
|
|
1849
|
+
error_hash = ensure_hash(log_entry_error)
|
|
1850
|
+
fail JSON::ParserError,
|
|
1851
|
+
"Entry #{index} is missing 'code' or 'message'." unless
|
|
1852
|
+
error_hash['code'] && error_hash['message']
|
|
1853
|
+
error_key = [error_hash['code'], error_hash['message']].freeze
|
|
1854
|
+
# TODO(qingling128): Convert indexes to integers.
|
|
1855
|
+
error_details_map[error_key] << index
|
|
1856
|
+
end
|
|
1857
|
+
error_details_map
|
|
1858
|
+
rescue JSON::ParserError => e
|
|
1859
|
+
@log.warn 'Failed to extract log entry errors from the error details:' \
|
|
1860
|
+
" #{error.body}.", error: e
|
|
1861
|
+
{}
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
def ensure_array(value)
|
|
1865
|
+
Array.try_convert(value) || (fail JSON::ParserError, "#{value.class}")
|
|
1866
|
+
end
|
|
1867
|
+
|
|
1868
|
+
def ensure_hash(value)
|
|
1869
|
+
Hash.try_convert(value) || (fail JSON::ParserError, "#{value.class}")
|
|
1870
|
+
end
|
|
1871
|
+
|
|
1713
1872
|
# Increment the metric for the number of successful requests.
|
|
1714
1873
|
def increment_successful_requests_count
|
|
1715
1874
|
return unless @successful_requests_count
|
|
@@ -1748,7 +1907,7 @@ end
|
|
|
1748
1907
|
|
|
1749
1908
|
module Google
|
|
1750
1909
|
module Apis
|
|
1751
|
-
module
|
|
1910
|
+
module LoggingV2
|
|
1752
1911
|
# Override MonitoredResource::dup to make a deep copy.
|
|
1753
1912
|
class MonitoredResource
|
|
1754
1913
|
def dup
|
data/test/plugin/base_test.rb
CHANGED
|
@@ -93,6 +93,18 @@ module BaseTest
|
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
def test_configure_partial_success
|
|
97
|
+
setup_gce_metadata_stubs
|
|
98
|
+
{
|
|
99
|
+
APPLICATION_DEFAULT_CONFIG => false,
|
|
100
|
+
PARTIAL_SUCCESS_CONFIG => true
|
|
101
|
+
}.each do |(config, partial_success)|
|
|
102
|
+
d = create_driver(config)
|
|
103
|
+
assert_equal partial_success,
|
|
104
|
+
d.instance.instance_variable_get(:@partial_success)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
96
108
|
def test_metadata_loading
|
|
97
109
|
setup_gce_metadata_stubs
|
|
98
110
|
d = create_driver
|
|
@@ -961,6 +973,26 @@ module BaseTest
|
|
|
961
973
|
verify_log_entries(1, DATAPROC_PARAMS, 'jsonPayload')
|
|
962
974
|
end
|
|
963
975
|
|
|
976
|
+
def test_cloud_ml_log
|
|
977
|
+
setup_gce_metadata_stubs
|
|
978
|
+
setup_logging_stubs do
|
|
979
|
+
d = create_driver(CONFIG_ML, ML_TAG)
|
|
980
|
+
d.emit(ml_log_entry(0))
|
|
981
|
+
d.run
|
|
982
|
+
end
|
|
983
|
+
verify_log_entries(1, ML_PARAMS)
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
def test_cloud_dataflow_log
|
|
987
|
+
setup_gce_metadata_stubs
|
|
988
|
+
setup_logging_stubs do
|
|
989
|
+
d = create_driver(CONFIG_DATAFLOW, DATAFLOW_TAG)
|
|
990
|
+
d.emit(dataflow_log_entry(0))
|
|
991
|
+
d.run
|
|
992
|
+
end
|
|
993
|
+
verify_log_entries(1, DATAFLOW_PARAMS)
|
|
994
|
+
end
|
|
995
|
+
|
|
964
996
|
def test_log_entry_http_request_field_from_record
|
|
965
997
|
verify_subfields_from_record(DEFAULT_HTTP_REQUEST_KEY)
|
|
966
998
|
end
|
|
@@ -1023,11 +1055,11 @@ module BaseTest
|
|
|
1023
1055
|
setup_logging_stubs do
|
|
1024
1056
|
d = create_driver
|
|
1025
1057
|
@logs_sent = []
|
|
1026
|
-
d.emit('httpRequest' =>
|
|
1058
|
+
d.emit('httpRequest' => http_request_message.merge('latency' => input))
|
|
1027
1059
|
d.run
|
|
1028
1060
|
end
|
|
1029
1061
|
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
|
1030
|
-
assert_equal
|
|
1062
|
+
assert_equal http_request_message.merge('latency' => expected),
|
|
1031
1063
|
entry['httpRequest'], entry
|
|
1032
1064
|
assert_nil get_fields(entry['jsonPayload'])['httpRequest'], entry
|
|
1033
1065
|
end
|
|
@@ -1044,11 +1076,11 @@ module BaseTest
|
|
|
1044
1076
|
setup_logging_stubs do
|
|
1045
1077
|
d = create_driver
|
|
1046
1078
|
@logs_sent = []
|
|
1047
|
-
d.emit('httpRequest' =>
|
|
1079
|
+
d.emit('httpRequest' => http_request_message.merge('latency' => input))
|
|
1048
1080
|
d.run
|
|
1049
1081
|
end
|
|
1050
1082
|
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
|
1051
|
-
assert_equal
|
|
1083
|
+
assert_equal http_request_message, entry['httpRequest'], entry
|
|
1052
1084
|
assert_nil get_fields(entry['jsonPayload'])['httpRequest'], entry
|
|
1053
1085
|
end
|
|
1054
1086
|
end
|
|
@@ -1597,8 +1629,22 @@ module BaseTest
|
|
|
1597
1629
|
end
|
|
1598
1630
|
end
|
|
1599
1631
|
|
|
1632
|
+
def log_entry_subfields_params
|
|
1633
|
+
{
|
|
1634
|
+
# The keys are the names of fields in the payload that we are extracting
|
|
1635
|
+
# LogEntry info from. The values are lists of two elements: the name of
|
|
1636
|
+
# the subfield in LogEntry object and the expected value of that field.
|
|
1637
|
+
DEFAULT_HTTP_REQUEST_KEY => [
|
|
1638
|
+
'httpRequest', http_request_message],
|
|
1639
|
+
DEFAULT_SOURCE_LOCATION_KEY => [
|
|
1640
|
+
'sourceLocation', source_location_message],
|
|
1641
|
+
DEFAULT_OPERATION_KEY => [
|
|
1642
|
+
'operation', OPERATION_MESSAGE]
|
|
1643
|
+
}
|
|
1644
|
+
end
|
|
1645
|
+
|
|
1600
1646
|
def verify_subfields_from_record(payload_key)
|
|
1601
|
-
destination_key, payload_value =
|
|
1647
|
+
destination_key, payload_value = log_entry_subfields_params[payload_key]
|
|
1602
1648
|
@logs_sent = []
|
|
1603
1649
|
setup_gce_metadata_stubs
|
|
1604
1650
|
setup_logging_stubs do
|
|
@@ -1614,7 +1660,7 @@ module BaseTest
|
|
|
1614
1660
|
end
|
|
1615
1661
|
|
|
1616
1662
|
def verify_subfields_partial_from_record(payload_key)
|
|
1617
|
-
destination_key, payload_value =
|
|
1663
|
+
destination_key, payload_value = log_entry_subfields_params[payload_key]
|
|
1618
1664
|
@logs_sent = []
|
|
1619
1665
|
setup_gce_metadata_stubs
|
|
1620
1666
|
setup_logging_stubs do
|
|
@@ -1631,7 +1677,7 @@ module BaseTest
|
|
|
1631
1677
|
end
|
|
1632
1678
|
|
|
1633
1679
|
def verify_subfields_when_not_hash(payload_key)
|
|
1634
|
-
destination_key =
|
|
1680
|
+
destination_key = log_entry_subfields_params[payload_key][0]
|
|
1635
1681
|
@logs_sent = []
|
|
1636
1682
|
setup_gce_metadata_stubs
|
|
1637
1683
|
setup_logging_stubs do
|
|
@@ -1646,14 +1692,22 @@ module BaseTest
|
|
|
1646
1692
|
end
|
|
1647
1693
|
end
|
|
1648
1694
|
|
|
1695
|
+
def http_request_message
|
|
1696
|
+
HTTP_REQUEST_MESSAGE
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
def source_location_message
|
|
1700
|
+
SOURCE_LOCATION_MESSAGE
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1649
1703
|
# Replace the 'referer' field with nil.
|
|
1650
1704
|
def http_request_message_with_nil_referer
|
|
1651
|
-
|
|
1705
|
+
http_request_message.merge('referer' => nil)
|
|
1652
1706
|
end
|
|
1653
1707
|
|
|
1654
1708
|
# Unset the 'referer' field.
|
|
1655
1709
|
def http_request_message_with_absent_referer
|
|
1656
|
-
|
|
1710
|
+
http_request_message.reject do |k, _|
|
|
1657
1711
|
k == 'referer'
|
|
1658
1712
|
end
|
|
1659
1713
|
end
|
data/test/plugin/constants.rb
CHANGED
|
@@ -97,7 +97,7 @@ module Constants
|
|
|
97
97
|
DATAFLOW_JOB_NAME = 'job_name_1'
|
|
98
98
|
DATAFLOW_JOB_ID = 'job_id_1'
|
|
99
99
|
DATAFLOW_STEP_ID = 'step_1'
|
|
100
|
-
DATAFLOW_TAG = 'dataflow
|
|
100
|
+
DATAFLOW_TAG = 'dataflow-worker'
|
|
101
101
|
|
|
102
102
|
# Dataproc specific labels.
|
|
103
103
|
DATAPROC_CLUSTER_NAME = 'test-cluster'
|
|
@@ -140,6 +140,10 @@ module Constants
|
|
|
140
140
|
detect_json true
|
|
141
141
|
)
|
|
142
142
|
|
|
143
|
+
PARTIAL_SUCCESS_CONFIG = %(
|
|
144
|
+
partial_success true
|
|
145
|
+
)
|
|
146
|
+
|
|
143
147
|
# rubocop:disable Metrics/LineLength
|
|
144
148
|
PRIVATE_KEY_CONFIG = %(
|
|
145
149
|
auth_method private_key
|
|
@@ -533,13 +537,6 @@ module Constants
|
|
|
533
537
|
'last' => true
|
|
534
538
|
}
|
|
535
539
|
|
|
536
|
-
LOG_ENTRY_SUBFIELDS_PARAMS = {
|
|
537
|
-
# payload key, destination key, payload value
|
|
538
|
-
DEFAULT_HTTP_REQUEST_KEY => ['httpRequest', HTTP_REQUEST_MESSAGE],
|
|
539
|
-
DEFAULT_SOURCE_LOCATION_KEY => ['sourceLocation', SOURCE_LOCATION_MESSAGE],
|
|
540
|
-
DEFAULT_OPERATION_KEY => ['operation', OPERATION_MESSAGE]
|
|
541
|
-
}
|
|
542
|
-
|
|
543
540
|
CUSTOM_LABELS_MESSAGE = {
|
|
544
541
|
'customKey' => 'value'
|
|
545
542
|
}
|
|
@@ -605,4 +602,43 @@ module Constants
|
|
|
605
602
|
}
|
|
606
603
|
}.to_json
|
|
607
604
|
}
|
|
605
|
+
|
|
606
|
+
PARTIAL_SUCCESS_RESPONSE_BODY = {
|
|
607
|
+
'error' => {
|
|
608
|
+
'code' => 403,
|
|
609
|
+
'message' => 'User not authorized.',
|
|
610
|
+
'status' => 'PERMISSION_DENIED',
|
|
611
|
+
'details' => [
|
|
612
|
+
{
|
|
613
|
+
'@type' => 'type.googleapis.com/google.logging.v2.WriteLogEntriesPa' \
|
|
614
|
+
'rtialErrors',
|
|
615
|
+
'logEntryErrors' => {
|
|
616
|
+
'0' => {
|
|
617
|
+
'code' => 7,
|
|
618
|
+
'message' => 'User not authorized.'
|
|
619
|
+
},
|
|
620
|
+
'1' => {
|
|
621
|
+
'code' => 3,
|
|
622
|
+
'message' => 'Log name contains illegal character :'
|
|
623
|
+
},
|
|
624
|
+
'2' => {
|
|
625
|
+
'code' => 3,
|
|
626
|
+
'message' => 'Log name contains illegal character :'
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
'@type' => 'type.googleapis.com/google.rpc.DebugInfo',
|
|
632
|
+
'detail' => '[ORIGINAL ERROR] generic::permission_denied: User not ' \
|
|
633
|
+
'authorized. [google.rpc.error_details_ext] { message: \"User not' \
|
|
634
|
+
' authorized.\" details { type_url: \"type.googleapis.com/google.' \
|
|
635
|
+
'logging.v2.WriteLogEntriesPartialErrors\" value: \"\\n\\034\\010' \
|
|
636
|
+
'\\000\\022\\030\\010\\007\\022\\024User not authorized.\\n-\\010' \
|
|
637
|
+
'\\001\\022)\\010\\003\\022%Log name contains illegal character :' \
|
|
638
|
+
'\\n-\\010\\002\\022)\\010\\003\\022%Log name contains illegal ch' \
|
|
639
|
+
'aracter :\" } }'
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
}
|
|
643
|
+
}.to_json
|
|
608
644
|
end
|
|
@@ -51,6 +51,28 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
|
51
51
|
assert_requested(:post, WRITE_LOG_ENTRIES_URI, times: 2)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
def test_partial_success
|
|
55
|
+
setup_gce_metadata_stubs
|
|
56
|
+
{
|
|
57
|
+
APPLICATION_DEFAULT_CONFIG => 4.0,
|
|
58
|
+
PARTIAL_SUCCESS_CONFIG => 3.0
|
|
59
|
+
}.each do |config, failed_entry_count|
|
|
60
|
+
setup_prometheus
|
|
61
|
+
# The API Client should not retry this and the plugin should consume
|
|
62
|
+
# the exception.
|
|
63
|
+
stub_request(:post, WRITE_LOG_ENTRIES_URI)
|
|
64
|
+
.to_return(status: 400, body: PARTIAL_SUCCESS_RESPONSE_BODY)
|
|
65
|
+
d = create_driver(PROMETHEUS_ENABLE_CONFIG + config)
|
|
66
|
+
4.times do |i|
|
|
67
|
+
d.emit('message' => log_entry(i.to_s))
|
|
68
|
+
end
|
|
69
|
+
d.run
|
|
70
|
+
assert_prometheus_metric_value(:stackdriver_dropped_entries_count,
|
|
71
|
+
failed_entry_count)
|
|
72
|
+
end
|
|
73
|
+
assert_requested(:post, WRITE_LOG_ENTRIES_URI, times: 2)
|
|
74
|
+
end
|
|
75
|
+
|
|
54
76
|
def test_server_error
|
|
55
77
|
setup_gce_metadata_stubs
|
|
56
78
|
# The API client should retry this once, then throw an exception which
|
|
@@ -251,7 +273,7 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
|
251
273
|
|
|
252
274
|
private
|
|
253
275
|
|
|
254
|
-
WRITE_LOG_ENTRIES_URI = 'https://logging.googleapis.com/
|
|
276
|
+
WRITE_LOG_ENTRIES_URI = 'https://logging.googleapis.com/v2/entries:write'
|
|
255
277
|
|
|
256
278
|
def rename_key(hash, old_key, new_key)
|
|
257
279
|
hash.merge(new_key => hash[old_key]).reject { |k, _| k == old_key }
|
|
@@ -316,4 +338,25 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
|
316
338
|
def null_value
|
|
317
339
|
nil
|
|
318
340
|
end
|
|
341
|
+
|
|
342
|
+
# 'responseSize' and 'requestSize' are Integers in the gRPC proto, yet Strings
|
|
343
|
+
# in REST API client.
|
|
344
|
+
# TODO(qingling128): Address this accordingly once the following question is
|
|
345
|
+
# answered: https://github.com/google/google-api-ruby-client/issues/619.
|
|
346
|
+
# If this discrepancy is legit, add some comments to explain the reason.
|
|
347
|
+
# Otherwise once the discrepancy is fixed, we need to upgrade to that version
|
|
348
|
+
# and change our tests accordingly.
|
|
349
|
+
def http_request_message
|
|
350
|
+
HTTP_REQUEST_MESSAGE.merge(
|
|
351
|
+
'responseSize' => HTTP_REQUEST_MESSAGE['responseSize'].to_s,
|
|
352
|
+
'requestSize' => HTTP_REQUEST_MESSAGE['requestSize'].to_s
|
|
353
|
+
)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# 'line' is an Integer in the gRPC proto, yet a String in the REST API client.
|
|
357
|
+
def source_location_message
|
|
358
|
+
SOURCE_LOCATION_MESSAGE.merge(
|
|
359
|
+
'line' => SOURCE_LOCATION_MESSAGE['line'].to_s
|
|
360
|
+
)
|
|
361
|
+
end
|
|
319
362
|
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.6.
|
|
4
|
+
version: 0.6.9.pre.1
|
|
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: 2017-
|
|
12
|
+
date: 2017-10-18 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: fluentd
|
|
@@ -45,28 +45,34 @@ dependencies:
|
|
|
45
45
|
requirements:
|
|
46
46
|
- - "~>"
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: 0.
|
|
48
|
+
version: '0.14'
|
|
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.
|
|
55
|
+
version: '0.14'
|
|
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
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '1.2'
|
|
63
|
+
- - ">="
|
|
61
64
|
- !ruby/object:Gem::Version
|
|
62
|
-
version:
|
|
65
|
+
version: 1.2.3
|
|
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:
|
|
72
|
+
version: '1.2'
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 1.2.3
|
|
70
76
|
- !ruby/object:Gem::Dependency
|
|
71
77
|
name: googleauth
|
|
72
78
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -249,9 +255,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
249
255
|
version: '2.0'
|
|
250
256
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
257
|
requirements:
|
|
252
|
-
- - "
|
|
258
|
+
- - ">"
|
|
253
259
|
- !ruby/object:Gem::Version
|
|
254
|
-
version:
|
|
260
|
+
version: 1.3.1
|
|
255
261
|
requirements: []
|
|
256
262
|
rubyforge_project:
|
|
257
263
|
rubygems_version: 2.4.8
|