fluent-plugin-google-cloud 0.7.15 → 0.7.16
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 +36 -38
- data/fluent-plugin-google-cloud.gemspec +20 -15
- data/lib/fluent/plugin/monitoring.rb +5 -1
- data/lib/fluent/plugin/out_google_cloud.rb +27 -5
- data/lib/fluent/plugin/statusz.rb +106 -10
- data/test/plugin/base_test.rb +158 -188
- data/test/plugin/constants.rb +71 -6
- data/test/plugin/test_out_google_cloud.rb +72 -63
- data/test/plugin/test_out_google_cloud_grpc.rb +41 -98
- metadata +40 -40
data/test/plugin/constants.rb
CHANGED
@@ -464,6 +464,53 @@ module Constants
|
|
464
464
|
#{CONFIG_LABEL_MAP_CONFLICTING}
|
465
465
|
).freeze
|
466
466
|
|
467
|
+
# For monitoring config.
|
468
|
+
CONFIG_UNKNOWN_MONITORING_TYPE = %(
|
469
|
+
enable_monitoring true
|
470
|
+
monitoring_type not_prometheus
|
471
|
+
).freeze
|
472
|
+
|
473
|
+
# For statusz.
|
474
|
+
CONFIG_STATUSZ = %(
|
475
|
+
statusz_port 5678
|
476
|
+
|
477
|
+
adjust_invalid_timestamps false
|
478
|
+
autoformat_stackdriver_trace false
|
479
|
+
coerce_to_utf8 false
|
480
|
+
detect_json true
|
481
|
+
detect_subservice false
|
482
|
+
enable_metadata_agent true
|
483
|
+
enable_monitoring true
|
484
|
+
http_request_key test_http_request_key
|
485
|
+
insert_id_key test_insert_id_key
|
486
|
+
k8s_cluster_location test-k8s-cluster-location
|
487
|
+
k8s_cluster_name test-k8s-cluster-name
|
488
|
+
kubernetes_tag_regexp .*test-regexp.*
|
489
|
+
label_map { "label_map_key": "label_map_value" }
|
490
|
+
labels_key test_labels_key
|
491
|
+
labels { "labels_key": "labels_value" }
|
492
|
+
logging_api_url http://localhost:52000
|
493
|
+
metadata_agent_url http://localhost:12345
|
494
|
+
monitoring_type not_prometheus
|
495
|
+
non_utf8_replacement_string zzz
|
496
|
+
operation_key test_operation_key
|
497
|
+
partial_success false
|
498
|
+
project_id test-project-id-123
|
499
|
+
require_valid_tags true
|
500
|
+
source_location_key test_source_location_key
|
501
|
+
span_id_key test_span_id_key
|
502
|
+
split_logs_by_tag true
|
503
|
+
subservice_name test_subservice_name
|
504
|
+
trace_key test_trace_key
|
505
|
+
trace_sampled_key test_trace_sampled_key
|
506
|
+
use_aws_availability_zone false
|
507
|
+
use_grpc true
|
508
|
+
use_metadata_service false
|
509
|
+
vm_id 12345
|
510
|
+
vm_name test.hostname.org
|
511
|
+
zone asia-east2
|
512
|
+
).freeze
|
513
|
+
|
467
514
|
# Service configurations for various services.
|
468
515
|
|
469
516
|
# GCE.
|
@@ -798,16 +845,16 @@ module Constants
|
|
798
845
|
).freeze
|
799
846
|
|
800
847
|
HTTP_REQUEST_MESSAGE = {
|
801
|
-
'cacheFillBytes' => 6653,
|
848
|
+
'cacheFillBytes' => '6653',
|
802
849
|
'cacheHit' => true,
|
803
850
|
'cacheLookup' => true,
|
804
851
|
'cacheValidatedWithOriginServer' => true,
|
805
852
|
'protocol' => 'HTTP/1.1',
|
806
853
|
'referer' => 'http://referer/',
|
807
854
|
'remoteIp' => '55.55.55.55',
|
808
|
-
'responseSize' => 65,
|
855
|
+
'responseSize' => '65',
|
809
856
|
'requestMethod' => 'POST',
|
810
|
-
'requestSize' => 210,
|
857
|
+
'requestSize' => '210',
|
811
858
|
'requestUrl' => 'http://example/',
|
812
859
|
'serverIp' => '66.66.66.66',
|
813
860
|
'status' => 200,
|
@@ -817,13 +864,13 @@ module Constants
|
|
817
864
|
SOURCE_LOCATION_MESSAGE = {
|
818
865
|
'file' => 'source/file',
|
819
866
|
'function' => 'my_function',
|
820
|
-
'line' => 18
|
867
|
+
'line' => '18'
|
821
868
|
}.freeze
|
822
869
|
|
823
870
|
SOURCE_LOCATION_MESSAGE2 = {
|
824
871
|
'file' => 'src/file',
|
825
872
|
'function' => 'my_func',
|
826
|
-
'line' => 8
|
873
|
+
'line' => '8'
|
827
874
|
}.freeze
|
828
875
|
|
829
876
|
OPERATION_MESSAGE = {
|
@@ -1056,8 +1103,26 @@ module Constants
|
|
1056
1103
|
}.freeze
|
1057
1104
|
end
|
1058
1105
|
|
1106
|
+
PRESERVED_KEYS_TIMESTAMP_FIELDS = [
|
1107
|
+
{
|
1108
|
+
'time' => K8S_TIMESTAMP
|
1109
|
+
},
|
1110
|
+
{
|
1111
|
+
'timeNanos' => K8S_NANOS
|
1112
|
+
},
|
1113
|
+
{
|
1114
|
+
'timestamp' => {
|
1115
|
+
'nanos' => K8S_NANOS,
|
1116
|
+
'seconds' => K8S_SECONDS_EPOCH
|
1117
|
+
}
|
1118
|
+
},
|
1119
|
+
{
|
1120
|
+
'timestampNanos' => K8S_NANOS,
|
1121
|
+
'timestampSeconds' => K8S_SECONDS_EPOCH
|
1122
|
+
}
|
1123
|
+
].freeze
|
1124
|
+
|
1059
1125
|
PRESERVED_KEYS_MAP = {
|
1060
|
-
'time' => K8S_TIMESTAMP,
|
1061
1126
|
'severity' => CONTAINER_SEVERITY,
|
1062
1127
|
DEFAULT_HTTP_REQUEST_KEY => HTTP_REQUEST_MESSAGE,
|
1063
1128
|
DEFAULT_INSERT_ID_KEY => INSERT_ID,
|
@@ -339,10 +339,54 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
339
339
|
WebMock.disable_net_connect!(allow_localhost: true)
|
340
340
|
# TODO(davidbtucker): Consider searching for an unused port
|
341
341
|
# instead of hardcoding a constant here.
|
342
|
-
d = create_driver(
|
342
|
+
d = create_driver(CONFIG_STATUSZ)
|
343
343
|
d.run do
|
344
|
-
|
345
|
-
|
344
|
+
resp = Net::HTTP.get('127.0.0.1', '/statusz', 5678)
|
345
|
+
must_match = [
|
346
|
+
'<h1>Status for .*</h1>.*',
|
347
|
+
|
348
|
+
'\badjust_invalid_timestamps\b.*\bfalse\b',
|
349
|
+
'\bautoformat_stackdriver_trace\b.*\bfalse\b',
|
350
|
+
'\bcoerce_to_utf8\b.*\bfalse\b',
|
351
|
+
'\bdetect_json\b.*\btrue\b',
|
352
|
+
'\bdetect_subservice\b.*\bfalse\b',
|
353
|
+
'\benable_metadata_agent\b.*\btrue\b',
|
354
|
+
'\benable_monitoring\b.*\btrue\b',
|
355
|
+
'\bhttp_request_key\b.*\btest_http_request_key\b',
|
356
|
+
'\binsert_id_key\b.*\btest_insert_id_key\b',
|
357
|
+
'\bk8s_cluster_location\b.*\btest-k8s-cluster-location\b',
|
358
|
+
'\bk8s_cluster_name\b.*\btest-k8s-cluster-name\b',
|
359
|
+
'\bkubernetes_tag_regexp\b.*\b.*test-regexp.*\b',
|
360
|
+
'\blabel_map\b.*{"label_map_key"=>"label_map_value"}',
|
361
|
+
'\blabels_key\b.*\btest_labels_key\b',
|
362
|
+
'\blabels\b.*{"labels_key"=>"labels_value"}',
|
363
|
+
'\blogging_api_url\b.*\bhttp://localhost:52000\b',
|
364
|
+
'\bmetadata_agent_url\b.*\bhttp://localhost:12345\b',
|
365
|
+
'\bmonitoring_type\b.*\bnot_prometheus\b',
|
366
|
+
'\bnon_utf8_replacement_string\b.*\bzzz\b',
|
367
|
+
'\boperation_key\b.*\btest_operation_key\b',
|
368
|
+
'\bpartial_success\b.*\bfalse\b',
|
369
|
+
'\bproject_id\b.*\btest-project-id-123\b',
|
370
|
+
'\brequire_valid_tags\b.*\btrue\b',
|
371
|
+
'\bsource_location_key\b.*\btest_source_location_key\b',
|
372
|
+
'\bspan_id_key\b.*\btest_span_id_key\b',
|
373
|
+
'\bsplit_logs_by_tag\b.*\btrue\b',
|
374
|
+
'\bstatusz_port\b.*\b5678\b',
|
375
|
+
'\bsubservice_name\b.*\btest_subservice_name\b',
|
376
|
+
'\btrace_key\b.*\btest_trace_key\b',
|
377
|
+
'\btrace_sampled_key\b.*\btest_trace_sampled_key\b',
|
378
|
+
'\buse_aws_availability_zone\b.*\bfalse\b',
|
379
|
+
'\buse_grpc\b.*\btrue\b',
|
380
|
+
'\buse_metadata_service\b.*\bfalse\b',
|
381
|
+
'\bvm_id\b.*\b12345\b',
|
382
|
+
'\bvm_name\b.*\btest.hostname.org\b',
|
383
|
+
'\bzone\b.*\basia-east2\b',
|
384
|
+
|
385
|
+
'^</html>$'
|
386
|
+
]
|
387
|
+
must_match.each do |re|
|
388
|
+
assert_match Regexp.new(re), resp
|
389
|
+
end
|
346
390
|
end
|
347
391
|
end
|
348
392
|
|
@@ -364,6 +408,27 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
364
408
|
yield
|
365
409
|
end
|
366
410
|
|
411
|
+
# The conversions from user input to output.
|
412
|
+
def latency_conversion
|
413
|
+
{
|
414
|
+
'32 s' => { 'seconds' => 32 },
|
415
|
+
'32s' => { 'seconds' => 32 },
|
416
|
+
'0.32s' => { 'nanos' => 320_000_000 },
|
417
|
+
' 123 s ' => { 'seconds' => 123 },
|
418
|
+
'1.3442 s' => { 'seconds' => 1, 'nanos' => 344_200_000 },
|
419
|
+
|
420
|
+
# Test whitespace.
|
421
|
+
# \t: tab. \r: carriage return. \n: line break.
|
422
|
+
# \v: vertical whitespace. \f: form feed.
|
423
|
+
"\t123.5\ts\t" => { 'seconds' => 123, 'nanos' => 500_000_000 },
|
424
|
+
"\r123.5\rs\r" => { 'seconds' => 123, 'nanos' => 500_000_000 },
|
425
|
+
"\n123.5\ns\n" => { 'seconds' => 123, 'nanos' => 500_000_000 },
|
426
|
+
"\v123.5\vs\v" => { 'seconds' => 123, 'nanos' => 500_000_000 },
|
427
|
+
"\f123.5\fs\f" => { 'seconds' => 123, 'nanos' => 500_000_000 },
|
428
|
+
"\r123.5\ts\f" => { 'seconds' => 123, 'nanos' => 500_000_000 }
|
429
|
+
}
|
430
|
+
end
|
431
|
+
|
367
432
|
# Create a Fluentd output test driver with the Google Cloud Output plugin.
|
368
433
|
def create_driver(conf = APPLICATION_DEFAULT_CONFIG,
|
369
434
|
tag = 'test',
|
@@ -400,69 +465,13 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
|
|
400
465
|
end
|
401
466
|
end
|
402
467
|
|
403
|
-
# Get the fields of the payload.
|
404
|
-
def get_fields(payload)
|
405
|
-
payload
|
406
|
-
end
|
407
|
-
|
408
|
-
# Get the value of a struct field.
|
409
|
-
def get_struct(field)
|
410
|
-
field
|
411
|
-
end
|
412
|
-
|
413
|
-
# Get the value of a string field.
|
414
|
-
def get_string(field)
|
415
|
-
field
|
416
|
-
end
|
417
|
-
|
418
|
-
# Get the value of a number field.
|
419
|
-
def get_number(field)
|
420
|
-
field
|
421
|
-
end
|
422
|
-
|
423
|
-
# The null value.
|
424
|
-
def null_value
|
425
|
-
nil
|
426
|
-
end
|
427
|
-
|
428
|
-
# Convert certain fields to strings for compatibility between gRPC and REST.
|
429
|
-
# See more details in:
|
430
|
-
# https://github.com/google/google-api-ruby-client/issues/619.
|
431
|
-
def convert_subfields_to_strings(full_hash, fields_to_convert)
|
432
|
-
full_hash.merge(Hash[
|
433
|
-
fields_to_convert.collect do |field_name|
|
434
|
-
[field_name, full_hash[field_name].to_s]
|
435
|
-
end
|
436
|
-
])
|
437
|
-
end
|
438
|
-
|
439
|
-
# 'responseSize', 'requestSize', and 'cacheFillBytes' are Integers in the gRPC
|
440
|
-
# protos, yet Strings in REST API client libraries.
|
441
|
-
def http_request_message
|
442
|
-
convert_subfields_to_strings(
|
443
|
-
HTTP_REQUEST_MESSAGE, %w(cacheFillBytes responseSize requestSize))
|
444
|
-
end
|
445
|
-
|
446
|
-
# 'line' is an Integer in the gRPC proto, yet a String in the REST API client.
|
447
|
-
def source_location_message
|
448
|
-
convert_subfields_to_strings(
|
449
|
-
SOURCE_LOCATION_MESSAGE, ['line'])
|
450
|
-
end
|
451
|
-
|
452
|
-
# 'line' is an Integer in the gRPC proto, yet a String in the REST API client.
|
453
|
-
def source_location_message2
|
454
|
-
convert_subfields_to_strings(
|
455
|
-
SOURCE_LOCATION_MESSAGE2, ['line'])
|
456
|
-
end
|
457
|
-
|
458
468
|
def expected_operation_message2
|
459
469
|
OPERATION_MESSAGE2
|
460
470
|
end
|
461
471
|
|
462
|
-
#
|
463
|
-
#
|
464
|
-
|
465
|
-
|
466
|
-
assert_equal expected, actual, "expected: #{expected}\nactual: #{actual}"
|
472
|
+
# Directly return the timestamp value, which should be a hash two keys:
|
473
|
+
# "seconds" and "nanos".
|
474
|
+
def timestamp_parse(timestamp)
|
475
|
+
timestamp
|
467
476
|
end
|
468
477
|
end
|
@@ -260,6 +260,8 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
260
260
|
end
|
261
261
|
end
|
262
262
|
|
263
|
+
# TODO(qingling128): Verify if we need this on the REST side and add it if
|
264
|
+
# needed.
|
263
265
|
def test_struct_payload_non_utf8_log
|
264
266
|
setup_gce_metadata_stubs
|
265
267
|
setup_logging_stubs do
|
@@ -273,14 +275,14 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
273
275
|
d.run
|
274
276
|
end
|
275
277
|
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
|
276
|
-
fields =
|
278
|
+
fields = entry['jsonPayload']
|
277
279
|
assert_equal 5, fields.size, entry
|
278
|
-
assert_equal 'test log entry 0',
|
279
|
-
assert_equal 'test non utf8',
|
280
|
-
assert_equal 5000,
|
281
|
-
assert_equal 'test non utf8',
|
282
|
-
|
283
|
-
|
280
|
+
assert_equal 'test log entry 0', fields['msg'], entry
|
281
|
+
assert_equal 'test non utf8', fields['normal_key'], entry
|
282
|
+
assert_equal 5000, fields['non_utf8 key'], entry
|
283
|
+
assert_equal 'test non utf8', fields['nested_struct']['non_utf8 key'],
|
284
|
+
entry
|
285
|
+
assert_nil fields['null_field'], entry
|
284
286
|
end
|
285
287
|
end
|
286
288
|
|
@@ -292,9 +294,9 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
292
294
|
{ 'seconds' => nil, 'nanos' => time.tv_nsec } => nil,
|
293
295
|
{ 'seconds' => 'seconds', 'nanos' => time.tv_nsec } => nil,
|
294
296
|
{ 'seconds' => time.tv_sec, 'nanos' => 'nanos' } => \
|
295
|
-
|
297
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
296
298
|
{ 'seconds' => time.tv_sec, 'nanos' => nil } => \
|
297
|
-
|
299
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
298
300
|
}.each do |input, expected|
|
299
301
|
setup_logging_stubs do
|
300
302
|
d = create_driver
|
@@ -318,6 +320,27 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
318
320
|
use_grpc true
|
319
321
|
).freeze
|
320
322
|
|
323
|
+
# The conversions from user input to output.
|
324
|
+
def latency_conversion
|
325
|
+
{
|
326
|
+
'32 s' => '32s',
|
327
|
+
'32s' => '32s',
|
328
|
+
'0.32s' => '0.320000000s',
|
329
|
+
' 123 s ' => '123s',
|
330
|
+
'1.3442 s' => '1.344200000s',
|
331
|
+
|
332
|
+
# Test whitespace.
|
333
|
+
# \t: tab. \r: carriage return. \n: line break.
|
334
|
+
# \v: vertical whitespace. \f: form feed.
|
335
|
+
"\t123.5\ts\t" => '123.500000000s',
|
336
|
+
"\r123.5\rs\r" => '123.500000000s',
|
337
|
+
"\n123.5\ns\n" => '123.500000000s',
|
338
|
+
"\v123.5\vs\v" => '123.500000000s',
|
339
|
+
"\f123.5\fs\f" => '123.500000000s',
|
340
|
+
"\r123.5\ts\f" => '123.500000000s'
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
321
344
|
# Create a Fluentd output test driver with the Google Cloud Output plugin with
|
322
345
|
# grpc enabled. The signature of this method is different between the grpc
|
323
346
|
# path and the non-grpc path. For grpc, an additional grpc stub class can be
|
@@ -393,7 +416,7 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
393
416
|
raise @error
|
394
417
|
rescue
|
395
418
|
# Google::Gax::GaxError will wrap the latest thrown exception as @cause.
|
396
|
-
raise Google::Gax::GaxError,
|
419
|
+
raise Google::Gax::GaxError, 'This test message does not matter.'
|
397
420
|
end
|
398
421
|
end
|
399
422
|
# rubocop:enable Lint/UnusedMethodArgument
|
@@ -448,99 +471,19 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
|
448
471
|
end
|
449
472
|
end
|
450
473
|
|
451
|
-
# Get the fields of the payload.
|
452
|
-
def get_fields(payload)
|
453
|
-
payload['fields']
|
454
|
-
end
|
455
|
-
|
456
|
-
# Get the value of a struct field.
|
457
|
-
def get_struct(field)
|
458
|
-
field['structValue']
|
459
|
-
end
|
460
|
-
|
461
|
-
# Get the value of a string field.
|
462
|
-
def get_string(field)
|
463
|
-
field['stringValue']
|
464
|
-
end
|
465
|
-
|
466
|
-
# Get the value of a number field.
|
467
|
-
def get_number(field)
|
468
|
-
field['numberValue']
|
469
|
-
end
|
470
|
-
|
471
|
-
def get_bool(field)
|
472
|
-
field['boolValue']
|
473
|
-
end
|
474
|
-
|
475
|
-
# The null value.
|
476
|
-
def null_value
|
477
|
-
{ 'nullValue' => 'NULL_VALUE' }
|
478
|
-
end
|
479
|
-
|
480
|
-
def http_request_message
|
481
|
-
HTTP_REQUEST_MESSAGE
|
482
|
-
end
|
483
|
-
|
484
|
-
def source_location_message
|
485
|
-
SOURCE_LOCATION_MESSAGE
|
486
|
-
end
|
487
|
-
|
488
|
-
def source_location_message2
|
489
|
-
SOURCE_LOCATION_MESSAGE2
|
490
|
-
end
|
491
|
-
|
492
474
|
def expected_operation_message2
|
493
475
|
# 'last' is a boolean field with false as default value. Protobuf omit
|
494
476
|
# fields with default values during deserialization.
|
495
477
|
OPERATION_MESSAGE2.reject { |k, _| k == 'last' }
|
496
478
|
end
|
497
479
|
|
498
|
-
#
|
499
|
-
#
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
# actual: A Ruby hash that represents a Proto object.
|
507
|
-
# e.g.:
|
508
|
-
# {
|
509
|
-
# "structValue" => {
|
510
|
-
# "fields" => {
|
511
|
-
# "file" => {
|
512
|
-
# "stringValue" => "source/file"
|
513
|
-
# },
|
514
|
-
# "function" => {
|
515
|
-
# "stringValue" => "my_function"
|
516
|
-
# },
|
517
|
-
# "line" => {
|
518
|
-
# "numberValue" => 18
|
519
|
-
# }
|
520
|
-
# }
|
521
|
-
# }
|
522
|
-
# }
|
523
|
-
# This method has a different implementation at the REST side.
|
524
|
-
def assert_hash_equal_json(expected, actual)
|
525
|
-
error_message = "expected: #{expected}\nactual: #{actual}"
|
526
|
-
assert_true actual.is_a?(Hash),
|
527
|
-
"Expect the actual value to be a hash. #{error_message}"
|
528
|
-
if actual.key?('stringValue')
|
529
|
-
assert_equal expected, get_string(actual), error_message
|
530
|
-
elsif actual.key?('numberValue')
|
531
|
-
assert_equal expected, get_number(actual), error_message
|
532
|
-
elsif actual.key?('boolValue')
|
533
|
-
assert_equal expected, get_bool(actual), error_message
|
534
|
-
elsif actual.key?('structValue')
|
535
|
-
expected_copy = expected.dup
|
536
|
-
get_fields(get_struct(actual)).each do |field_name, nested_actual|
|
537
|
-
assert_hash_equal_json expected_copy[field_name], nested_actual
|
538
|
-
expected_copy.reject! { |k, _| k == field_name }
|
539
|
-
end
|
540
|
-
# Make sure all fields are matched.
|
541
|
-
assert_true expected_copy.empty?
|
542
|
-
else
|
543
|
-
assert_true false, "Unsupported proto format. #{error_message}"
|
544
|
-
end
|
480
|
+
# Parse timestamp and convert it to a hash with two keys:
|
481
|
+
# "seconds" and "nanos".
|
482
|
+
def timestamp_parse(timestamp)
|
483
|
+
parsed = Time.parse(timestamp)
|
484
|
+
{
|
485
|
+
'seconds' => parsed.tv_sec,
|
486
|
+
'nanos' => parsed.tv_nsec
|
487
|
+
}
|
545
488
|
end
|
546
489
|
end
|