fluent-plugin-google-cloud 0.6.23 → 0.6.24

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dbbe816a86396963d80686517b9dbd529fa6262c
4
- data.tar.gz: b2cbe43dabcd06979dfcdef8949ad725456d338b
3
+ metadata.gz: e9d4e0c7112b19f2fdf43ac7bad7c2d030932f63
4
+ data.tar.gz: ea83fcd3888666a51aae9c4a03ea0ffb0bdba527
5
5
  SHA512:
6
- metadata.gz: 80a44dbf1c339c0cbd5e0dfb8d776d7bbd62ae8c10dcd334618fe29c9b409094528c04c6a90f49f4b8174a2fea05845a68e4b288b1ade37098e01e2cd6f93357
7
- data.tar.gz: 0e432cc308b9956b4b8a38d1fcac33cfd911518d384ff7a57e158d55761816df12b8f197fd47c47d4b506bb3c50d0f122444cce60c67397126bb43ed800fe9a9
6
+ metadata.gz: 2b179f5f31c11216c3e04b5d071cb244700f8352ed11fe1fa00fcf3282e5016e52a02e1880859d92a445d641e9bc1280a861a19896e5166ee493ad9b81569b2a
7
+ data.tar.gz: 3e9e799eb94b3e35156ce44cb8511f942082ac2ebc6bde9d21590543b421148f029f24f499c5373c7ca60a57054e066bbce3c9b19e8c312fa562f902420edf1d
@@ -1,9 +1,10 @@
1
1
  = Google Cloud Logging plugin for {fluentd}[http://github.com/fluent/fluentd]
2
2
 
3
- fluent-plugin-google-cloud is an
4
- {output plugin for fluentd}[http://docs.fluentd.org/articles/output-plugin-overview]
5
- which sends logs to the
6
- {Stackdriver Logging API}[https://cloud.google.com/logging/docs/api/].
3
+ fluent-plugin-google-cloud gem includes two plugins:
4
+ 1. A {filter plugin for fluentd}[http://docs.fluentd.org/articles/filter-plugin-overview]
5
+ that embeds insertIds into log entries to guarantee order and uniqueness.
6
+ 2. An {output plugin for fluentd}[http://docs.fluentd.org/articles/output-plugin-overview]
7
+ which sends logs to the {Stackdriver Logging API}[https://cloud.google.com/logging/docs/api/].
7
8
 
8
9
  This is an official Google Ruby gem.
9
10
 
@@ -12,8 +13,7 @@ This is an official Google Ruby gem.
12
13
 
13
14
  == Installation
14
15
 
15
- This gem is hosted at
16
- {RubyGems.org}[https://rubygems.org/gems/fluent-plugin-google-cloud]
16
+ This gem is hosted at {RubyGems.org}[https://rubygems.org/gems/fluent-plugin-google-cloud]
17
17
  and can be installed using:
18
18
 
19
19
  $ gem install fluent-plugin-google-cloud
@@ -23,22 +23,31 @@ will also install and configure the gem.
23
23
 
24
24
  == Configuration
25
25
 
26
- To send logs to Google Cloud Logging, specify <code>type google_cloud</code>
27
- in a
28
- {match clause}[http://docs.fluentd.org/articles/config-file#2-ldquomatchrdquo-tell-fluentd-what-to-do]
29
- of your fluentd configuration file, for example:
26
+ To embed insertIds into log entries, specify <code>@type add_insert_ids</code>
27
+ in a {filter clause}[https://docs.fluentd.org/v1.0/articles/config-file#(3)-%E2%80%9Cfilter%E2%80%9D:-event-processing-pipeline]
28
+ of your Fluentd configuration file, for example:
29
+
30
+ <filter **>
31
+ @type add_insert_ids
32
+ insert_id_key my_insert_id_field_name # Optional.
33
+ </filter>
34
+
35
+ insert_id_key can be used to customize the insertId field name.
36
+
37
+ To send logs to Google Cloud Logging, specify <code>@type google_cloud</code>
38
+ in a {match clause}[http://docs.fluentd.org/articles/config-file#2-ldquomatchrdquo-tell-fluentd-what-to-do]
39
+ of your Fluentd configuration file, for example:
30
40
 
31
41
  <match **>
32
- type google_cloud
42
+ @type google_cloud
33
43
  </match>
34
44
 
35
- No further configuration is required. The plugin uses
45
+ See detailed instructions on how to configure this output plugin {here}[https://cloud.google.com/logging/docs/agent/configuration#cloud-fluentd-config].
46
+ The plugin uses
36
47
  {Google Application Default Credentials}[https://developers.google.com/identity/protocols/application-default-credentials]
37
- for authorization - for additional information see
48
+ for authorization - for additional information see
38
49
  {here}[https://cloud.google.com/logging/docs/agent/authorization].
39
50
 
40
- <em>The previously documented parameters auth_method, private_key_email,
41
- and private_key_path are removed, and can no longer be used.</em>
42
51
 
43
52
  == Copyright
44
53
 
@@ -1,17 +1,17 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'fluent-plugin-google-cloud'
3
3
  gem.description = <<-eos
4
- Fluentd output plugin for the Stackdriver Logging API, which will make
5
- logs viewable in the Developer Console's log viewer and can optionally
6
- store them in Google Cloud Storage and/or BigQuery.
4
+ Fluentd plugins for the Stackdriver Logging API, which will make logs
5
+ viewable in the Stackdriver Logs Viewer and can optionally store them
6
+ in Google Cloud Storage and/or BigQuery.
7
7
  This is an official Google Ruby gem.
8
8
  eos
9
- gem.summary = 'fluentd output plugin for the Stackdriver Logging API'
9
+ gem.summary = 'fluentd plugins for the Stackdriver Logging API'
10
10
  gem.homepage =
11
11
  'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
12
12
  gem.license = 'Apache-2.0'
13
- gem.version = '0.6.23'
14
- gem.authors = ['Ling Huang', 'Igor Peshansky']
13
+ gem.version = '0.6.24'
14
+ gem.authors = ['Stackdriver Agents Team']
15
15
  gem.email = ['stackdriver-agents@google.com']
16
16
  gem.required_ruby_version = Gem::Requirement.new('>= 2.2')
17
17
 
@@ -0,0 +1,103 @@
1
+ # Copyright 2018 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'fluent/plugin/filter'
16
+
17
+ module Fluent
18
+ module Plugin
19
+ # Fluentd filter plugin for adding insertIds to guarantee log entry order
20
+ # and uniqueness.
21
+ # Sample log entries enriched by this plugin:
22
+ # {
23
+ # "timestamp": "2017-08-22 13:35:28",
24
+ # "message": "1",
25
+ # "logging.googleapis.com/insertId": "aye7eakuf23h41aef0"
26
+ # }
27
+ # {
28
+ # "timestamp": "2017-08-22 13:35:28",
29
+ # "message": "2",
30
+ # "logging.googleapis.com/insertId": "aye7eakuf23h41aef1"
31
+ # }
32
+ # {
33
+ # "timestamp": "2017-08-22 13:35:28",
34
+ # "message": "3",
35
+ # "logging.googleapis.com/insertId": "aye7eakuf23h41aef2"
36
+ # }
37
+ class AddInsertIdsFilter < Filter
38
+ Fluent::Plugin.register_filter('add_insert_ids', self)
39
+
40
+ # Constants for configuration.
41
+ module ConfigConstants
42
+ # The default field name of insertIds in the log entry.
43
+ DEFAULT_INSERT_ID_KEY = 'logging.googleapis.com/insertId'.freeze
44
+ # The character size of the insertIds. This matches the setup in the
45
+ # Stackdriver Logging backend.
46
+ INSERT_ID_SIZE = 17
47
+ # The characters that are allowed in the insertIds. This matches the
48
+ # allowed collection by the Stackdriver Logging Backend.
49
+ ALLOWED_CHARS = (Array(0..9) + Array('a'..'z')).freeze
50
+ end
51
+
52
+ include self::ConfigConstants
53
+
54
+ desc 'The field name for insertIds in the log record.'
55
+ config_param :insert_id_key, :string, default: DEFAULT_INSERT_ID_KEY
56
+
57
+ # Expose attr_readers for testing.
58
+ attr_reader :insert_id_key
59
+
60
+ def start
61
+ super
62
+ @log = $log # rubocop:disable Style/GlobalVars
63
+
64
+ # Initialize the insertID.
65
+ @log.info "Started the add_insert_ids plugin with #{@insert_id_key}" \
66
+ ' as the insert ID key.'
67
+ @insert_id = generate_initial_insert_id
68
+ @log.info "Initialized the insert ID key to #{@insert_id}."
69
+ end
70
+
71
+ def configure(conf)
72
+ super
73
+ end
74
+
75
+ def shutdown
76
+ super
77
+ end
78
+
79
+ # rubocop:disable Style/UnusedMethodArgument
80
+ def filter(tag, time, record)
81
+ # Only generate and add an insertId field if the record is a hash and
82
+ # the insert ID field is not already set (or set to an empty string).
83
+ if record.is_a?(Hash) && record[@insert_id_key].to_s.empty?
84
+ record[@insert_id_key] = increment_insert_id
85
+ end
86
+ record
87
+ end
88
+ # rubocop:enable Style/UnusedMethodArgument
89
+
90
+ private
91
+
92
+ # Generate a random string as the initial insertId.
93
+ def generate_initial_insert_id
94
+ Array.new(INSERT_ID_SIZE) { ALLOWED_CHARS.sample }.join
95
+ end
96
+
97
+ # Increment the insertId and return the new value.
98
+ def increment_insert_id
99
+ @insert_id = @insert_id.next
100
+ end
101
+ end
102
+ end
103
+ end
@@ -148,6 +148,7 @@ module Fluent
148
148
  'logging.googleapis.com/sourceLocation'.freeze
149
149
  DEFAULT_TRACE_KEY = 'logging.googleapis.com/trace'.freeze
150
150
  DEFAULT_SPAN_ID_KEY = 'logging.googleapis.com/spanId'.freeze
151
+ DEFAULT_INSERT_ID_KEY = 'logging.googleapis.com/insertId'.freeze
151
152
 
152
153
  DEFAULT_METADATA_AGENT_URL =
153
154
  'http://local-metadata-agent.stackdriver.com:8000'.freeze
@@ -227,7 +228,16 @@ module Fluent
227
228
  Fluent::Plugin.register_output('google_cloud', self)
228
229
 
229
230
  PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'.freeze
230
- PLUGIN_VERSION = '0.6.23'.freeze
231
+ # Extract plugin version by finding the spec this file was loaded from.
232
+ PLUGIN_VERSION = begin
233
+ dependency = Gem::Dependency.new('fluent-plugin-google-cloud')
234
+ all_specs, = Gem::SpecFetcher.fetcher.spec_for_dependency(dependency)
235
+ matching_spec, = all_specs.grep(
236
+ proc { |spec,| __FILE__.include?(spec.full_gem_path) }) do |spec,|
237
+ spec.version.to_s
238
+ end
239
+ matching_spec
240
+ end.freeze
231
241
 
232
242
  # Name of the the Google cloud logging write scope.
233
243
  LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
@@ -273,6 +283,7 @@ module Fluent
273
283
  DEFAULT_SOURCE_LOCATION_KEY
274
284
  config_param :trace_key, :string, :default => DEFAULT_TRACE_KEY
275
285
  config_param :span_id_key, :string, :default => DEFAULT_SPAN_ID_KEY
286
+ config_param :insert_id_key, :string, :default => DEFAULT_INSERT_ID_KEY
276
287
 
277
288
  # Whether to try to detect if the record is a text log entry with JSON
278
289
  # content that needs to be parsed.
@@ -573,16 +584,17 @@ module Fluent
573
584
 
574
585
  is_json = false
575
586
  if @detect_json
576
- # Save the timestamp and severity if available, then clear it out to
587
+ # Save the following fields if available, then clear them out to
577
588
  # allow for determining whether we should parse the log or message
578
589
  # field.
579
- timestamp = record.delete('time')
580
- severity = record.delete('severity')
590
+ preserved_keys = [
591
+ 'time', 'severity', @trace_key, @span_id_key, @insert_id_key
592
+ ]
581
593
 
582
594
  # If the log is json, we want to export it as a structured log
583
595
  # unless there is additional metadata that would be lost.
584
596
  record_json = nil
585
- if record.length == 1
597
+ if (record.keys - preserved_keys).length == 1
586
598
  %w(log message msg).each do |field|
587
599
  if record.key?(field)
588
600
  record_json = parse_json_or_nil(record[field])
@@ -590,13 +602,15 @@ module Fluent
590
602
  end
591
603
  end
592
604
  unless record_json.nil?
605
+ # Propagate these if necessary. Note that we don't want to
606
+ # override these keys in the JSON we've just parsed.
607
+ preserved_keys.each do |key|
608
+ record_json[key] ||= record[key] if record.key?(key)
609
+ end
610
+
593
611
  record = record_json
594
612
  is_json = true
595
613
  end
596
- # Restore timestamp and severity if necessary. Note that we don't
597
- # want to override these keys in the JSON we've just parsed.
598
- record['time'] ||= timestamp if timestamp
599
- record['severity'] ||= severity if severity
600
614
  end
601
615
 
602
616
  ts_secs, ts_nanos = compute_timestamp(
@@ -610,12 +624,12 @@ module Fluent
610
624
  ts_secs,
611
625
  ts_nanos)
612
626
 
613
- # Get fully-qualified trace id for LogEntry "trace" field.
614
- fq_trace_id = record.delete(@trace_key)
615
- entry.trace = fq_trace_id if fq_trace_id
616
-
627
+ trace = record.delete(@trace_key)
628
+ entry.trace = trace if trace
617
629
  span_id = record.delete(@span_id_key)
618
630
  entry.span_id = span_id if span_id
631
+ insert_id = record.delete(@insert_id_key)
632
+ entry.insert_id = insert_id if insert_id
619
633
 
620
634
  set_log_entry_fields(record, entry)
621
635
  set_payload(entry_level_resource.type, record, entry, is_json)
@@ -1983,8 +1997,13 @@ module Fluent
1983
1997
  creds = :this_channel_is_insecure
1984
1998
  end
1985
1999
  port = ":#{uri.port}" if uri.port
2000
+ user_agent = \
2001
+ "#{PLUGIN_NAME}/#{PLUGIN_VERSION} grpc-ruby/#{GRPC::VERSION} " \
2002
+ "#{Google::Apis::OS_VERSION}"
1986
2003
  @client = Google::Cloud::Logging::V2::LoggingServiceV2Client.new(
1987
- credentials: GRPC::Core::Channel.new("#{host}#{port}", nil, creds))
2004
+ credentials: GRPC::Core::Channel.new(
2005
+ "#{host}#{port}", { 'grpc.primary_user_agent' => user_agent },
2006
+ creds))
1988
2007
  else
1989
2008
  # TODO: Use a non-default ClientOptions object.
1990
2009
  Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
@@ -1221,13 +1221,32 @@ module BaseTest
1221
1221
 
1222
1222
  def test_log_entry_trace_field
1223
1223
  verify_field_key('trace', DEFAULT_TRACE_KEY, 'custom_trace_key',
1224
- CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
1225
- 'projects/proj1/traces/1234567890abcdef1234567890abcdef')
1224
+ CONFIG_CUSTOM_TRACE_KEY_SPECIFIED, TRACE)
1226
1225
  end
1227
1226
 
1228
1227
  def test_log_entry_span_id_field
1229
1228
  verify_field_key('spanId', DEFAULT_SPAN_ID_KEY, 'custom_span_id_key',
1230
- CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED, '000000000000004a')
1229
+ CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED, SPAN_ID)
1230
+ end
1231
+
1232
+ def test_log_entry_insert_id_field
1233
+ verify_field_key('insertId', DEFAULT_INSERT_ID_KEY, 'custom_insert_id_key',
1234
+ CONFIG_CUSTOM_INSERT_ID_KEY_SPECIFIED, INSERT_ID)
1235
+ end
1236
+
1237
+ def test_cascading_json_detection_with_log_entry_trace_field
1238
+ verify_cascading_json_detection_with_log_entry_fields(
1239
+ 'trace', DEFAULT_TRACE_KEY, TRACE, TRACE2)
1240
+ end
1241
+
1242
+ def test_cascading_json_detection_with_log_entry_span_id_field
1243
+ verify_cascading_json_detection_with_log_entry_fields(
1244
+ 'spanId', DEFAULT_SPAN_ID_KEY, SPAN_ID, SPAN_ID2)
1245
+ end
1246
+
1247
+ def test_cascading_json_detection_with_log_entry_insert_id_field
1248
+ verify_cascading_json_detection_with_log_entry_fields(
1249
+ 'insertId', DEFAULT_INSERT_ID_KEY, INSERT_ID, INSERT_ID2)
1231
1250
  end
1232
1251
 
1233
1252
  # Metadata Agent related tests.
@@ -1855,6 +1874,13 @@ module BaseTest
1855
1874
  }
1856
1875
  end
1857
1876
 
1877
+ def structured_log_entry
1878
+ {
1879
+ 'name' => 'test name',
1880
+ 'code' => 'test code'
1881
+ }
1882
+ end
1883
+
1858
1884
  def log_entry(i)
1859
1885
  "test log entry #{i}"
1860
1886
  end
@@ -2004,6 +2030,71 @@ module BaseTest
2004
2030
  end
2005
2031
  end
2006
2032
 
2033
+ # Cascading JSON detection is only triggered when the record has one field
2034
+ # left with name "log", "message" or "msg". This test verifies additional
2035
+ # LogEntry fields like spanId and traceId do not disable that by accident.
2036
+ def verify_cascading_json_detection_with_log_entry_fields(
2037
+ log_entry_field, default_key, root_level_value, nested_level_value)
2038
+ setup_gce_metadata_stubs
2039
+
2040
+ # {
2041
+ # "logging.googleapis.com/XXX' => 'sample value'
2042
+ # "msg": {
2043
+ # "name": "test name",
2044
+ # "code": "test code"
2045
+ # }
2046
+ # }
2047
+ log_entry_with_root_level_field = {
2048
+ default_key => root_level_value,
2049
+ 'msg' => structured_log_entry.to_json
2050
+ }
2051
+ # {
2052
+ # "msg": {
2053
+ # "logging.googleapis.com/XXX' => 'another value',
2054
+ # "name": "test name",
2055
+ # "code": "test code"
2056
+ # }
2057
+ # }
2058
+ log_entry_with_nested_level_field = {
2059
+ 'msg' => {
2060
+ default_key => nested_level_value
2061
+ }.merge(structured_log_entry).to_json
2062
+ }
2063
+ # {
2064
+ # "logging.googleapis.com/XXX' => 'sample value'
2065
+ # "msg": {
2066
+ # "logging.googleapis.com/XXX' => 'another value',
2067
+ # "name": "test name",
2068
+ # "code": "test code"
2069
+ # }
2070
+ # }
2071
+ log_entry_with_both_level_fields = log_entry_with_nested_level_field.merge(
2072
+ default_key => root_level_value)
2073
+
2074
+ {
2075
+ log_entry_with_root_level_field => root_level_value,
2076
+ log_entry_with_nested_level_field => nested_level_value,
2077
+ log_entry_with_both_level_fields => nested_level_value
2078
+ }.each_with_index do |(input_log_entry, expected_value), index|
2079
+ setup_logging_stubs do
2080
+ @logs_sent = []
2081
+ d = create_driver(DETECT_JSON_CONFIG)
2082
+ d.emit(input_log_entry)
2083
+ d.run
2084
+ end
2085
+ verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
2086
+ assert_equal expected_value, entry[log_entry_field],
2087
+ "Index #{index} failed. #{expected_value} is expected" \
2088
+ " for #{log_entry_field} field."
2089
+ payload_fields = get_fields(entry['jsonPayload'])
2090
+ assert_equal structured_log_entry.size, payload_fields.size
2091
+ payload_fields.each do |key, value|
2092
+ assert_equal structured_log_entry[key], get_string(value)
2093
+ end
2094
+ end
2095
+ end
2096
+ end
2097
+
2007
2098
  def verify_field_key(log_entry_field, default_key, custom_key,
2008
2099
  custom_key_config, sample_value)
2009
2100
  setup_gce_metadata_stubs
@@ -98,6 +98,14 @@ module Constants
98
98
  MANAGED_VM_BACKEND_NAME = 'default'.freeze
99
99
  MANAGED_VM_BACKEND_VERSION = 'guestbook2.0'.freeze
100
100
 
101
+ # LogEntry fields for extraction.
102
+ TRACE = 'projects/proj1/traces/1234567890abcdef1234567890abcdef'.freeze
103
+ TRACE2 = 'projects/proj1/traces/1234567890abcdef1234567890fedcba'.freeze
104
+ SPAN_ID = '000000000000004a'.freeze
105
+ SPAN_ID2 = '000000000000007e'.freeze
106
+ INSERT_ID = 'fah7yr7iw64tg857y'.freeze
107
+ INSERT_ID2 = 'fah7yr7iw64tgaeuf'.freeze
108
+
101
109
  # Docker Container labels.
102
110
  DOCKER_CONTAINER_ID =
103
111
  '0d0f03ff8d3c42688692536d1af77a28cd135c0a5c531f25a31'.freeze
@@ -348,6 +356,10 @@ module Constants
348
356
  span_id_key custom_span_id_key
349
357
  ).freeze
350
358
 
359
+ CONFIG_CUSTOM_INSERT_ID_KEY_SPECIFIED = %(
360
+ insert_id_key custom_insert_id_key
361
+ ).freeze
362
+
351
363
  # Service configurations for various services.
352
364
 
353
365
  # GCE.
@@ -0,0 +1,135 @@
1
+ # Copyright 2018 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../helper'
16
+
17
+ require 'fluent/test/driver/filter'
18
+ require 'fluent/plugin/filter_add_insert_ids'
19
+
20
+ # Unit tests for filter_add_insert_ids plugin.
21
+ class FilterAddInsertIdsTest < Test::Unit::TestCase
22
+ include Fluent::Plugin::AddInsertIdsFilter::ConfigConstants
23
+
24
+ CUSTOM_INSERT_ID_KEY = 'custom_insert_id_key'.freeze
25
+ INSERT_ID = 'aeyr82r92h249gh9h'.freeze
26
+ TEST_MESSAGE = 'test message for add_insert_ids plugin.'.freeze
27
+ APPLICATION_DEFAULT_CONFIG = ''.freeze
28
+ INSERT_ID_KEY_CONFIG = %(
29
+ insert_id_key #{CUSTOM_INSERT_ID_KEY}
30
+ ).freeze
31
+
32
+ def setup
33
+ Fluent::Test.setup
34
+ end
35
+
36
+ def test_configure_insert_id_key
37
+ {
38
+ APPLICATION_DEFAULT_CONFIG => DEFAULT_INSERT_ID_KEY,
39
+ INSERT_ID_KEY_CONFIG => CUSTOM_INSERT_ID_KEY
40
+ }.each do |config, insert_id_key|
41
+ d = create_driver(config)
42
+ assert_equal insert_id_key, d.instance.insert_id_key
43
+ end
44
+ end
45
+
46
+ def test_add_insert_ids
47
+ total_entry_count = 1000
48
+ d = create_driver
49
+ d.run do
50
+ total_entry_count.times do |index|
51
+ d.emit(log_entry(index))
52
+ end
53
+ end
54
+ filtered_events = d.filtered_as_array
55
+
56
+ assert_equal total_entry_count, filtered_events.size,
57
+ "#{total_entry_count} log entries after filtering is" \
58
+ " expected. Only #{filtered_events.size} are detected."
59
+ # The expected insertId will be assigned as we scan the first log entry.
60
+ expected_insert_id = nil
61
+ unique_insert_ids = Set.new
62
+ filtered_events.each_with_index do |event, index|
63
+ assert_equal 3, event.size, "Index #{index} failed. Log event should" \
64
+ ' include 3 elements: tag, time and record.'
65
+ record = event[2]
66
+ assert_true record.is_a?(Hash), "Index #{index} failed. Log record" \
67
+ " #{record} should be a hash."
68
+ assert_equal index, record['id'], "Index #{index} failed. Log entries" \
69
+ ' should come in order.'
70
+ assert_equal TEST_MESSAGE, record['message'], "Index #{index} failed."
71
+
72
+ # Get the first insertID.
73
+ expected_insert_id = record[DEFAULT_INSERT_ID_KEY] if index == 0
74
+ insert_id = record[DEFAULT_INSERT_ID_KEY]
75
+ assert_equal expected_insert_id, insert_id, "Index #{index} failed."
76
+ expected_insert_id = expected_insert_id.next
77
+ assert_true insert_id < expected_insert_id,
78
+ "Index #{index} failed. #{insert_id}" \
79
+ " < #{expected_insert_id} is false."
80
+ unique_insert_ids << insert_id
81
+ end
82
+ assert_equal total_entry_count, unique_insert_ids.size,
83
+ "Expected #{total_entry_count} unique insertIds." \
84
+ " Only #{unique_insert_ids.size} found."
85
+ end
86
+
87
+ def test_insert_ids_not_added_if_present
88
+ log_entry_with_empty_insert_id = log_entry(0).merge(
89
+ DEFAULT_INSERT_ID_KEY => '')
90
+ {
91
+ log_entry(0).merge(DEFAULT_INSERT_ID_KEY => INSERT_ID) => true,
92
+ # Still generate insertId if it's an empty string
93
+ log_entry_with_empty_insert_id => false
94
+ }.each do |test_data|
95
+ input_log_entry, retain_original_insert_id = test_data
96
+ # Make a copy because the log entry gets modified by the filter plugin.
97
+ log_entry = input_log_entry.dup
98
+ d = create_driver
99
+ d.run do
100
+ d.emit(log_entry)
101
+ end
102
+ filtered_events = d.filtered_as_array
103
+
104
+ assert_equal 1, filtered_events.size, 'Exact 1 log entry after' \
105
+ " filtering is expected. Test data: #{test_data}."
106
+ event = filtered_events[0]
107
+ assert_equal 3, event.size, 'Log event should include 3 elements: tag,' \
108
+ " time and record. Test data: #{test_data}."
109
+ record = event[2]
110
+ assert_true record.is_a?(Hash), "Log record #{record} should be a hash." \
111
+ " Test data: #{test_data}."
112
+ assert_equal 0, record['id'], "Test data: #{test_data}."
113
+ assert_equal TEST_MESSAGE, record['message'], "Test data: #{test_data}."
114
+ insert_id = record[DEFAULT_INSERT_ID_KEY]
115
+ assert_false insert_id.to_s.empty?, 'Insert ID should not be empty.' \
116
+ " Test data: #{test_data}."
117
+ assert_equal retain_original_insert_id,
118
+ input_log_entry[DEFAULT_INSERT_ID_KEY] == insert_id,
119
+ "Input value is #{input_log_entry[DEFAULT_INSERT_ID_KEY]}." \
120
+ " Output value is #{insert_id}. Test data: #{test_data}."
121
+ end
122
+ end
123
+
124
+ def create_driver(conf = APPLICATION_DEFAULT_CONFIG)
125
+ Fluent::Test::FilterTestDriver.new(
126
+ Fluent::Plugin::AddInsertIdsFilter).configure(conf, true)
127
+ end
128
+
129
+ def log_entry(index)
130
+ {
131
+ 'id' => index,
132
+ 'message' => TEST_MESSAGE
133
+ }
134
+ end
135
+ end
@@ -25,6 +25,21 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
25
25
  assert_false d.instance.instance_variable_get(:@use_grpc)
26
26
  end
27
27
 
28
+ def test_user_agent
29
+ setup_gce_metadata_stubs
30
+ user_agent = nil
31
+ stub_request(:post, WRITE_LOG_ENTRIES_URI).to_return do |request|
32
+ user_agent = request.headers['User-Agent']
33
+ { body: '' }
34
+ end
35
+ d = create_driver
36
+ d.emit('message' => log_entry(0))
37
+ d.run
38
+ assert_match Regexp.new("#{Fluent::GoogleCloudOutput::PLUGIN_NAME}/" \
39
+ "#{Fluent::GoogleCloudOutput::PLUGIN_VERSION}"), \
40
+ user_agent
41
+ end
42
+
28
43
  def test_client_400
29
44
  setup_gce_metadata_stubs
30
45
  # The API Client should not retry this and the plugin should consume
@@ -27,6 +27,26 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
27
27
  assert_true d.instance.instance_variable_get(:@use_grpc)
28
28
  end
29
29
 
30
+ def test_user_agent
31
+ setup_gce_metadata_stubs
32
+
33
+ user_agent = nil
34
+ # Record user agent when creating a GRPC::Core::Channel.
35
+ GRPC::Core::Channel.class_eval do
36
+ old_initialize = instance_method(:initialize)
37
+ define_method(:initialize) do |url, args, creds|
38
+ user_agent = args['grpc.primary_user_agent']
39
+ old_initialize.bind(self).call(url, args, creds)
40
+ end
41
+ end
42
+
43
+ d = create_driver
44
+ d.instance.send :init_api_client
45
+ assert_match Regexp.new("#{Fluent::GoogleCloudOutput::PLUGIN_NAME}/" \
46
+ "#{Fluent::GoogleCloudOutput::PLUGIN_VERSION}"), \
47
+ user_agent
48
+ end
49
+
30
50
  def test_client_error
31
51
  setup_gce_metadata_stubs
32
52
  {
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-google-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.23
4
+ version: 0.6.24
5
5
  platform: ruby
6
6
  authors:
7
- - Ling Huang
8
- - Igor Peshansky
7
+ - Stackdriver Agents Team
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2018-08-10 00:00:00.000000000 Z
11
+ date: 2018-09-05 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: fluentd
@@ -220,9 +219,9 @@ dependencies:
220
219
  - !ruby/object:Gem::Version
221
220
  version: 0.7.1
222
221
  description: |2
223
- Fluentd output plugin for the Stackdriver Logging API, which will make
224
- logs viewable in the Developer Console's log viewer and can optionally
225
- store them in Google Cloud Storage and/or BigQuery.
222
+ Fluentd plugins for the Stackdriver Logging API, which will make logs
223
+ viewable in the Stackdriver Logs Viewer and can optionally store them
224
+ in Google Cloud Storage and/or BigQuery.
226
225
  This is an official Google Ruby gem.
227
226
  email:
228
227
  - stackdriver-agents@google.com
@@ -236,6 +235,7 @@ files:
236
235
  - README.rdoc
237
236
  - Rakefile
238
237
  - fluent-plugin-google-cloud.gemspec
238
+ - lib/fluent/plugin/filter_add_insert_ids.rb
239
239
  - lib/fluent/plugin/monitoring.rb
240
240
  - lib/fluent/plugin/out_google_cloud.rb
241
241
  - test/helper.rb
@@ -247,6 +247,7 @@ files:
247
247
  - test/plugin/data/invalid_credentials.json
248
248
  - test/plugin/data/new-style-credentials.json
249
249
  - test/plugin/test_driver.rb
250
+ - test/plugin/test_filter_add_insert_ids.rb
250
251
  - test/plugin/test_out_google_cloud.rb
251
252
  - test/plugin/test_out_google_cloud_grpc.rb
252
253
  homepage: https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud
@@ -272,7 +273,7 @@ rubyforge_project:
272
273
  rubygems_version: 2.6.14
273
274
  signing_key:
274
275
  specification_version: 4
275
- summary: fluentd output plugin for the Stackdriver Logging API
276
+ summary: fluentd plugins for the Stackdriver Logging API
276
277
  test_files:
277
278
  - test/helper.rb
278
279
  - test/plugin/base_test.rb
@@ -283,5 +284,6 @@ test_files:
283
284
  - test/plugin/data/invalid_credentials.json
284
285
  - test/plugin/data/new-style-credentials.json
285
286
  - test/plugin/test_driver.rb
287
+ - test/plugin/test_filter_add_insert_ids.rb
286
288
  - test/plugin/test_out_google_cloud.rb
287
289
  - test/plugin/test_out_google_cloud_grpc.rb