fluent-plugin-google-cloud 0.12.10 → 0.12.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +26 -19
- data/Rakefile +6 -5
- data/fluent-plugin-google-cloud.gemspec +9 -9
- data/lib/fluent/plugin/common.rb +42 -29
- data/lib/fluent/plugin/filter_add_insert_ids.rb +1 -11
- data/lib/fluent/plugin/filter_analyze_config.rb +65 -47
- data/lib/fluent/plugin/in_object_space_dump.rb +1 -1
- data/lib/fluent/plugin/monitoring.rb +34 -23
- data/lib/fluent/plugin/out_google_cloud.rb +222 -166
- data/lib/fluent/plugin/statusz.rb +7 -9
- data/test/helper.rb +6 -0
- data/test/plugin/base_test.rb +194 -134
- data/test/plugin/constants.rb +18 -14
- data/test/plugin/test_driver.rb +2 -1
- data/test/plugin/test_filter_add_insert_ids.rb +5 -3
- data/test/plugin/test_filter_analyze_config.rb +32 -17
- data/test/plugin/test_out_google_cloud.rb +33 -21
- data/test/plugin/test_out_google_cloud_grpc.rb +35 -22
- data/test/plugin/utils.rb +8 -6
- metadata +29 -29
@@ -85,6 +85,7 @@ require 'strptime'
|
|
85
85
|
# Dummy Strptime class.
|
86
86
|
class Strptime
|
87
87
|
def self.new(_)
|
88
|
+
# empty
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
@@ -130,22 +131,22 @@ module Fluent
|
|
130
131
|
# Map from subfields' names to their types.
|
131
132
|
[
|
132
133
|
# subfield key in the payload, destination key, cast lambda (opt)
|
133
|
-
%w
|
134
|
-
%w
|
135
|
-
%w
|
136
|
-
%w
|
137
|
-
cache_validated_with_origin_server parse_bool
|
138
|
-
%w
|
139
|
-
%w
|
140
|
-
%w
|
141
|
-
%w
|
142
|
-
%w
|
143
|
-
%w
|
144
|
-
%w
|
145
|
-
%w
|
146
|
-
%w
|
147
|
-
%w
|
148
|
-
%w
|
134
|
+
%w[cacheFillBytes cache_fill_bytes parse_int],
|
135
|
+
%w[cacheHit cache_hit parse_bool],
|
136
|
+
%w[cacheLookup cache_lookup parse_bool],
|
137
|
+
%w[cacheValidatedWithOriginServer
|
138
|
+
cache_validated_with_origin_server parse_bool],
|
139
|
+
%w[latency latency parse_latency],
|
140
|
+
%w[protocol protocol parse_string],
|
141
|
+
%w[referer referer parse_string],
|
142
|
+
%w[remoteIp remote_ip parse_string],
|
143
|
+
%w[responseSize response_size parse_int],
|
144
|
+
%w[requestMethod request_method parse_string],
|
145
|
+
%w[requestSize request_size parse_int],
|
146
|
+
%w[requestUrl request_url parse_string],
|
147
|
+
%w[serverIp server_ip parse_string],
|
148
|
+
%w[status status parse_int],
|
149
|
+
%w[userAgent user_agent parse_string]
|
149
150
|
],
|
150
151
|
# The grpc version class name.
|
151
152
|
'Google::Logging::Type::HttpRequest',
|
@@ -155,10 +156,10 @@ module Fluent
|
|
155
156
|
'operation' => [
|
156
157
|
'@operation_key',
|
157
158
|
[
|
158
|
-
%w
|
159
|
-
%w
|
160
|
-
%w
|
161
|
-
%w
|
159
|
+
%w[id id parse_string],
|
160
|
+
%w[producer producer parse_string],
|
161
|
+
%w[first first parse_bool],
|
162
|
+
%w[last last parse_bool]
|
162
163
|
],
|
163
164
|
'Google::Logging::V2::LogEntryOperation',
|
164
165
|
'Google::Apis::LoggingV2::LogEntryOperation'
|
@@ -166,9 +167,9 @@ module Fluent
|
|
166
167
|
'source_location' => [
|
167
168
|
'@source_location_key',
|
168
169
|
[
|
169
|
-
%w
|
170
|
-
%w
|
171
|
-
%w
|
170
|
+
%w[file file parse_string],
|
171
|
+
%w[function function parse_string],
|
172
|
+
%w[line line parse_int]
|
172
173
|
],
|
173
174
|
'Google::Logging::V2::LogEntrySourceLocation',
|
174
175
|
'Google::Apis::LoggingV2::LogEntrySourceLocation'
|
@@ -195,7 +196,8 @@ module Fluent
|
|
195
196
|
PLUGIN_VERSION = begin
|
196
197
|
# Extract plugin version from file path.
|
197
198
|
match_data = __FILE__.match(
|
198
|
-
%r{fluent-plugin-google-cloud-(?<version>[^/]*)/}
|
199
|
+
%r{fluent-plugin-google-cloud-(?<version>[^/]*)/}
|
200
|
+
)
|
199
201
|
if match_data
|
200
202
|
match_data['version']
|
201
203
|
else
|
@@ -203,9 +205,10 @@ module Fluent
|
|
203
205
|
dependency = Gem::Dependency.new('fluent-plugin-google-cloud')
|
204
206
|
all_specs, = Gem::SpecFetcher.fetcher.spec_for_dependency(dependency)
|
205
207
|
matching_version, = all_specs.grep(
|
206
|
-
proc { |spec,| __FILE__.include?(spec.full_gem_path) }
|
207
|
-
|
208
|
-
|
208
|
+
proc { |spec,| __FILE__.include?(spec.full_gem_path) }
|
209
|
+
) do |spec,|
|
210
|
+
spec.version.to_s
|
211
|
+
end
|
209
212
|
# If no matching version was found, return a valid but obviously wrong
|
210
213
|
# value.
|
211
214
|
matching_version || '0.0.0-unknown'
|
@@ -323,7 +326,7 @@ module Fluent
|
|
323
326
|
# Whether to enable gRPC compression when communicating with the Stackdriver
|
324
327
|
# Logging API. Only used if 'use_grpc' is set to true.
|
325
328
|
config_param :grpc_compression_algorithm, :enum,
|
326
|
-
list: [
|
329
|
+
list: %i[none gzip],
|
327
330
|
:default => nil
|
328
331
|
|
329
332
|
# Whether valid entries should be written even if some other entries fail
|
@@ -428,12 +431,7 @@ module Fluent
|
|
428
431
|
|
429
432
|
# Expose attr_readers to make testing of metadata more direct than only
|
430
433
|
# testing it indirectly through metadata sent with logs.
|
431
|
-
attr_reader :project_id
|
432
|
-
attr_reader :zone
|
433
|
-
attr_reader :vm_id
|
434
|
-
attr_reader :resource
|
435
|
-
attr_reader :common_labels
|
436
|
-
attr_reader :monitoring_resource
|
434
|
+
attr_reader :project_id, :zone, :vm_id, :resource, :common_labels, :monitoring_resource
|
437
435
|
|
438
436
|
def initialize
|
439
437
|
super
|
@@ -496,13 +494,15 @@ module Fluent
|
|
496
494
|
|
497
495
|
# All metadata parameters must now be set.
|
498
496
|
@utils.check_required_metadata_variables(
|
499
|
-
@platform, @project_id, @zone, @vm_id
|
497
|
+
@platform, @project_id, @zone, @vm_id
|
498
|
+
)
|
500
499
|
|
501
500
|
# Retrieve monitored resource.
|
502
501
|
# Fail over to retrieve monitored resource via the legacy path if we fail
|
503
502
|
# to get it from Metadata Agent.
|
504
503
|
@resource ||= @utils.determine_agent_level_monitored_resource_via_legacy(
|
505
|
-
@platform, @subservice_name, @detect_subservice, @vm_id, @zone
|
504
|
+
@platform, @subservice_name, @detect_subservice, @vm_id, @zone
|
505
|
+
)
|
506
506
|
|
507
507
|
if @metrics_resource
|
508
508
|
unless @metrics_resource[:type].is_a?(String)
|
@@ -517,7 +517,7 @@ module Fluent
|
|
517
517
|
" #{@metrics_resource}."
|
518
518
|
end
|
519
519
|
extra_keys = @metrics_resource.reject do |k, _|
|
520
|
-
|
520
|
+
%i[type labels].include?(k)
|
521
521
|
end
|
522
522
|
unless extra_keys.empty?
|
523
523
|
raise Fluent::ConfigError,
|
@@ -545,16 +545,18 @@ module Fluent
|
|
545
545
|
# and store metric objects for future use.
|
546
546
|
if @enable_monitoring
|
547
547
|
unless Monitoring::MonitoringRegistryFactory.supports_monitoring_type(
|
548
|
-
@monitoring_type
|
548
|
+
@monitoring_type
|
549
|
+
)
|
549
550
|
@log.warn "monitoring_type '#{@monitoring_type}' is unknown; "\
|
550
551
|
'there will be no metrics'
|
551
552
|
end
|
552
|
-
if @metrics_resource
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
553
|
+
@monitoring_resource = if @metrics_resource
|
554
|
+
@utils.create_monitored_resource(
|
555
|
+
@metrics_resource[:type], @metrics_resource[:labels]
|
556
|
+
)
|
557
|
+
else
|
558
|
+
@resource
|
559
|
+
end
|
558
560
|
@registry = Monitoring::MonitoringRegistryFactory
|
559
561
|
.create(@monitoring_type, @project_id,
|
560
562
|
@monitoring_resource, @gcm_service_address)
|
@@ -564,37 +566,43 @@ module Fluent
|
|
564
566
|
# we can't change it.
|
565
567
|
@uptime_metric = @registry.counter(
|
566
568
|
:uptime, [:version], 'Uptime of Logging agent',
|
567
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
569
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
570
|
+
)
|
568
571
|
update_uptime
|
569
572
|
timer_execute(:update_uptime, 1) { update_uptime }
|
570
573
|
@successful_requests_count = @registry.counter(
|
571
574
|
:stackdriver_successful_requests_count,
|
572
|
-
[
|
575
|
+
%i[grpc code],
|
573
576
|
'A number of successful requests to the Stackdriver Logging API',
|
574
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
577
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
578
|
+
)
|
575
579
|
@failed_requests_count = @registry.counter(
|
576
580
|
:stackdriver_failed_requests_count,
|
577
|
-
[
|
581
|
+
%i[grpc code],
|
578
582
|
'A number of failed requests to the Stackdriver Logging '\
|
579
583
|
'API, broken down by the error code',
|
580
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
584
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
585
|
+
)
|
581
586
|
@ingested_entries_count = @registry.counter(
|
582
587
|
:stackdriver_ingested_entries_count,
|
583
|
-
[
|
588
|
+
%i[grpc code],
|
584
589
|
'A number of log entries ingested by Stackdriver Logging',
|
585
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
590
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
591
|
+
)
|
586
592
|
@dropped_entries_count = @registry.counter(
|
587
593
|
:stackdriver_dropped_entries_count,
|
588
|
-
[
|
594
|
+
%i[grpc code],
|
589
595
|
'A number of log entries dropped by the Stackdriver output plugin',
|
590
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
596
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
597
|
+
)
|
591
598
|
@retried_entries_count = @registry.counter(
|
592
599
|
:stackdriver_retried_entries_count,
|
593
|
-
[
|
600
|
+
%i[grpc code],
|
594
601
|
'The number of log entries that failed to be ingested by '\
|
595
602
|
'the Stackdriver output plugin due to a transient error '\
|
596
603
|
'and were retried',
|
597
|
-
'agent.googleapis.com/agent', 'CUMULATIVE'
|
604
|
+
'agent.googleapis.com/agent', 'CUMULATIVE'
|
605
|
+
)
|
598
606
|
@ok_code = @use_grpc ? GRPC::Core::StatusCodes::OK : 200
|
599
607
|
end
|
600
608
|
|
@@ -625,12 +633,12 @@ module Fluent
|
|
625
633
|
@write_request = method(:write_request_via_rest)
|
626
634
|
end
|
627
635
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
636
|
+
return unless [Common::Platform::GCE, Common::Platform::EC2].include?(@platform)
|
637
|
+
|
638
|
+
# Log an informational message containing the Logs viewer URL
|
639
|
+
@log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
|
640
|
+
"viewer?project=#{@project_id}&resource=#{@resource.type}/",
|
641
|
+
"instance_id/#{@vm_id}"
|
634
642
|
end
|
635
643
|
|
636
644
|
def start
|
@@ -639,16 +647,16 @@ module Fluent
|
|
639
647
|
@successful_call = false
|
640
648
|
@timenanos_warning = false
|
641
649
|
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
650
|
+
return unless @statusz_port.positive?
|
651
|
+
|
652
|
+
@log.info "Starting statusz server on port #{@statusz_port}"
|
653
|
+
server_create(:out_google_cloud_statusz,
|
654
|
+
@statusz_port,
|
655
|
+
bind: '127.0.0.1') do |data, conn|
|
656
|
+
if data.split(' ')[1] == '/statusz'
|
657
|
+
write_html_response(data, conn, 200, Statusz.response(self))
|
658
|
+
else
|
659
|
+
write_html_response(data, conn, 404, "Not found\n")
|
652
660
|
end
|
653
661
|
end
|
654
662
|
end
|
@@ -657,7 +665,7 @@ module Fluent
|
|
657
665
|
super
|
658
666
|
# Export metrics on shutdown. This is a best-effort attempt, and it might
|
659
667
|
# fail, for instance if there was a recent write to the same time series.
|
660
|
-
@registry
|
668
|
+
@registry&.export
|
661
669
|
end
|
662
670
|
|
663
671
|
def write(chunk)
|
@@ -668,12 +676,14 @@ module Fluent
|
|
668
676
|
entries = []
|
669
677
|
group_level_resource, group_level_common_labels =
|
670
678
|
determine_group_level_monitored_resource_and_labels(
|
671
|
-
tag, local_resource_id
|
679
|
+
tag, local_resource_id
|
680
|
+
)
|
672
681
|
|
673
682
|
arr.each do |time, record|
|
674
683
|
entry_level_resource, entry_level_common_labels =
|
675
684
|
determine_entry_level_monitored_resource_and_labels(
|
676
|
-
group_level_resource, group_level_common_labels, record
|
685
|
+
group_level_resource, group_level_common_labels, record
|
686
|
+
)
|
677
687
|
|
678
688
|
is_json = false
|
679
689
|
if @detect_json
|
@@ -703,10 +713,8 @@ module Fluent
|
|
703
713
|
# unless there is additional metadata that would be lost.
|
704
714
|
record_json = nil
|
705
715
|
if (record.keys - preserved_keys).length == 1
|
706
|
-
%w
|
707
|
-
if record.key?(field)
|
708
|
-
record_json = parse_json_or_nil(record[field])
|
709
|
-
end
|
716
|
+
%w[log message msg].each do |field|
|
717
|
+
record_json = parse_json_or_nil(record[field]) if record.key?(field)
|
710
718
|
end
|
711
719
|
end
|
712
720
|
unless record_json.nil?
|
@@ -727,12 +735,16 @@ module Fluent
|
|
727
735
|
if @adjust_invalid_timestamps && timestamp
|
728
736
|
|
729
737
|
severity = compute_severity(
|
730
|
-
entry_level_resource.type, record, entry_level_common_labels
|
738
|
+
entry_level_resource.type, record, entry_level_common_labels
|
739
|
+
)
|
731
740
|
|
732
741
|
dynamic_labels_from_payload = parse_labels(record)
|
733
742
|
|
734
|
-
|
735
|
-
|
743
|
+
if dynamic_labels_from_payload
|
744
|
+
entry_level_common_labels.merge!(
|
745
|
+
dynamic_labels_from_payload
|
746
|
+
)
|
747
|
+
end
|
736
748
|
|
737
749
|
entry = @construct_log_entry.call(entry_level_common_labels,
|
738
750
|
entry_level_resource,
|
@@ -759,7 +771,8 @@ module Fluent
|
|
759
771
|
next if entries.empty?
|
760
772
|
|
761
773
|
log_name = "projects/#{@project_id}/logs/#{log_name(
|
762
|
-
tag, group_level_resource
|
774
|
+
tag, group_level_resource
|
775
|
+
)}"
|
763
776
|
|
764
777
|
requests_to_send << {
|
765
778
|
entries: entries,
|
@@ -803,7 +816,8 @@ module Fluent
|
|
803
816
|
now = Time.now.to_i
|
804
817
|
@uptime_metric.increment(
|
805
818
|
by: now - @uptime_update_time,
|
806
|
-
labels: { version: Fluent::GoogleCloudOutput.version_string }
|
819
|
+
labels: { version: Fluent::GoogleCloudOutput.version_string }
|
820
|
+
)
|
807
821
|
@uptime_update_time = now
|
808
822
|
end
|
809
823
|
|
@@ -823,6 +837,7 @@ module Fluent
|
|
823
837
|
def compute_trace(trace)
|
824
838
|
return trace unless @autoformat_stackdriver_trace &&
|
825
839
|
STACKDRIVER_TRACE_ID_REGEXP.match(trace)
|
840
|
+
|
826
841
|
"projects/#{@project_id}/traces/#{trace}"
|
827
842
|
end
|
828
843
|
|
@@ -901,10 +916,9 @@ module Fluent
|
|
901
916
|
@successful_call = true
|
902
917
|
@log.info 'Successfully sent gRPC to Stackdriver Logging API.'
|
903
918
|
end
|
904
|
-
|
905
|
-
rescue Google::Gax::GaxError => gax_error
|
919
|
+
rescue Google::Gax::GaxError => e
|
906
920
|
# GRPC::BadStatus is wrapped in error.cause.
|
907
|
-
error =
|
921
|
+
error = e.cause
|
908
922
|
|
909
923
|
# See the mapping between HTTP status and gRPC status code at:
|
910
924
|
# https://github.com/grpc/grpc/blob/master/src/core/lib/transport/status_conversion.cc
|
@@ -959,7 +973,7 @@ module Fluent
|
|
959
973
|
GRPC::InvalidArgument,
|
960
974
|
# HTTP status 403 (Forbidden).
|
961
975
|
GRPC::PermissionDenied
|
962
|
-
error_details_map = construct_error_details_map_grpc(
|
976
|
+
error_details_map = construct_error_details_map_grpc(e)
|
963
977
|
if error_details_map.empty?
|
964
978
|
increment_failed_requests_count(error.code)
|
965
979
|
increment_dropped_entries_count(entries_count, error.code)
|
@@ -995,13 +1009,13 @@ module Fluent
|
|
995
1009
|
|
996
1010
|
# Got an unexpected error (not Google::Gax::GaxError) from the
|
997
1011
|
# google-cloud-logging lib.
|
998
|
-
rescue StandardError =>
|
1012
|
+
rescue StandardError => e
|
999
1013
|
increment_failed_requests_count(GRPC::Core::StatusCodes::UNKNOWN)
|
1000
1014
|
increment_dropped_entries_count(entries_count,
|
1001
1015
|
GRPC::Core::StatusCodes::UNKNOWN)
|
1002
|
-
@log.error "Unexpected error type #{
|
1016
|
+
@log.error "Unexpected error type #{e.class.name} from the client" \
|
1003
1017
|
" library, dropping #{entries_count} log message(s)",
|
1004
|
-
error:
|
1018
|
+
error: e.to_s
|
1005
1019
|
end
|
1006
1020
|
|
1007
1021
|
def write_request_via_rest(entries:,
|
@@ -1029,32 +1043,29 @@ module Fluent
|
|
1029
1043
|
@successful_call = true
|
1030
1044
|
@log.info 'Successfully sent to Stackdriver Logging API.'
|
1031
1045
|
end
|
1032
|
-
|
1033
|
-
rescue Google::Apis::ServerError => error
|
1046
|
+
rescue Google::Apis::ServerError => e
|
1034
1047
|
# 5xx server errors. Retry via re-raising the error.
|
1035
|
-
increment_retried_entries_count(entries_count,
|
1048
|
+
increment_retried_entries_count(entries_count, e.status_code)
|
1036
1049
|
@log.debug "Retrying #{entries_count} log message(s) later.",
|
1037
|
-
error:
|
1038
|
-
raise
|
1039
|
-
|
1040
|
-
rescue Google::Apis::AuthorizationError => error
|
1050
|
+
error: e.to_s, error_code: e.status_code.to_s
|
1051
|
+
raise e
|
1052
|
+
rescue Google::Apis::AuthorizationError => e
|
1041
1053
|
# 401 authorization error.
|
1042
1054
|
# These are usually solved via a `gcloud auth` call, or by modifying
|
1043
1055
|
# the permissions on the Google Cloud project.
|
1044
|
-
increment_failed_requests_count(
|
1045
|
-
increment_dropped_entries_count(entries_count,
|
1056
|
+
increment_failed_requests_count(e.status_code)
|
1057
|
+
increment_dropped_entries_count(entries_count, e.status_code)
|
1046
1058
|
@log.warn "Dropping #{entries_count} log message(s)",
|
1047
|
-
error:
|
1048
|
-
|
1049
|
-
rescue Google::Apis::ClientError => error
|
1059
|
+
error: e.to_s, error_code: e.status_code.to_s
|
1060
|
+
rescue Google::Apis::ClientError => e
|
1050
1061
|
# 4xx client errors. Most client errors indicate a problem with the
|
1051
1062
|
# request itself and should not be retried.
|
1052
|
-
error_details_map = construct_error_details_map(
|
1063
|
+
error_details_map = construct_error_details_map(e)
|
1053
1064
|
if error_details_map.empty?
|
1054
|
-
increment_failed_requests_count(
|
1055
|
-
increment_dropped_entries_count(entries_count,
|
1065
|
+
increment_failed_requests_count(e.status_code)
|
1066
|
+
increment_dropped_entries_count(entries_count, e.status_code)
|
1056
1067
|
@log.warn "Dropping #{entries_count} log message(s)",
|
1057
|
-
error:
|
1068
|
+
error: e.to_s, error_code: e.status_code.to_s
|
1058
1069
|
else
|
1059
1070
|
error_details_map.each do |(error_code, error_message), indexes|
|
1060
1071
|
partial_errors_count = indexes.length
|
@@ -1086,9 +1097,9 @@ module Fluent
|
|
1086
1097
|
# in which case we continue to look for a left curly bracket.
|
1087
1098
|
# Whitespace as per the JSON spec are: tabulation (U+0009),
|
1088
1099
|
# line feed (U+000A), carriage return (U+000D), and space (U+0020).
|
1089
|
-
break unless
|
1090
|
-
end
|
1091
|
-
end
|
1100
|
+
break unless [9, 10, 13, 32].include?(c)
|
1101
|
+
end
|
1102
|
+
end
|
1092
1103
|
nil
|
1093
1104
|
end
|
1094
1105
|
|
@@ -1191,7 +1202,8 @@ module Fluent
|
|
1191
1202
|
# "k8s_container.<namespace_name>.<pod_name>.<container_name>"
|
1192
1203
|
if local_resource_id
|
1193
1204
|
converted_resource = monitored_resource_from_local_resource_id(
|
1194
|
-
local_resource_id
|
1205
|
+
local_resource_id
|
1206
|
+
)
|
1195
1207
|
resource = converted_resource if converted_resource
|
1196
1208
|
end
|
1197
1209
|
|
@@ -1213,13 +1225,17 @@ module Fluent
|
|
1213
1225
|
# constants like GKE_CONSTANTS[:extra_resource_labels].
|
1214
1226
|
'container_name' => 'container_name',
|
1215
1227
|
'namespace_name' => 'namespace_id',
|
1216
|
-
'pod_name' => 'pod_id'
|
1228
|
+
'pod_name' => 'pod_id'
|
1229
|
+
)
|
1230
|
+
)
|
1217
1231
|
|
1218
1232
|
common_labels.merge!(
|
1219
1233
|
delete_and_extract_labels(
|
1220
1234
|
common_labels_candidates,
|
1221
1235
|
GKE_CONSTANTS[:extra_common_labels]
|
1222
|
-
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h
|
1236
|
+
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h
|
1237
|
+
)
|
1238
|
+
)
|
1223
1239
|
end
|
1224
1240
|
|
1225
1241
|
# TODO(qingling128): Temporary fallback for metadata agent restarts.
|
@@ -1237,10 +1253,13 @@ module Fluent
|
|
1237
1253
|
# e.g. "dataflow.googleapis.com/job_id" => "job_id"
|
1238
1254
|
[DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
|
1239
1255
|
next unless resource.type == service_constants[:resource_type]
|
1256
|
+
|
1240
1257
|
resource.labels.merge!(
|
1241
1258
|
delete_and_extract_labels(
|
1242
1259
|
common_labels, service_constants[:extra_resource_labels]
|
1243
|
-
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h
|
1260
|
+
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h
|
1261
|
+
)
|
1262
|
+
)
|
1244
1263
|
end
|
1245
1264
|
|
1246
1265
|
resource.freeze
|
@@ -1253,7 +1272,8 @@ module Fluent
|
|
1253
1272
|
# Extract entry level monitored resource and common labels that should be
|
1254
1273
|
# applied to individual entries.
|
1255
1274
|
def determine_entry_level_monitored_resource_and_labels(
|
1256
|
-
group_level_resource, group_level_common_labels, record
|
1275
|
+
group_level_resource, group_level_common_labels, record
|
1276
|
+
)
|
1257
1277
|
resource = group_level_resource.dup
|
1258
1278
|
resource.labels = group_level_resource.labels.dup
|
1259
1279
|
common_labels = group_level_common_labels.dup
|
@@ -1264,7 +1284,9 @@ module Fluent
|
|
1264
1284
|
# Move the stdout/stderr annotation from the record into a label.
|
1265
1285
|
common_labels.merge!(
|
1266
1286
|
delete_and_extract_labels(
|
1267
|
-
record, 'stream' => "#{GKE_CONSTANTS[:service]}/stream"
|
1287
|
+
record, 'stream' => "#{GKE_CONSTANTS[:service]}/stream"
|
1288
|
+
)
|
1289
|
+
)
|
1268
1290
|
|
1269
1291
|
# If the record has been annotated by the kubernetes_metadata_filter
|
1270
1292
|
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
@@ -1273,17 +1295,23 @@ module Fluent
|
|
1273
1295
|
resource.labels.merge!(
|
1274
1296
|
delete_and_extract_labels(
|
1275
1297
|
record['kubernetes'], GKE_CONSTANTS[:extra_resource_labels]
|
1276
|
-
.map { |l| [l, l] }.to_h
|
1298
|
+
.map { |l| [l, l] }.to_h
|
1299
|
+
)
|
1300
|
+
)
|
1277
1301
|
common_labels.merge!(
|
1278
1302
|
delete_and_extract_labels(
|
1279
1303
|
record['kubernetes'], GKE_CONSTANTS[:extra_common_labels]
|
1280
|
-
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h
|
1304
|
+
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h
|
1305
|
+
)
|
1306
|
+
)
|
1281
1307
|
# Prepend label/ to all user-defined labels' keys.
|
1282
1308
|
if record['kubernetes'].key?('labels')
|
1283
1309
|
common_labels.merge!(
|
1284
1310
|
delete_and_extract_labels(
|
1285
1311
|
record['kubernetes']['labels'], record['kubernetes']['labels']
|
1286
|
-
.map { |key, _| [key, "label/#{key}"] }.to_h
|
1312
|
+
.map { |key, _| [key, "label/#{key}"] }.to_h
|
1313
|
+
)
|
1314
|
+
)
|
1287
1315
|
end
|
1288
1316
|
# We've explicitly consumed all the fields we care about -- don't
|
1289
1317
|
# litter the log entries with the remaining fields that the kubernetes
|
@@ -1304,10 +1332,13 @@ module Fluent
|
|
1304
1332
|
# e.g. "dataflow.googleapis.com/job_id" => "job_id"
|
1305
1333
|
[DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
|
1306
1334
|
next unless resource.type == service_constants[:resource_type]
|
1335
|
+
|
1307
1336
|
resource.labels.merge!(
|
1308
1337
|
delete_and_extract_labels(
|
1309
1338
|
common_labels, service_constants[:extra_resource_labels]
|
1310
|
-
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h
|
1339
|
+
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h
|
1340
|
+
)
|
1341
|
+
)
|
1311
1342
|
end
|
1312
1343
|
|
1313
1344
|
[resource, common_labels]
|
@@ -1350,7 +1381,7 @@ module Fluent
|
|
1350
1381
|
# k8s ISO8601 timestamp
|
1351
1382
|
begin
|
1352
1383
|
timestamp = Time.iso8601(record.delete('time'))
|
1353
|
-
rescue
|
1384
|
+
rescue StandardError
|
1354
1385
|
timestamp = Time.at(time)
|
1355
1386
|
end
|
1356
1387
|
ts_secs = timestamp.tv_sec
|
@@ -1361,15 +1392,15 @@ module Fluent
|
|
1361
1392
|
ts_nanos = timestamp.tv_nsec
|
1362
1393
|
end
|
1363
1394
|
ts_secs = begin
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1395
|
+
Integer ts_secs
|
1396
|
+
rescue ArgumentError, TypeError
|
1397
|
+
ts_secs
|
1398
|
+
end
|
1368
1399
|
ts_nanos = begin
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1400
|
+
Integer ts_nanos
|
1401
|
+
rescue ArgumentError, TypeError
|
1402
|
+
ts_nanos
|
1403
|
+
end
|
1373
1404
|
|
1374
1405
|
[ts_secs, ts_nanos, timestamp]
|
1375
1406
|
end
|
@@ -1420,6 +1451,7 @@ module Fluent
|
|
1420
1451
|
stream = entry_level_common_labels["#{GKE_CONSTANTS[:service]}/stream"]
|
1421
1452
|
return GKE_CONSTANTS[:stream_severity_map].fetch(stream, 'DEFAULT')
|
1422
1453
|
end
|
1454
|
+
|
1423
1455
|
'DEFAULT'
|
1424
1456
|
end
|
1425
1457
|
|
@@ -1440,6 +1472,7 @@ module Fluent
|
|
1440
1472
|
do |(original_key, destination_key, cast_fn), extracted_fields|
|
1441
1473
|
value = fields.delete(original_key)
|
1442
1474
|
next if value.nil?
|
1475
|
+
|
1443
1476
|
begin
|
1444
1477
|
casted_value = send(cast_fn, value)
|
1445
1478
|
rescue TypeError
|
@@ -1448,6 +1481,7 @@ module Fluent
|
|
1448
1481
|
next
|
1449
1482
|
end
|
1450
1483
|
next if casted_value.nil?
|
1484
|
+
|
1451
1485
|
extracted_fields[destination_key] = casted_value
|
1452
1486
|
end
|
1453
1487
|
|
@@ -1465,8 +1499,8 @@ module Fluent
|
|
1465
1499
|
record.delete(payload_key) if fields.empty?
|
1466
1500
|
|
1467
1501
|
entry.send("#{field_name}=", output)
|
1468
|
-
rescue StandardError =>
|
1469
|
-
@log.error "Failed to set log entry field for #{field_name}.",
|
1502
|
+
rescue StandardError => e
|
1503
|
+
@log.error "Failed to set log entry field for #{field_name}.", e
|
1470
1504
|
end
|
1471
1505
|
end
|
1472
1506
|
end
|
@@ -1475,6 +1509,7 @@ module Fluent
|
|
1475
1509
|
def parse_labels(record)
|
1476
1510
|
payload_labels = record.delete(@labels_key)
|
1477
1511
|
return nil unless payload_labels
|
1512
|
+
|
1478
1513
|
unless payload_labels.is_a?(Hash)
|
1479
1514
|
@log.error "Invalid value of '#{@labels_key}' in the payload: " \
|
1480
1515
|
"#{payload_labels}. Labels need to be a JSON object."
|
@@ -1491,14 +1526,14 @@ module Fluent
|
|
1491
1526
|
return nil
|
1492
1527
|
end
|
1493
1528
|
payload_labels
|
1494
|
-
rescue StandardError =>
|
1495
|
-
@log.error "Failed to extract '#{@labels_key}' from payload.",
|
1496
|
-
|
1529
|
+
rescue StandardError => e
|
1530
|
+
@log.error "Failed to extract '#{@labels_key}' from payload.", e
|
1531
|
+
nil
|
1497
1532
|
end
|
1498
1533
|
|
1499
1534
|
# Values permitted by the API for 'severity' (which is an enum).
|
1500
1535
|
VALID_SEVERITIES = Set.new(
|
1501
|
-
%w
|
1536
|
+
%w[DEFAULT DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY]
|
1502
1537
|
).freeze
|
1503
1538
|
|
1504
1539
|
# Translates other severity strings to one of the valid values above.
|
@@ -1544,22 +1579,20 @@ module Fluent
|
|
1544
1579
|
begin
|
1545
1580
|
numeric_severity = (severity.to_i / 100) * 100
|
1546
1581
|
case
|
1547
|
-
when numeric_severity
|
1582
|
+
when numeric_severity.negative?
|
1548
1583
|
return 0
|
1549
1584
|
when numeric_severity > 800
|
1550
1585
|
return 800
|
1551
1586
|
else
|
1552
1587
|
return numeric_severity
|
1553
1588
|
end
|
1554
|
-
rescue
|
1589
|
+
rescue StandardError
|
1555
1590
|
return 'DEFAULT'
|
1556
1591
|
end
|
1557
1592
|
end
|
1558
1593
|
|
1559
1594
|
# Try to translate the severity.
|
1560
|
-
if SEVERITY_TRANSLATIONS.key?(severity)
|
1561
|
-
return SEVERITY_TRANSLATIONS[severity]
|
1562
|
-
end
|
1595
|
+
return SEVERITY_TRANSLATIONS[severity] if SEVERITY_TRANSLATIONS.key?(severity)
|
1563
1596
|
|
1564
1597
|
# If all else fails, use 'DEFAULT'.
|
1565
1598
|
'DEFAULT'
|
@@ -1591,9 +1624,8 @@ module Fluent
|
|
1591
1624
|
# if severity.is_a? String
|
1592
1625
|
# return Google::Logging::Type::LogSeverity.resolve(severity)
|
1593
1626
|
# end
|
1594
|
-
if GRPC_SEVERITY_MAPPING.key?(severity)
|
1595
|
-
|
1596
|
-
end
|
1627
|
+
return GRPC_SEVERITY_MAPPING[severity] if GRPC_SEVERITY_MAPPING.key?(severity)
|
1628
|
+
|
1597
1629
|
severity
|
1598
1630
|
end
|
1599
1631
|
|
@@ -1624,15 +1656,15 @@ module Fluent
|
|
1624
1656
|
seconds = match['seconds'].to_i
|
1625
1657
|
nanos = (match['decimal'].to_f * 1000 * 1000 * 1000).round
|
1626
1658
|
if @use_grpc
|
1627
|
-
|
1659
|
+
Google::Protobuf::Duration.new(
|
1628
1660
|
seconds: seconds,
|
1629
1661
|
nanos: nanos
|
1630
1662
|
)
|
1631
1663
|
else
|
1632
|
-
|
1664
|
+
{
|
1633
1665
|
seconds: seconds,
|
1634
1666
|
nanos: nanos
|
1635
|
-
}.delete_if { |_, v| v
|
1667
|
+
}.delete_if { |_, v| v.zero? }
|
1636
1668
|
end
|
1637
1669
|
end
|
1638
1670
|
|
@@ -1653,6 +1685,7 @@ module Fluent
|
|
1653
1685
|
(!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
|
1654
1686
|
return nil
|
1655
1687
|
end
|
1688
|
+
|
1656
1689
|
tag = convert_to_utf8(tag.to_s)
|
1657
1690
|
tag = '_' if tag == ''
|
1658
1691
|
tag
|
@@ -1664,6 +1697,7 @@ module Fluent
|
|
1664
1697
|
def delete_and_extract_labels(hash, label_map)
|
1665
1698
|
return {} if label_map.nil? || !label_map.is_a?(Hash) ||
|
1666
1699
|
hash.nil? || !hash.is_a?(Hash)
|
1700
|
+
|
1667
1701
|
label_map.each_with_object({}) \
|
1668
1702
|
do |(original_label, new_label), extracted_labels|
|
1669
1703
|
value = hash.delete(original_label)
|
@@ -1758,13 +1792,12 @@ module Fluent
|
|
1758
1792
|
elsif resource.type == GKE_CONSTANTS[:resource_type]
|
1759
1793
|
# For Kubernetes logs, use just the container name as the log name
|
1760
1794
|
# if we have it.
|
1761
|
-
if resource.labels
|
1795
|
+
if resource.labels&.key?('container_name')
|
1762
1796
|
sanitized_tag = sanitize_tag(resource.labels['container_name'])
|
1763
1797
|
tag = sanitized_tag unless sanitized_tag.nil?
|
1764
1798
|
end
|
1765
1799
|
end
|
1766
|
-
|
1767
|
-
tag
|
1800
|
+
ERB::Util.url_encode(tag)
|
1768
1801
|
end
|
1769
1802
|
|
1770
1803
|
def init_api_client
|
@@ -1781,7 +1814,8 @@ module Fluent
|
|
1781
1814
|
if @grpc_compression_algorithm
|
1782
1815
|
compression_options =
|
1783
1816
|
GRPC::Core::CompressionOptions.new(
|
1784
|
-
default_algorithm: @grpc_compression_algorithm
|
1817
|
+
default_algorithm: @grpc_compression_algorithm
|
1818
|
+
)
|
1785
1819
|
compression_channel_args = compression_options.to_channel_arg_hash
|
1786
1820
|
else
|
1787
1821
|
compression_channel_args = {}
|
@@ -1802,14 +1836,17 @@ module Fluent
|
|
1802
1836
|
.merge!(compression_channel_args)
|
1803
1837
|
@client = Google::Cloud::Logging::V2::LoggingServiceV2Client.new(
|
1804
1838
|
credentials: GRPC::Core::Channel.new(
|
1805
|
-
"#{host}#{port}", channel_args, creds
|
1839
|
+
"#{host}#{port}", channel_args, creds
|
1840
|
+
)
|
1841
|
+
)
|
1806
1842
|
else
|
1807
1843
|
# TODO: Use a non-default ClientOptions object.
|
1808
1844
|
Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
|
1809
1845
|
Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
|
1810
1846
|
@client = Google::Apis::LoggingV2::LoggingService.new
|
1811
1847
|
@client.authorization = Google::Auth.get_application_default(
|
1812
|
-
Common::LOGGING_SCOPE
|
1848
|
+
Common::LOGGING_SCOPE
|
1849
|
+
)
|
1813
1850
|
end
|
1814
1851
|
end
|
1815
1852
|
|
@@ -1839,7 +1876,8 @@ module Fluent
|
|
1839
1876
|
'utf-8',
|
1840
1877
|
invalid: :replace,
|
1841
1878
|
undef: :replace,
|
1842
|
-
replace: @non_utf8_replacement_string
|
1879
|
+
replace: @non_utf8_replacement_string
|
1880
|
+
)
|
1843
1881
|
else
|
1844
1882
|
begin
|
1845
1883
|
input.encode('utf-8')
|
@@ -1932,19 +1970,22 @@ module Fluent
|
|
1932
1970
|
error_details_map = Hash.new { |h, k| h[k] = [] }
|
1933
1971
|
|
1934
1972
|
error_details = ensure_array(
|
1935
|
-
ensure_hash(ensure_hash(JSON.parse(error.body))['error'])['details']
|
1973
|
+
ensure_hash(ensure_hash(JSON.parse(error.body))['error'])['details']
|
1974
|
+
)
|
1936
1975
|
partial_errors = error_details.detect(
|
1937
1976
|
-> { raise JSON::ParserError, "No type #{PARTIAL_ERROR_FIELD}." }
|
1938
1977
|
) do |error_detail|
|
1939
1978
|
ensure_hash(error_detail)['@type'] == PARTIAL_ERROR_FIELD
|
1940
1979
|
end
|
1941
1980
|
log_entry_errors = ensure_hash(
|
1942
|
-
ensure_hash(partial_errors)['logEntryErrors']
|
1981
|
+
ensure_hash(partial_errors)['logEntryErrors']
|
1982
|
+
)
|
1943
1983
|
log_entry_errors.each do |index, log_entry_error|
|
1944
1984
|
error_hash = ensure_hash(log_entry_error)
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1985
|
+
unless error_hash['code'] && error_hash['message']
|
1986
|
+
raise JSON::ParserError,
|
1987
|
+
"Entry #{index} is missing 'code' or 'message'."
|
1988
|
+
end
|
1948
1989
|
error_key = [error_hash['code'], error_hash['message']].freeze
|
1949
1990
|
# TODO(qingling128): Convert indexes to integers.
|
1950
1991
|
error_details_map[error_key] << index
|
@@ -2003,7 +2044,9 @@ module Fluent
|
|
2003
2044
|
error_details.empty?
|
2004
2045
|
raise JSON::ParserError, 'No partial error info in error details.' unless
|
2005
2046
|
error_details[0].is_a?(
|
2006
|
-
Google::Logging::V2::WriteLogEntriesPartialErrors
|
2047
|
+
Google::Logging::V2::WriteLogEntriesPartialErrors
|
2048
|
+
)
|
2049
|
+
|
2007
2050
|
log_entry_errors = ensure_hash(error_details[0].log_entry_errors)
|
2008
2051
|
log_entry_errors.each do |index, log_entry_error|
|
2009
2052
|
error_key = [log_entry_error[:code], log_entry_error[:message]].freeze
|
@@ -2039,9 +2082,11 @@ module Fluent
|
|
2039
2082
|
|
2040
2083
|
begin
|
2041
2084
|
@k8s_cluster_name ||= @utils.fetch_gce_metadata(
|
2042
|
-
@platform, 'instance/attributes/cluster-name'
|
2085
|
+
@platform, 'instance/attributes/cluster-name'
|
2086
|
+
)
|
2043
2087
|
@k8s_cluster_location ||= @utils.fetch_gce_metadata(
|
2044
|
-
@platform, 'instance/attributes/cluster-location'
|
2088
|
+
@platform, 'instance/attributes/cluster-location'
|
2089
|
+
)
|
2045
2090
|
rescue StandardError => e
|
2046
2091
|
@log.error 'Failed to retrieve k8s cluster name and location.', \
|
2047
2092
|
error: e
|
@@ -2080,7 +2125,8 @@ module Fluent
|
|
2080
2125
|
end
|
2081
2126
|
constructed_resource = Google::Apis::LoggingV2::MonitoredResource.new(
|
2082
2127
|
type: resource_type,
|
2083
|
-
labels: labels
|
2128
|
+
labels: labels
|
2129
|
+
)
|
2084
2130
|
@log.debug("Constructed #{resource_type} resource locally: " \
|
2085
2131
|
"#{constructed_resource.inspect}")
|
2086
2132
|
constructed_resource
|
@@ -2099,40 +2145,50 @@ module Fluent
|
|
2099
2145
|
# Increment the metric for the number of successful requests.
|
2100
2146
|
def increment_successful_requests_count
|
2101
2147
|
return unless @successful_requests_count
|
2148
|
+
|
2102
2149
|
@successful_requests_count.increment(
|
2103
|
-
labels: { grpc: @use_grpc, code: @ok_code }
|
2150
|
+
labels: { grpc: @use_grpc, code: @ok_code }
|
2151
|
+
)
|
2104
2152
|
end
|
2105
2153
|
|
2106
2154
|
# Increment the metric for the number of failed requests, labeled by
|
2107
2155
|
# the provided status code.
|
2108
2156
|
def increment_failed_requests_count(code)
|
2109
2157
|
return unless @failed_requests_count
|
2158
|
+
|
2110
2159
|
@failed_requests_count.increment(
|
2111
|
-
labels: { grpc: @use_grpc, code: code }
|
2160
|
+
labels: { grpc: @use_grpc, code: code }
|
2161
|
+
)
|
2112
2162
|
end
|
2113
2163
|
|
2114
2164
|
# Increment the metric for the number of log entries, successfully
|
2115
2165
|
# ingested by the Stackdriver Logging API.
|
2116
2166
|
def increment_ingested_entries_count(count)
|
2117
2167
|
return unless @ingested_entries_count
|
2168
|
+
|
2118
2169
|
@ingested_entries_count.increment(
|
2119
|
-
labels: { grpc: @use_grpc, code: @ok_code }, by: count
|
2170
|
+
labels: { grpc: @use_grpc, code: @ok_code }, by: count
|
2171
|
+
)
|
2120
2172
|
end
|
2121
2173
|
|
2122
2174
|
# Increment the metric for the number of log entries that were dropped
|
2123
2175
|
# and not ingested by the Stackdriver Logging API.
|
2124
2176
|
def increment_dropped_entries_count(count, code)
|
2125
2177
|
return unless @dropped_entries_count
|
2178
|
+
|
2126
2179
|
@dropped_entries_count.increment(
|
2127
|
-
labels: { grpc: @use_grpc, code: code }, by: count
|
2180
|
+
labels: { grpc: @use_grpc, code: code }, by: count
|
2181
|
+
)
|
2128
2182
|
end
|
2129
2183
|
|
2130
2184
|
# Increment the metric for the number of log entries that were dropped
|
2131
2185
|
# and not ingested by the Stackdriver Logging API.
|
2132
2186
|
def increment_retried_entries_count(count, code)
|
2133
2187
|
return unless @retried_entries_count
|
2188
|
+
|
2134
2189
|
@retried_entries_count.increment(
|
2135
|
-
labels: { grpc: @use_grpc, code: code }, by: count
|
2190
|
+
labels: { grpc: @use_grpc, code: code }, by: count
|
2191
|
+
)
|
2136
2192
|
end
|
2137
2193
|
end
|
2138
2194
|
end
|