fluent-plugin-google-cloud 0.6.23 → 0.6.24

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 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