fluent-plugin-google-cloud 0.6.8 → 0.6.9.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|