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