-
Notifications
You must be signed in to change notification settings - Fork 97
/
base_test.rb
2680 lines (2489 loc) · 93 KB
/
base_test.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Enable coveralls for plugin test coverage analysis.
require 'coveralls'
Coveralls.wear!
require 'google/apis'
require 'helper'
require 'mocha/test_unit'
require 'prometheus/client'
require 'webmock/test_unit'
require 'cgi'
require_relative 'asserts'
require_relative 'constants'
require_relative 'utils'
module Monitoring
# Prevent OpenCensus from writing to the network.
OpenCensusMonitoringRegistry.class_eval do
# Suppress redefine warning (https://bugs.ruby-lang.org/issues/17055).
alias_method :export, :export
define_method(:export) do
nil
end
end
end
# Unit tests for Google Cloud Logging plugin
module BaseTest
include Asserts
include Constants
include Utils
def setup
Fluent::Test.setup
delete_env_vars
# Unregister Prometheus metrics.
registry = Prometheus::Client.registry
registry.unregister(:stackdriver_successful_requests_count)
registry.unregister(:stackdriver_failed_requests_count)
registry.unregister(:stackdriver_ingested_entries_count)
registry.unregister(:stackdriver_dropped_entries_count)
registry.unregister(:stackdriver_retried_entries_count)
setup_auth_stubs('https://www.googleapis.com/oauth2/v4/token')
setup_auth_stubs('https://oauth2.googleapis.com/token')
@logs_sent = []
end
# Shared tests.
def test_configure_service_account_application_default
setup_gce_metadata_stubs
d = create_driver
assert_equal HOSTNAME, d.instance.vm_name
end
def test_configure_service_account_private_key
# Using out-of-date config method.
exception_count = 0
begin
create_driver(PRIVATE_KEY_CONFIG)
rescue Fluent::ConfigError => e
assert e.message.include? 'Please remove configuration parameters'
exception_count += 1
end
assert_equal 1, exception_count
end
def test_configure_logging_api_url
setup_gce_metadata_stubs
{
APPLICATION_DEFAULT_CONFIG => DEFAULT_LOGGING_API_URL,
CUSTOM_LOGGING_API_URL_CONFIG => CUSTOM_LOGGING_API_URL
}.each do |(config, url)|
d = create_driver(config)
assert_equal url, d.instance.instance_variable_get(:@logging_api_url)
end
end
def test_configure_custom_metadata
setup_no_metadata_service_stubs
d = create_driver(CUSTOM_METADATA_CONFIG)
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
assert_equal CUSTOM_ZONE, d.instance.zone
assert_equal CUSTOM_VM_ID, d.instance.vm_id
end
def test_configure_metadata_missing_parts_on_other_platforms
setup_no_metadata_service_stubs
Common::Utils::CredentialsInfo.stubs(:project_id).returns(nil)
[
[CONFIG_MISSING_METADATA_PROJECT_ID, ['project_id'], false],
[CONFIG_MISSING_METADATA_ZONE, [], true],
[CONFIG_MISSING_METADATA_VM_ID, [], true],
[CONFIG_MISSING_METADATA_ALL, ['project_id'], false]
].each_with_index do |(config, missing_parts, is_valid_config), index|
create_driver(config)
assert_true is_valid_config, "Invalid config at index #{index} should "\
'have raised an error.'
rescue Fluent::ConfigError => e
assert_false is_valid_config, "Valid config at index #{index} should "\
"not have raised an error #{e}."
assert e.message.include?('Unable to obtain metadata parameters:'),
"Index #{index} failed."
missing_parts.each do |part|
assert e.message.include?(part), "Index #{index} failed."
end
end
end
def test_configure_ignores_unknown_monitoring_type
# Verify that driver creation succeeds when monitoring type is not
# "prometheus" or "opencensus" (in which case, we simply don't record
# metrics), and that the counters are set to nil.
setup_gce_metadata_stubs
create_driver(CONFIG_UNKNOWN_MONITORING_TYPE)
assert_nil(Prometheus::Client.registry.get(
:stackdriver_successful_requests_count
))
assert_nil(Prometheus::Client.registry.get(
:stackdriver_failed_requests_count
))
assert_nil(Prometheus::Client.registry.get(
:stackdriver_ingested_entries_count
))
assert_nil(Prometheus::Client.registry.get(
:stackdriver_dropped_entries_count
))
assert_nil(Prometheus::Client.registry.get(
:stackdriver_retried_entries_count
))
assert_nil(OpenCensus::Stats::MeasureRegistry.get(
Monitoring::MetricTranslator.new(
:stackdriver_successful_requests_count, {}
)
))
assert_nil(OpenCensus::Stats::MeasureRegistry.get(
Monitoring::MetricTranslator.new(
:stackdriver_failed_requests_count, {}
)
))
assert_nil(OpenCensus::Stats::MeasureRegistry.get(
Monitoring::MetricTranslator.new(
:stackdriver_ingested_entries_count, {}
)
))
assert_nil(OpenCensus::Stats::MeasureRegistry.get(
Monitoring::MetricTranslator.new(
:stackdriver_dropped_entries_count, {}
)
))
assert_nil(OpenCensus::Stats::MeasureRegistry.get(
Monitoring::MetricTranslator.new(
:stackdriver_retried_entries_count, {}
)
))
end
def test_configure_uses_metrics_resource
setup_gce_metadata_stubs
[CONFIG_METRICS_RESOURCE_JSON,
CONFIG_METRICS_RESOURCE_HASH,
CONFIG_METRICS_RESOURCE_JSON_HASH].each_with_index do |config, index|
d = create_driver(config)
assert_equal 'custom_resource', d.instance.monitoring_resource.type, \
"Index #{index}"
assert_equal '123', d.instance.monitoring_resource.labels['label1'], \
"Index #{index}"
assert_equal 'abc', d.instance.monitoring_resource.labels['label2'], \
"Index #{index}"
assert_true d.instance.instance_variable_get(:@enable_monitoring)
registry = d.instance.instance_variable_get(:@registry)
assert_not_nil registry
monitored_resource = registry.instance_variable_get(
:@metrics_monitored_resource
)
assert_equal('custom_resource', monitored_resource.type, "Index #{index}")
assert_equal({ 'label1' => '123', 'label2' => 'abc' },
monitored_resource.labels, "Index #{index}")
end
end
def test_configure_metrics_resource_validation
setup_gce_metadata_stubs
{
CONFIG_METRICS_RESOURCE_JSON_NO_TYPE => /type must be a string/,
CONFIG_METRICS_RESOURCE_JSON_BAD_LABELS => /labels must be a hash/,
CONFIG_METRICS_RESOURCE_JSON_BAD_KEYS =>
/unrecognized keys: \[:random\]/,
CONFIG_METRICS_RESOURCE_JSON_BAD_KEYS_LABELS =>
/unrecognized keys: \[:"labels\.random"\]/,
CONFIG_METRICS_RESOURCE_JSON_BAD_KEYS_NO_LABELS =>
/unrecognized keys: \[:random\]/
}.each_with_index do |(config, pattern), index|
create_driver(config)
assert false,
"Invalid config at index #{index} should have raised an error."
rescue Fluent::ConfigError => e
assert e.message.match?(pattern), \
"Index #{index} failed: got #{e.message}."
end
end
def test_metadata_loading
setup_gce_metadata_stubs
d = create_driver
d.run
assert_equal PROJECT_ID, d.instance.project_id
assert_equal ZONE, d.instance.zone
assert_equal VM_ID, d.instance.vm_id
assert_equal COMPUTE_CONSTANTS[:resource_type], d.instance.resource.type
end
def test_managed_vm_metadata_loading
setup_gce_metadata_stubs
setup_managed_vm_metadata_stubs
d = create_driver
d.run
assert_equal PROJECT_ID, d.instance.project_id
assert_equal ZONE, d.instance.zone
assert_equal VM_ID, d.instance.vm_id
assert_equal APPENGINE_CONSTANTS[:resource_type], d.instance.resource.type
assert_equal MANAGED_VM_BACKEND_NAME,
d.instance.resource.labels['module_id']
assert_equal MANAGED_VM_BACKEND_VERSION,
d.instance.resource.labels['version_id']
end
def test_gce_metadata_does_not_load_when_use_metadata_service_is_false
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
d.run
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
assert_equal CUSTOM_ZONE, d.instance.zone
assert_equal CUSTOM_VM_ID, d.instance.vm_id
assert_equal COMPUTE_CONSTANTS[:resource_type], d.instance.resource.type
end
def test_gce_used_when_detect_subservice_is_false
setup_gce_metadata_stubs
# This would cause the resource type to be container.googleapis.com if not
# for the detect_subservice=false config.
setup_k8s_metadata_stubs
d = create_driver(NO_DETECT_SUBSERVICE_CONFIG)
d.run
assert_equal COMPUTE_CONSTANTS[:resource_type], d.instance.resource.type
end
def test_metadata_overrides
{
# In this case we are overriding all configured parameters so we should
# see all "custom" values rather than the ones from the metadata server.
CUSTOM_METADATA_CONFIG =>
['gce', CUSTOM_PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
# Similar to above, but we are not overriding project_id in this config so
# we should see the metadata value for project_id and "custom" otherwise.
CONFIG_MISSING_METADATA_PROJECT_ID =>
['gce', PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
CONFIG_EC2_PROJECT_ID =>
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, EC2_VM_ID],
CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID =>
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, CUSTOM_VM_ID],
CONFIG_EC2_PROJECT_ID_USE_REGION =>
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_REGION, EC2_VM_ID]
}.each_with_index do |(config, parts), index|
send("setup_#{parts[0]}_metadata_stubs")
d = create_driver(config)
d.run
assert_equal parts[1], d.instance.project_id, "Index #{index} failed."
assert_equal parts[2], d.instance.zone, "Index #{index} failed."
assert_equal parts[3], d.instance.vm_id, "Index #{index} failed."
end
end
def test_ec2_metadata_requires_project_id
setup_ec2_metadata_stubs
exception_count = 0
begin
create_driver
rescue Fluent::ConfigError => e
assert e.message.include? 'Unable to obtain metadata parameters:'
assert e.message.include? 'project_id'
exception_count += 1
end
assert_equal 1, exception_count
end
def test_project_id_from_credentials
%w[gce ec2].each do |platform|
send("setup_#{platform}_metadata_stubs")
[IAM_CREDENTIALS, NEW_STYLE_CREDENTIALS, LEGACY_CREDENTIALS].each \
do |creds|
ENV[CREDENTIALS_PATH_ENV_VAR] = creds[:path]
d = create_driver
d.run
assert_equal creds[:project_id], d.instance.project_id
end
end
end
def test_one_log
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver
d.emit('message' => log_entry(0))
d.run
end
verify_log_entries(1, COMPUTE_PARAMS)
end
def test_one_log_with_json_credentials
setup_gce_metadata_stubs
ENV[CREDENTIALS_PATH_ENV_VAR] = IAM_CREDENTIALS[:path]
setup_logging_stubs do
d = create_driver
d.emit('message' => log_entry(0))
d.run
end
verify_log_entries(1, COMPUTE_PARAMS.merge(
project_id: IAM_CREDENTIALS[:project_id]
))
end
def test_invalid_json_credentials
%w[gce_metadata ec2_metadata no_metadata_service].each do |platform|
send("setup_#{platform}_stubs")
exception_count = 0
ENV[CREDENTIALS_PATH_ENV_VAR] = INVALID_CREDENTIALS[:path]
begin
create_driver
rescue RuntimeError => e
assert e.message.include? 'Unable to read the credential file'
exception_count += 1
end
assert_equal 1, exception_count
end
end
def test_unset_or_empty_credentials_path_env_var
# An empty string should be treated as if it's not set.
[nil, ''].each do |value|
ENV[CREDENTIALS_PATH_ENV_VAR] = value
setup_gce_metadata_stubs
create_driver
assert_nil ENV[CREDENTIALS_PATH_ENV_VAR]
end
end
def test_one_log_custom_metadata
# don't set up any metadata stubs, so the test will fail if we try to
# fetch metadata (and explicitly check this as well).
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
ENV[CREDENTIALS_PATH_ENV_VAR] = IAM_CREDENTIALS[:path]
setup_logging_stubs do
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
d.emit('message' => log_entry(0))
d.run
end
verify_log_entries(1, CUSTOM_PARAMS)
end
def test_one_log_ec2
ENV[CREDENTIALS_PATH_ENV_VAR] = IAM_CREDENTIALS[:path]
setup_ec2_metadata_stubs
setup_logging_stubs do
d = create_driver(CONFIG_EC2_PROJECT_ID)
d.emit('message' => log_entry(0))
d.run
end
verify_log_entries(1, EC2_ZONE_PARAMS)
end
def test_one_log_ec2_region
ENV[CREDENTIALS_PATH_ENV_VAR] = IAM_CREDENTIALS[:path]
setup_ec2_metadata_stubs
setup_logging_stubs do
d = create_driver(CONFIG_EC2_PROJECT_ID_USE_REGION)
d.emit('message' => log_entry(0))
d.run
end
verify_log_entries(1, EC2_REGION_PARAMS)
end
def test_structured_payload_log
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver
d.emit('msg' => log_entry(0), 'tag2' => 'test', 'data' => 5000,
'some_null_field' => nil)
d.run
end
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry, i|
fields = entry['jsonPayload']
assert_equal 4, fields.size, entry
verify_default_log_entry_text(fields['msg'], i, entry)
assert_equal 'test', fields['tag2'], entry
assert_equal 5000, fields['data'], entry
assert_nil fields['some_null_field'], entry
end
end
def test_autoformat_enabled_with_stackdriver_trace_id_as_trace
[
APPLICATION_DEFAULT_CONFIG,
ENABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG
].each do |config|
new_stub_context do
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver(config)
d.emit(DEFAULT_TRACE_KEY => STACKDRIVER_TRACE_ID)
d.run
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
assert_equal FULL_STACKDRIVER_TRACE, entry['trace'],
'stackdriver trace id should be autoformatted ' \
'when autoformat_stackdriver_trace is enabled.'
end
end
end
end
end
def test_autoformat_disabled_with_stackdriver_trace_id_as_trace
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver(DISABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG)
d.emit(DEFAULT_TRACE_KEY => STACKDRIVER_TRACE_ID)
d.run
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
assert_equal STACKDRIVER_TRACE_ID, entry['trace'],
'trace as stackdriver trace id should not be ' \
'autoformatted with config ' \
"#{DISABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG}."
end
end
end
def test_no_trace_when_trace_key_not_exists_with_any_autoformat_config
[
APPLICATION_DEFAULT_CONFIG,
ENABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG,
DISABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG
].each do |config|
new_stub_context do
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver(config)
d.emit('msg' => log_entry(0))
d.run
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
assert_false entry.key?('trace'), entry
end
end
end
end
end
def test_non_stackdriver_trace_id_compliant_trace_with_any_autoformat_config
configs = [
APPLICATION_DEFAULT_CONFIG,
ENABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG,
DISABLE_AUTOFORMAT_STACKDRIVER_TRACE_CONFIG
]
traces = [
TRACE, # Full trace won't be modified.
EMPTY_STRING,
INVALID_SHORT_STACKDRIVER_TRACE_ID,
INVALID_LONG_STACKDRIVER_TRACE_ID,
INVALID_NON_HEX_STACKDRIVER_TRACE_ID,
INVALID_TRACE_NO_TRACE_ID,
INVALID_TRACE_NO_PROJECT_ID,
INVALID_TRACE_WITH_SHORT_TRACE_ID,
INVALID_TRACE_WITH_LONG_TRACE_ID,
INVALID_TRACE_WITH_NON_HEX_TRACE_ID
]
configs.product(traces).each do |config, trace|
new_stub_context do
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver(config)
d.emit(DEFAULT_TRACE_KEY => trace)
d.run
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
assert_equal_with_default \
entry['trace'], trace, '',
'trace as non stackdriver trace id should not be ' \
"autoformatted with config #{config}."
end
end
end
end
end
def test_structured_payload_malformatted_log
setup_gce_metadata_stubs
message = 'test message'
setup_logging_stubs do
d = create_driver
d.emit(
'int_key' => { 1 => message },
'int_array_key' => { [1, 2, 3, 4] => message },
'string_array_key' => { %w[a b c] => message },
'hash_key' => { { 'some_key' => 'some_value' } => message },
'mixed_key' => { { 'some_key' => %w[a b c] } => message },
'symbol_key' => { some_symbol: message },
'nil_key' => { nil => message }
)
d.run
end
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
fields = entry['jsonPayload']
assert_equal 7, fields.size, entry
assert_equal message, fields['int_key']['1'], entry
assert_equal message, fields['int_array_key']['[1, 2, 3, 4]'], entry
assert_equal message, fields['string_array_key']['["a", "b", "c"]'], entry
assert_equal message, fields['hash_key']['{"some_key"=>"some_value"}'],
entry
assert_equal message,
fields['mixed_key']['{"some_key"=>["a", "b", "c"]}'], entry
assert_equal message, fields['symbol_key']['some_symbol'], entry
assert_equal message, fields['nil_key'][''], entry
end
end
def test_structured_payload_json_log_default_not_parsed_text
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(APPLICATION_DEFAULT_CONFIG)
d.emit('message' => "notJSON #{json_string}")
d.emit('message' => json_string)
d.emit('message' => " \r\n \t#{json_string}")
d.run
end
verify_log_entries(3, COMPUTE_PARAMS, 'textPayload') do
# Only check for the existence of textPayload.
end
end
def test_structured_payload_json_log_default_not_parsed_json
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(APPLICATION_DEFAULT_CONFIG)
%w[log msg].each do |field|
d.emit(field => "notJSON #{json_string}")
d.emit(field => json_string)
d.emit(field => " \r\n \t#{json_string}")
end
d.run
end
verify_log_entries(6, COMPUTE_PARAMS, 'jsonPayload') do |entry|
fields = entry['jsonPayload']
assert !fields.key?('tag2'), 'Did not expect tag2'
assert !fields.key?('data'), 'Did not expect data'
assert !fields.key?('some_null_field'), 'Did not expect some_null_field'
end
end
def test_structured_payload_json_log_detect_json_not_parsed_text
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(DETECT_JSON_CONFIG)
d.emit('message' => "notJSON #{json_string}")
d.run
end
verify_log_entries(1, COMPUTE_PARAMS, 'textPayload') do
# Only check for the existence of textPayload.
end
end
def test_structured_payload_json_log_detect_json_not_parsed_json
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(DETECT_JSON_CONFIG)
%w[log msg].each do |field|
d.emit(field => "notJSON #{json_string}")
end
d.run
end
verify_log_entries(2, COMPUTE_PARAMS, 'jsonPayload') do |entry|
fields = entry['jsonPayload']
assert !fields.key?('tag2'), 'Did not expect tag2'
assert !fields.key?('data'), 'Did not expect data'
assert !fields.key?('some_null_field'), 'Did not expect some_null_field'
end
end
# TODO(qingling128): Fix the inconsistent behavior of 'message', 'log' and
# 'msg' in the next major version 1.0.0.
def test_structured_payload_json_log_detect_json_with_hash_input
hash_value = {
'msg' => 'test log entry 0',
'tag2' => 'test',
'data' => 5000,
'some_null_field' => nil
}
[
{
config: APPLICATION_DEFAULT_CONFIG,
field_name: 'log',
expected_payload: 'jsonPayload'
},
{
config: APPLICATION_DEFAULT_CONFIG,
field_name: 'msg',
expected_payload: 'jsonPayload'
},
{
config: APPLICATION_DEFAULT_CONFIG,
field_name: 'message',
expected_payload: 'textPayload'
},
{
config: DETECT_JSON_CONFIG,
field_name: 'log',
expected_payload: 'jsonPayload'
},
{
config: DETECT_JSON_CONFIG,
field_name: 'msg',
expected_payload: 'jsonPayload'
},
{
config: DETECT_JSON_CONFIG,
field_name: 'message',
expected_payload: 'textPayload'
}
].each do |test_params|
new_stub_context do
setup_gce_metadata_stubs
setup_logging_stubs do
d = create_driver(test_params[:config])
d.emit(test_params[:field_name] => hash_value)
d.run
end
if test_params[:expected_payload] == 'textPayload'
verify_log_entries(1, COMPUTE_PARAMS, 'textPayload') do |entry|
text_payload = entry['textPayload']
assert_equal '{"msg"=>"test log entry 0", "tag2"=>"test", ' \
'"data"=>5000, "some_null_field"=>nil}',
text_payload, entry
end
else
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
json_payload = entry['jsonPayload']
assert_equal 1, json_payload.size, entry
fields = json_payload[test_params[:field_name]]
assert_equal 4, fields.size, entry
assert_equal 'test log entry 0', fields['msg'], entry
assert_equal 'test', fields['tag2'], entry
assert_equal 5000, fields['data'], entry
assert_nil fields['some_null_field'], entry
end
end
end
end
end
def test_structured_payload_json_log_detect_json_parsed
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(DETECT_JSON_CONFIG)
%w[message log msg].each do |field|
d.emit(field => json_string)
d.emit(field => " \r\n \t#{json_string}")
end
d.run
end
verify_log_entries(6, COMPUTE_PARAMS, 'jsonPayload') do |entry|
fields = entry['jsonPayload']
assert_equal 4, fields.size, entry
assert_equal 'test log entry 0', fields['msg'], entry
assert_equal 'test', fields['tag2'], entry
assert_equal 5000, fields['data'], entry
assert_nil fields['some_null_field'], entry
end
end
def test_structured_payload_json_log_default_container_not_parsed
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
d.emit(container_log_entry_with_metadata("notJSON#{json_string}"))
d.emit(container_log_entry_with_metadata(json_string))
d.emit(container_log_entry_with_metadata(" \r\n \t#{json_string}"))
d.run
end
verify_log_entries(3, CONTAINER_FROM_METADATA_PARAMS, 'textPayload') do
# Only check for the existence of textPayload.
end
end
def test_structured_payload_json_log_detect_json_container_not_parsed
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(DETECT_JSON_CONFIG, CONTAINER_TAG)
d.emit(container_log_entry_with_metadata("notJSON#{json_string}"))
d.run
end
verify_log_entries(1, CONTAINER_FROM_METADATA_PARAMS, 'textPayload') do
# Only check for the existence of textPayload.
end
end
def test_structured_payload_json_log_detect_json_container_parsed
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
setup_logging_stubs do
d = create_driver(DETECT_JSON_CONFIG, CONTAINER_TAG)
d.emit(container_log_entry_with_metadata(json_string))
d.emit(container_log_entry_with_metadata(" \r\n \t#{json_string}"))
d.run
end
verify_log_entries(2, CONTAINER_FROM_METADATA_PARAMS, 'jsonPayload') \
do |entry|
fields = entry['jsonPayload']
assert_equal 4, fields.size, entry
assert_equal 'test log entry 0', fields['msg'], entry
assert_equal 'test', fields['tag2'], entry
assert_equal 5000, fields['data'], entry
assert_nil fields['some_null_field'], entry
end
end
# Verify that when the log has only one effective field (named 'log',
# 'message', or 'msg') and the field is in JSON format, the field is parsed as
# JSON and sent as jsonPayload.
def test_detect_json_auto_triggered_with_one_field
setup_gce_metadata_stubs
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
'"data": 5000, "some_null_field": null}'
PRESERVED_KEYS_TIMESTAMP_FIELDS.each do |timestamp_fields|
setup_logging_stubs do
@logs_sent = []
d = create_driver(DETECT_JSON_CONFIG)
%w[message log msg].each do |field|
d.emit(PRESERVED_KEYS_MAP.merge(
field => json_string
).merge(timestamp_fields))
end
d.run
end
expected_params = COMPUTE_PARAMS.merge(
labels: COMPUTE_PARAMS[:labels].merge(LABELS_MESSAGE)
)
verify_log_entries(3, expected_params, 'jsonPayload') do |entry|
fields = entry['jsonPayload']
assert_equal 4, fields.size, entry
assert_equal 'test log entry 0', fields['msg'], entry
assert_equal 'test', fields['tag2'], entry
assert_equal 5000, fields['data'], entry
assert_nil fields['some_null_field'], entry
end
end
end
# Verify that we drop the log entries when 'require_valid_tags' is true and
# any non-string tags or tags with non-utf8 characters are detected.
def test_reject_invalid_tags_with_require_valid_tags_true
setup_gce_metadata_stubs
INVALID_TAGS.each_key do |tag|
setup_logging_stubs do
@logs_sent = []
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
d.emit('msg' => log_entry(0))
d.run
end
verify_log_entries(0, COMPUTE_PARAMS, 'jsonPayload')
end
end
# Verify that empty string container name should fail the kubernetes regex
# match, thus the original tag is used as the log name.
def test_handle_empty_container_name
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
container_name = ''
# This tag will not match the kubernetes regex because it requires a
# non-empty container name.
tag = container_tag_with_container_name(container_name)
setup_logging_stubs do
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
d.run
end
params = CONTAINER_FROM_METADATA_PARAMS.merge(
resource: CONTAINER_FROM_METADATA_PARAMS[:resource].merge(
labels: CONTAINER_FROM_METADATA_PARAMS[:resource][:labels].merge(
'container_name' => container_name
)
),
log_name: tag
)
verify_log_entries(1, params, 'textPayload')
end
# Verify that container names with non-utf8 characters should be rejected when
# 'require_valid_tags' is true.
def test_reject_non_utf8_container_name_with_require_valid_tags_true
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
non_utf8_tags = INVALID_TAGS.select do |tag, _|
tag.is_a?(String) && !tag.empty?
end
non_utf8_tags.each do |container_name, encoded_name|
setup_logging_stubs do
@logs_sent = []
d = create_driver(REQUIRE_VALID_TAGS_CONFIG,
container_tag_with_container_name(container_name))
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
d.run
end
params = CONTAINER_FROM_METADATA_PARAMS.merge(
labels: CONTAINER_FROM_METADATA_PARAMS[:labels].merge(
"#{GKE_CONSTANTS[:service]}/container_name" =>
CGI.unescape(encoded_name)
),
log_name: encoded_name
)
verify_log_entries(0, params, 'textPayload')
end
end
# Verify that tags are properly encoded. When 'require_valid_tags' is true, we
# only accept string tags with utf8 characters.
def test_encode_tags_with_require_valid_tags_true
setup_gce_metadata_stubs
VALID_TAGS.each do |tag, encoded_tag|
setup_logging_stubs do
@logs_sent = []
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
d.emit('msg' => log_entry(0))
d.run
end
verify_log_entries(1, COMPUTE_PARAMS.merge(log_name: encoded_tag),
'jsonPayload')
end
end
# Verify that tags extracted from container names are properly encoded.
def test_encode_tags_from_container_name_with_require_valid_tags_true
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
VALID_TAGS.each do |tag, encoded_tag|
setup_logging_stubs do
@logs_sent = []
d = create_driver(REQUIRE_VALID_TAGS_CONFIG,
container_tag_with_container_name(tag))
d.emit(container_log_entry_with_metadata(log_entry(0), tag))
d.run
end
params = CONTAINER_FROM_METADATA_PARAMS.merge(
resource: CONTAINER_FROM_METADATA_PARAMS[:resource].merge(
labels: CONTAINER_FROM_METADATA_PARAMS[:resource][:labels].merge(
'container_name' => tag
)
),
log_name: encoded_tag
)
verify_log_entries(1, params, 'textPayload')
end
end
# Verify that tags are properly encoded and sanitized. When
# 'require_valid_tags' is false, we try to convert any non-string tags to
# strings, and replace non-utf8 characters with a replacement string.
def test_sanitize_tags_with_require_valid_tags_false
setup_gce_metadata_stubs
ALL_TAGS.each do |tag, sanitized_tag|
setup_logging_stubs do
@logs_sent = []
d = create_driver(APPLICATION_DEFAULT_CONFIG, tag)
d.emit('msg' => log_entry(0))
d.run
end
verify_log_entries(1, COMPUTE_PARAMS.merge(log_name: sanitized_tag),
'jsonPayload')
end
end
# Verify that tags extracted from container names are properly encoded and
# sanitized.
def test_sanitize_tags_from_container_name_with_require_valid_tags_false
setup_gce_metadata_stubs
setup_k8s_metadata_stubs
# Log names are derived from container names for containers. And container
# names are extracted from the tag based on a regex match pattern. As a
# prerequisite, the tag should already be a string, thus we only test
# non-empty string cases here.
string_tags = ALL_TAGS.select { |tag, _| tag.is_a?(String) && !tag.empty? }
string_tags.each do |container_name, encoded_container_name|
# Container name in the label is sanitized but not encoded, while the log
# name is encoded.
setup_logging_stubs do
@logs_sent = []
d = create_driver(APPLICATION_DEFAULT_CONFIG,
container_tag_with_container_name(container_name))
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
d.run
end
params = CONTAINER_FROM_METADATA_PARAMS.merge(
resource: CONTAINER_FROM_METADATA_PARAMS[:resource].merge(
labels: CONTAINER_FROM_METADATA_PARAMS[:resource][:labels].merge(
'container_name' => CGI.unescape(encoded_container_name)
)
),
log_name: encoded_container_name
)
verify_log_entries(1, params, 'textPayload')
end
end
def test_configure_split_logs_by_tag
setup_gce_metadata_stubs
{
APPLICATION_DEFAULT_CONFIG => false,
ENABLE_SPLIT_LOGS_BY_TAG_CONFIG => true
}.each do |(config, split_logs_by_tag)|
d = create_driver(config)
assert_equal split_logs_by_tag,
d.instance.instance_variable_get(:@split_logs_by_tag)
end
end
def test_split_logs_by_tag
setup_gce_metadata_stubs
log_entry_count = 5
dynamic_log_names = (0..log_entry_count - 1).map do |index|
"projects/test-project-id/logs/tag#{index}"
end
[
[APPLICATION_DEFAULT_CONFIG, 1, [''], dynamic_log_names],
# [] returns nil for any index.
[ENABLE_SPLIT_LOGS_BY_TAG_CONFIG, log_entry_count, dynamic_log_names, []]
].each do |(config, request_count, request_log_names, entry_log_names)|
clear_metrics
setup_logging_stubs do
@logs_sent = []
d = create_driver(config + ENABLE_PROMETHEUS_CONFIG, 'test', true)
log_entry_count.times do |i|
d.emit("tag#{i}", 'message' => log_entry(i))
end
d.run
@logs_sent.zip(request_log_names).each do |request, log_name|
assert_equal log_name, request['logName']
end
verify_log_entries(log_entry_count, COMPUTE_PARAMS_NO_LOG_NAME,
'textPayload') do |entry, entry_index|
verify_default_log_entry_text(entry['textPayload'], entry_index,
entry)
assert_equal entry_log_names[entry_index], entry['logName']
end
# Verify the number of requests is different based on whether the
# 'split_logs_by_tag' flag is enabled.
assert_prometheus_metric_value(
:stackdriver_successful_requests_count,
request_count,
'agent.googleapis.com/agent',
OpenCensus::Stats::Aggregation::Sum, d,
:aggregate
)
assert_prometheus_metric_value(
:stackdriver_ingested_entries_count,
log_entry_count,
'agent.googleapis.com/agent',
OpenCensus::Stats::Aggregation::Sum, d,
:aggregate
)