fluent-plugin-opensearch 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +7 -0
- data/README.OpenSearchInput.md +13 -0
- data/README.md +38 -0
- data/fluent-plugin-opensearch.gemspec +1 -1
- data/lib/fluent/plugin/in_opensearch.rb +11 -1
- data/lib/fluent/plugin/opensearch_error_handler.rb +24 -5
- data/lib/fluent/plugin/out_opensearch.rb +24 -6
- data/lib/fluent/plugin/out_opensearch_data_stream.rb +13 -3
- data/test/plugin/test_opensearch_error_handler.rb +81 -0
- data/test/plugin/test_out_opensearch.rb +3 -15
- data/test/plugin/test_out_opensearch_data_stream.rb +71 -1
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 217feaf328809b259c6eb716e968b02417df7b10075a9a1e278c6e337d8a660b
         | 
| 4 | 
            +
              data.tar.gz: 351327bd18d5dd3aa7b6a360ebba4a4f574a86b65ed470cffdf15b38e5d16022
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c374adb923d9533e50c1c11355b304ba04ca92112dee36dcacd886d53be0ec910481559c7fdb0f6cdb3b61c7c949b5b917c3540d4bbd2789a9d568015cb52cbb
         | 
| 7 | 
            +
              data.tar.gz: 11259ac1ee5e4dfe0de5b18336fd2167b40b8c10c381a283068557da1d206e06c03d5d76ef46411a8803b00c333370f47c21fbfdf4331e528cb05a145803a9b4
         | 
    
        data/History.md
    CHANGED
    
    | @@ -2,6 +2,13 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            ### [Unreleased]
         | 
| 4 4 |  | 
| 5 | 
            +
            ### 1.0.3
         | 
| 6 | 
            +
             - Configurable unrecoverable record types (#40)
         | 
| 7 | 
            +
             - Handle exceptions on retrieving AWS credentials (#39)
         | 
| 8 | 
            +
             - Suppress emit error label events (#38)
         | 
| 9 | 
            +
             - Provide suppress_type_name parameter (#29)
         | 
| 10 | 
            +
             - Honor @time_key (data streams) (#28)
         | 
| 11 | 
            +
             | 
| 5 12 | 
             
            ### 1.0.2
         | 
| 6 13 | 
             
             - Honor @hosts parameter for Data Streams (#21)
         | 
| 7 14 | 
             
             - Use template_file for Data Streams (#20)
         | 
    
        data/README.OpenSearchInput.md
    CHANGED
    
    | @@ -17,6 +17,7 @@ | |
| 17 17 | 
             
              + [reload_on_failure](#reload_on_failure)
         | 
| 18 18 | 
             
              + [resurrect_after](#resurrect_after)
         | 
| 19 19 | 
             
              + [with_transporter_log](#with_transporter_log)
         | 
| 20 | 
            +
              + [emit_error_label_event](#emit-error-label-event)
         | 
| 20 21 | 
             
              + [Client/host certificate options](#clienthost-certificate-options)
         | 
| 21 22 | 
             
              + [sniffer_class_name](#sniffer-class-name)
         | 
| 22 23 | 
             
              + [custom_headers](#custom_headers)
         | 
| @@ -190,6 +191,18 @@ We recommend to set this true if you start to debug this plugin. | |
| 190 191 | 
             
            with_transporter_log true
         | 
| 191 192 | 
             
            ```
         | 
| 192 193 |  | 
| 194 | 
            +
            ### emit_error_label_event
         | 
| 195 | 
            +
             | 
| 196 | 
            +
            Default `emit_error_label_event` value is `true`.
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            Emitting error label events is default behavior.
         | 
| 199 | 
            +
             | 
| 200 | 
            +
            When using the followin configuration, OpenSearch plugin will cut error events on error handler:
         | 
| 201 | 
            +
             | 
| 202 | 
            +
            ```aconf
         | 
| 203 | 
            +
            emit_error_label_event false
         | 
| 204 | 
            +
            ```
         | 
| 205 | 
            +
             | 
| 193 206 | 
             
            ### Client/host certificate options
         | 
| 194 207 |  | 
| 195 208 | 
             
            Need to verify OpenSearch's certificate?  You can use the following parameter to specify a CA instead of using an environment variable.
         | 
    
        data/README.md
    CHANGED
    
    | @@ -76,6 +76,8 @@ Send your logs to OpenSearch (and search them with OpenSearch Dashboards maybe?) | |
| 76 76 | 
             
              + [reload_after](#reload-after)
         | 
| 77 77 | 
             
              + [validate_client_version](#validate-client-version)
         | 
| 78 78 | 
             
              + [unrecoverable_error_types](#unrecoverable-error-types)
         | 
| 79 | 
            +
              + [unrecoverable_record_types](#unrecoverable-record-types)
         | 
| 80 | 
            +
              + [emit_error_label_event](#emit-error-label-event)
         | 
| 79 81 | 
             
              + [verify os version at startup](#verify_os_version_at_startup)
         | 
| 80 82 | 
             
              + [default_opensearch_version](#default_opensearch_version)
         | 
| 81 83 | 
             
              + [custom_headers](#custom_headers)
         | 
| @@ -356,6 +358,15 @@ utc_index true | |
| 356 358 |  | 
| 357 359 | 
             
            By default, the records inserted into index `logstash-YYMMDD` with UTC (Coordinated Universal Time). This option allows to use local time if you describe utc_index to false.
         | 
| 358 360 |  | 
| 361 | 
            +
             | 
| 362 | 
            +
            ### suppress_type_name
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            If OpenSearch cluster complains types removal warnings, this can be suppressed with:
         | 
| 365 | 
            +
             | 
| 366 | 
            +
            ```
         | 
| 367 | 
            +
            suppress_type_name true
         | 
| 368 | 
            +
            ```
         | 
| 369 | 
            +
             | 
| 359 370 | 
             
            ### target_index_key
         | 
| 360 371 |  | 
| 361 372 | 
             
            Tell this plugin to find the index name to write to in the record under this key in preference to other mechanisms. Key can be specified as path to nested record using dot ('.') as a separator.
         | 
| @@ -1097,6 +1108,33 @@ Then, remove `rejected_execution_exception` from `unrecoverable_error_types` par | |
| 1097 1108 | 
             
            unrecoverable_error_types ["out_of_memory_error"]
         | 
| 1098 1109 | 
             
            ```
         | 
| 1099 1110 |  | 
| 1111 | 
            +
             | 
| 1112 | 
            +
            ### Unrecoverable Record Types
         | 
| 1113 | 
            +
             | 
| 1114 | 
            +
            Default `unrecoverable_record_types` parameter is set up loosely.
         | 
| 1115 | 
            +
            Because `json_parse_exception` is caused by invalid JSON record.
         | 
| 1116 | 
            +
            Another possible unrecoverable record error should be included within this paramater.
         | 
| 1117 | 
            +
             | 
| 1118 | 
            +
            If you want to handle `security_exception` as _unrecoverable exceptions_, please consider to change `unrecoverable_record_types` parameter from default value:
         | 
| 1119 | 
            +
             | 
| 1120 | 
            +
            ```
         | 
| 1121 | 
            +
            unrecoverable_record_types ["json_parse_exception", "security_exception"]
         | 
| 1122 | 
            +
            ```
         | 
| 1123 | 
            +
             | 
| 1124 | 
            +
            If this error type is included in OpenSearch response, an invalid record should be sent in `@ERROR` data pipeline.
         | 
| 1125 | 
            +
             | 
| 1126 | 
            +
            ### emit_error_label_event
         | 
| 1127 | 
            +
             | 
| 1128 | 
            +
            Default `emit_error_label_event` value is `true`.
         | 
| 1129 | 
            +
             | 
| 1130 | 
            +
            Emitting error label events is default behavior.
         | 
| 1131 | 
            +
             | 
| 1132 | 
            +
            When using the followin configuration, OpenSearch plugin will cut error events on error handler:
         | 
| 1133 | 
            +
             | 
| 1134 | 
            +
            ```aconf
         | 
| 1135 | 
            +
            emit_error_label_event false
         | 
| 1136 | 
            +
            ```
         | 
| 1137 | 
            +
             | 
| 1100 1138 | 
             
            ### verify_os_version_at_startup
         | 
| 1101 1139 |  | 
| 1102 1140 | 
             
            Because OpenSearch plugin will ought to change behavior each of OpenSearch major versions.
         | 
| @@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__) | |
| 3 3 |  | 
| 4 4 | 
             
            Gem::Specification.new do |s|
         | 
| 5 5 | 
             
              s.name          = 'fluent-plugin-opensearch'
         | 
| 6 | 
            -
              s.version       = '1.0. | 
| 6 | 
            +
              s.version       = '1.0.3'
         | 
| 7 7 | 
             
              s.authors       = ['Hiroshi Hatake']
         | 
| 8 8 | 
             
              s.email         = ['cosmo0920.wp@gmail.com']
         | 
| 9 9 | 
             
              s.description   = %q{Opensearch output plugin for Fluent event collector}
         | 
| @@ -73,6 +73,7 @@ module Fluent::Plugin | |
| 73 73 | 
             
                config_param :ca_file, :string, :default => nil
         | 
| 74 74 | 
             
                config_param :ssl_version, :enum, list: [:SSLv23, :TLSv1, :TLSv1_1, :TLSv1_2], :default => :TLSv1_2
         | 
| 75 75 | 
             
                config_param :with_transporter_log, :bool, :default => false
         | 
| 76 | 
            +
                config_param :emit_error_label_event, :bool, :default => true
         | 
| 76 77 | 
             
                config_param :sniffer_class_name, :string, :default => nil
         | 
| 77 78 | 
             
                config_param :custom_headers, :hash, :default => {}
         | 
| 78 79 | 
             
                config_param :docinfo_fields, :array, :default => ['_index', '_type', '_id']
         | 
| @@ -180,6 +181,13 @@ module Fluent::Plugin | |
| 180 181 | 
             
                  }
         | 
| 181 182 | 
             
                end
         | 
| 182 183 |  | 
| 184 | 
            +
                def emit_error_label_event(&block)
         | 
| 185 | 
            +
                  # If `emit_error_label_event` is specified as false, error event emittions are not occurred.
         | 
| 186 | 
            +
                  if emit_error_label_event
         | 
| 187 | 
            +
                    block.call
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 183 191 | 
             
                def start
         | 
| 184 192 | 
             
                  super
         | 
| 185 193 |  | 
| @@ -224,7 +232,9 @@ module Fluent::Plugin | |
| 224 232 | 
             
                def parse_time(value, event_time, tag)
         | 
| 225 233 | 
             
                  @timestamp_parser.call(value)
         | 
| 226 234 | 
             
                rescue => e
         | 
| 227 | 
            -
                   | 
| 235 | 
            +
                  emit_error_label_event do
         | 
| 236 | 
            +
                    router.emit_error_event(@timestamp_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @timestamp_key_format, 'value' => value}, e)
         | 
| 237 | 
            +
                  end
         | 
| 228 238 | 
             
                  return Time.at(event_time).to_time
         | 
| 229 239 | 
             
                end
         | 
| 230 240 |  | 
| @@ -49,8 +49,12 @@ class Fluent::Plugin::OpenSearchErrorHandler | |
| 49 49 | 
             
                unrecoverable_error_types.include?(type)
         | 
| 50 50 | 
             
              end
         | 
| 51 51 |  | 
| 52 | 
            +
              def unrecoverable_record_types
         | 
| 53 | 
            +
                @plugin.unrecoverable_record_types
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 52 56 | 
             
              def unrecoverable_record_error?(type)
         | 
| 53 | 
            -
                 | 
| 57 | 
            +
                unrecoverable_record_types.include?(type)
         | 
| 54 58 | 
             
              end
         | 
| 55 59 |  | 
| 56 60 | 
             
              def log_os_400_reason(&block)
         | 
| @@ -61,6 +65,13 @@ class Fluent::Plugin::OpenSearchErrorHandler | |
| 61 65 | 
             
                end
         | 
| 62 66 | 
             
              end
         | 
| 63 67 |  | 
| 68 | 
            +
              def emit_error_label_event(&block)
         | 
| 69 | 
            +
                # If `emit_error_label_event` is specified as false, error event emittions are not occurred.
         | 
| 70 | 
            +
                if @plugin.emit_error_label_event
         | 
| 71 | 
            +
                  block.call
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 64 75 | 
             
              def handle_error(response, tag, chunk, bulk_message_count, extracted_values)
         | 
| 65 76 | 
             
                items = response['items']
         | 
| 66 77 | 
             
                if items.nil? || !items.is_a?(Array)
         | 
| @@ -127,12 +138,16 @@ class Fluent::Plugin::OpenSearchErrorHandler | |
| 127 138 | 
             
                        reason += " [reason]: \'#{item[write_operation]['error']['reason']}\'"
         | 
| 128 139 | 
             
                      end
         | 
| 129 140 | 
             
                    end
         | 
| 130 | 
            -
                     | 
| 141 | 
            +
                    emit_error_label_event do
         | 
| 142 | 
            +
                      @plugin.router.emit_error_event(tag, time, rawrecord, OpenSearchError.new("400 - Rejected by OpenSearch#{reason}"))
         | 
| 143 | 
            +
                    end
         | 
| 131 144 | 
             
                  else
         | 
| 132 145 | 
             
                    if item[write_operation]['error'].is_a?(String)
         | 
| 133 146 | 
             
                      reason = item[write_operation]['error']
         | 
| 134 147 | 
             
                      stats[:errors_block_resp] += 1
         | 
| 135 | 
            -
                       | 
| 148 | 
            +
                      emit_error_label_event do
         | 
| 149 | 
            +
                        @plugin.router.emit_error_event(tag, time, rawrecord, OpenSearchError.new("#{status} - #{reason}"))
         | 
| 150 | 
            +
                      end
         | 
| 136 151 | 
             
                      next
         | 
| 137 152 | 
             
                    elsif item[write_operation].has_key?('error') && item[write_operation]['error'].has_key?('type')
         | 
| 138 153 | 
             
                      type = item[write_operation]['error']['type']
         | 
| @@ -141,7 +156,9 @@ class Fluent::Plugin::OpenSearchErrorHandler | |
| 141 156 | 
             
                        raise OpenSearchRequestAbortError, "Rejected OpenSearch due to #{type}"
         | 
| 142 157 | 
             
                      end
         | 
| 143 158 | 
             
                      if unrecoverable_record_error?(type)
         | 
| 144 | 
            -
                         | 
| 159 | 
            +
                        emit_error_label_event do
         | 
| 160 | 
            +
                          @plugin.router.emit_error_event(tag, time, rawrecord, OpenSearchError.new("#{status} - #{type}: #{reason}"))
         | 
| 161 | 
            +
                        end
         | 
| 145 162 | 
             
                        next
         | 
| 146 163 | 
             
                      else
         | 
| 147 164 | 
             
                        retry_stream.add(time, rawrecord) unless unrecoverable_record_error?(type)
         | 
| @@ -150,7 +167,9 @@ class Fluent::Plugin::OpenSearchErrorHandler | |
| 150 167 | 
             
                      # When we don't have a type field, something changed in the API
         | 
| 151 168 | 
             
                      # expected return values.
         | 
| 152 169 | 
             
                      stats[:errors_bad_resp] += 1
         | 
| 153 | 
            -
                       | 
| 170 | 
            +
                      emit_error_label_event do
         | 
| 171 | 
            +
                        @plugin.router.emit_error_event(tag, time, rawrecord, OpenSearchError.new("#{status} - No error type provided in the response"))
         | 
| 172 | 
            +
                      end
         | 
| 154 173 | 
             
                      next
         | 
| 155 174 | 
             
                    end
         | 
| 156 175 | 
             
                    stats[type] += 1
         | 
| @@ -110,6 +110,7 @@ module Fluent::Plugin | |
| 110 110 | 
             
                config_param :logstash_prefix_separator, :string, :default => '-'
         | 
| 111 111 | 
             
                config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
         | 
| 112 112 | 
             
                config_param :utc_index, :bool, :default => true
         | 
| 113 | 
            +
                config_param :suppress_type_name, :bool, :default => false
         | 
| 113 114 | 
             
                config_param :index_name, :string, :default => "fluentd"
         | 
| 114 115 | 
             
                config_param :id_key, :string, :default => nil
         | 
| 115 116 | 
             
                config_param :write_operation, :string, :default => "index"
         | 
| @@ -160,6 +161,8 @@ module Fluent::Plugin | |
| 160 161 | 
             
                config_param :validate_client_version, :bool, :default => false
         | 
| 161 162 | 
             
                config_param :prefer_oj_serializer, :bool, :default => false
         | 
| 162 163 | 
             
                config_param :unrecoverable_error_types, :array, :default => ["out_of_memory_error", "rejected_execution_exception"]
         | 
| 164 | 
            +
                config_param :unrecoverable_record_types, :array, :default => ["json_parse_exception"]
         | 
| 165 | 
            +
                config_param :emit_error_label_event, :bool, :default => true
         | 
| 163 166 | 
             
                config_param :verify_os_version_at_startup, :bool, :default => true
         | 
| 164 167 | 
             
                config_param :default_opensearch_version, :integer, :default => DEFAULT_OPENSEARCH_VERSION
         | 
| 165 168 | 
             
                config_param :log_os_400_reason, :bool, :default => false
         | 
| @@ -219,8 +222,8 @@ module Fluent::Plugin | |
| 219 222 | 
             
                    if conf[:assume_role_arn].nil?
         | 
| 220 223 | 
             
                      aws_container_credentials_relative_uri = conf[:ecs_container_credentials_relative_uri] || ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
         | 
| 221 224 | 
             
                      if aws_container_credentials_relative_uri.nil?
         | 
| 222 | 
            -
                        credentials = Aws::SharedCredentials.new({retries: 2}).credentials
         | 
| 223 | 
            -
                        credentials ||= Aws::InstanceProfileCredentials.new.credentials
         | 
| 225 | 
            +
                        credentials = Aws::SharedCredentials.new({retries: 2}).credentials rescue nil
         | 
| 226 | 
            +
                        credentials ||= Aws::InstanceProfileCredentials.new.credentials rescue nil
         | 
| 224 227 | 
             
                        credentials ||= Aws::ECSCredentials.new.credentials
         | 
| 225 228 | 
             
                      else
         | 
| 226 229 | 
             
                        credentials = Aws::ECSCredentials.new({
         | 
| @@ -462,6 +465,13 @@ module Fluent::Plugin | |
| 462 465 | 
             
                  placeholder_validities.include?(true)
         | 
| 463 466 | 
             
                end
         | 
| 464 467 |  | 
| 468 | 
            +
                def emit_error_label_event(&block)
         | 
| 469 | 
            +
                  # If `emit_error_label_event` is specified as false, error event emittions are not occurred.
         | 
| 470 | 
            +
                  if @emit_error_label_event
         | 
| 471 | 
            +
                    block.call
         | 
| 472 | 
            +
                  end
         | 
| 473 | 
            +
                end
         | 
| 474 | 
            +
             | 
| 465 475 | 
             
                def compression
         | 
| 466 476 | 
             
                  !(@compression_level == :no_compression)
         | 
| 467 477 | 
             
                end
         | 
| @@ -578,7 +588,9 @@ module Fluent::Plugin | |
| 578 588 | 
             
                def parse_time(value, event_time, tag)
         | 
| 579 589 | 
             
                  @time_parser.call(value)
         | 
| 580 590 | 
             
                rescue => e
         | 
| 581 | 
            -
                   | 
| 591 | 
            +
                  emit_error_label_event do
         | 
| 592 | 
            +
                    router.emit_error_event(@time_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @time_key_format, 'value' => value}, e)
         | 
| 593 | 
            +
                  end
         | 
| 582 594 | 
             
                  return Time.at(event_time).to_datetime
         | 
| 583 595 | 
             
                end
         | 
| 584 596 |  | 
| @@ -870,7 +882,9 @@ module Fluent::Plugin | |
| 870 882 | 
             
                        end
         | 
| 871 883 | 
             
                      end
         | 
| 872 884 | 
             
                    rescue => e
         | 
| 873 | 
            -
                       | 
| 885 | 
            +
                      emit_error_label_event do
         | 
| 886 | 
            +
                        router.emit_error_event(tag, time, record, e)
         | 
| 887 | 
            +
                      end
         | 
| 874 888 | 
             
                    end
         | 
| 875 889 | 
             
                  end
         | 
| 876 890 |  | 
| @@ -978,8 +992,12 @@ module Fluent::Plugin | |
| 978 992 | 
             
                    end
         | 
| 979 993 | 
             
                  end
         | 
| 980 994 |  | 
| 981 | 
            -
                   | 
| 982 | 
            -
             | 
| 995 | 
            +
                  if @suppress_type_name
         | 
| 996 | 
            +
                    target_type = nil
         | 
| 997 | 
            +
                  else
         | 
| 998 | 
            +
                    # OpenSearch only supports "_doc".
         | 
| 999 | 
            +
                    target_type = DEFAULT_TYPE_NAME
         | 
| 1000 | 
            +
                  end
         | 
| 983 1001 |  | 
| 984 1002 | 
             
                  meta.clear
         | 
| 985 1003 | 
             
                  meta["_index".freeze] = target_index
         | 
| @@ -191,12 +191,22 @@ module Fluent::Plugin | |
| 191 191 | 
             
                  tag = chunk.metadata.tag
         | 
| 192 192 | 
             
                  chunk.msgpack_each do |time, record|
         | 
| 193 193 | 
             
                    next unless record.is_a? Hash
         | 
| 194 | 
            -
             | 
| 195 194 | 
             
                    begin
         | 
| 196 | 
            -
                      record. | 
| 195 | 
            +
                      if record.has_key?(TIMESTAMP_FIELD)
         | 
| 196 | 
            +
                        rts = record[TIMESTAMP_FIELD]
         | 
| 197 | 
            +
                        dt = parse_time(rts, time, tag)
         | 
| 198 | 
            +
                      elsif record.has_key?(@time_key)
         | 
| 199 | 
            +
                        rts = record[@time_key]
         | 
| 200 | 
            +
                        dt = parse_time(rts, time, tag)
         | 
| 201 | 
            +
                      else
         | 
| 202 | 
            +
                        dt = Time.at(time).to_datetime
         | 
| 203 | 
            +
                      end
         | 
| 204 | 
            +
                      record.merge!({"@timestamp" => dt.iso8601(@time_precision)})
         | 
| 197 205 | 
             
                      bulk_message = append_record_to_messages(CREATE_OP, {}, headers, record, bulk_message)
         | 
| 198 206 | 
             
                    rescue => e
         | 
| 199 | 
            -
                       | 
| 207 | 
            +
                      emit_error_label_event do
         | 
| 208 | 
            +
                        router.emit_error_event(tag, time, record, e)
         | 
| 209 | 
            +
                      end
         | 
| 200 210 | 
             
                    end
         | 
| 201 211 | 
             
                  end
         | 
| 202 212 |  | 
| @@ -35,14 +35,18 @@ class TestOpenSearchErrorHandler < Test::Unit::TestCase | |
| 35 35 | 
             
                attr_reader :log
         | 
| 36 36 | 
             
                attr_reader :error_events
         | 
| 37 37 | 
             
                attr_accessor :unrecoverable_error_types
         | 
| 38 | 
            +
                attr_accessor :unrecoverable_record_types
         | 
| 38 39 | 
             
                attr_accessor :log_os_400_reason
         | 
| 39 40 | 
             
                attr_accessor :write_operation
         | 
| 41 | 
            +
                attr_accessor :emit_error_label_event
         | 
| 40 42 | 
             
                def initialize(log, log_os_400_reason = false)
         | 
| 41 43 | 
             
                  @log = log
         | 
| 42 44 | 
             
                  @write_operation = 'index'
         | 
| 43 45 | 
             
                  @error_events = []
         | 
| 44 46 | 
             
                  @unrecoverable_error_types = ["out_of_memory_error", "rejected_execution_exception"]
         | 
| 47 | 
            +
                  @unrecoverable_record_types = ["json_parse_exception"]
         | 
| 45 48 | 
             
                  @log_os_400_reason = log_os_400_reason
         | 
| 49 | 
            +
                  @emit_error_label_event = true
         | 
| 46 50 | 
             
                end
         | 
| 47 51 |  | 
| 48 52 | 
             
                def router
         | 
| @@ -135,6 +139,44 @@ class TestOpenSearchErrorHandler < Test::Unit::TestCase | |
| 135 139 | 
             
                end
         | 
| 136 140 | 
             
              end
         | 
| 137 141 |  | 
| 142 | 
            +
              class TEST400ResponseReasonWithoutErrorLog < self
         | 
| 143 | 
            +
                def setup
         | 
| 144 | 
            +
                  Fluent::Test.setup
         | 
| 145 | 
            +
                  @log_device = Fluent::Test::DummyLogDevice.new
         | 
| 146 | 
            +
                  dl_opts = {:log_level => ServerEngine::DaemonLogger::DEBUG}
         | 
| 147 | 
            +
                  logger = ServerEngine::DaemonLogger.new(@log_device, dl_opts)
         | 
| 148 | 
            +
                  @log = Fluent::Log.new(logger)
         | 
| 149 | 
            +
                  @plugin = TestPlugin.new(@log)
         | 
| 150 | 
            +
                  @handler = Fluent::Plugin::OpenSearchErrorHandler.new(@plugin)
         | 
| 151 | 
            +
                  @plugin.emit_error_label_event = false
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def test_400_responses_reason_log
         | 
| 155 | 
            +
                  records = [{time: 123, record: {"foo" => "bar", '_id' => 'abc'}}]
         | 
| 156 | 
            +
                  response = parse_response(%({
         | 
| 157 | 
            +
                  "took" : 0,
         | 
| 158 | 
            +
                  "errors" : true,
         | 
| 159 | 
            +
                  "items" : [
         | 
| 160 | 
            +
                    {
         | 
| 161 | 
            +
                      "create" : {
         | 
| 162 | 
            +
                        "_index" : "foo",
         | 
| 163 | 
            +
                        "status" : 400,
         | 
| 164 | 
            +
                        "error" : {
         | 
| 165 | 
            +
                          "type"  : "mapper_parsing_exception",
         | 
| 166 | 
            +
                          "reason" : "failed to parse"
         | 
| 167 | 
            +
                        }
         | 
| 168 | 
            +
                      }
         | 
| 169 | 
            +
                    }
         | 
| 170 | 
            +
                  ]
         | 
| 171 | 
            +
                 }))
         | 
| 172 | 
            +
                  chunk = MockChunk.new(records)
         | 
| 173 | 
            +
                  dummy_extracted_values = []
         | 
| 174 | 
            +
                  @handler.handle_error(response, 'atag', chunk, records.length, dummy_extracted_values)
         | 
| 175 | 
            +
                  assert_equal(0, @plugin.error_events.size)
         | 
| 176 | 
            +
                  assert_true(@plugin.error_events.empty?)
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
             | 
| 138 180 | 
             
              class TEST400ResponseReasonNoDebug < self
         | 
| 139 181 | 
             
                def setup
         | 
| 140 182 | 
             
                  Fluent::Test.setup
         | 
| @@ -177,6 +219,45 @@ class TestOpenSearchErrorHandler < Test::Unit::TestCase | |
| 177 219 | 
             
                end
         | 
| 178 220 | 
             
              end
         | 
| 179 221 |  | 
| 222 | 
            +
              class TEST400ResponseReasonNoDebugAndNoErrorLog < self
         | 
| 223 | 
            +
                def setup
         | 
| 224 | 
            +
                  Fluent::Test.setup
         | 
| 225 | 
            +
                  @log_device = Fluent::Test::DummyLogDevice.new
         | 
| 226 | 
            +
                  dl_opts = {:log_level => ServerEngine::DaemonLogger::INFO}
         | 
| 227 | 
            +
                  logger = ServerEngine::DaemonLogger.new(@log_device, dl_opts)
         | 
| 228 | 
            +
                  @log = Fluent::Log.new(logger)
         | 
| 229 | 
            +
                  @plugin = TestPlugin.new(@log)
         | 
| 230 | 
            +
                  @handler = Fluent::Plugin::OpenSearchErrorHandler.new(@plugin)
         | 
| 231 | 
            +
                  @plugin.log_os_400_reason = true
         | 
| 232 | 
            +
                  @plugin.emit_error_label_event = false
         | 
| 233 | 
            +
                end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                def test_400_responses_reason_log
         | 
| 236 | 
            +
                  records = [{time: 123, record: {"foo" => "bar", '_id' => 'abc'}}]
         | 
| 237 | 
            +
                  response = parse_response(%({
         | 
| 238 | 
            +
                  "took" : 0,
         | 
| 239 | 
            +
                  "errors" : true,
         | 
| 240 | 
            +
                  "items" : [
         | 
| 241 | 
            +
                    {
         | 
| 242 | 
            +
                      "create" : {
         | 
| 243 | 
            +
                        "_index" : "foo",
         | 
| 244 | 
            +
                        "status" : 400,
         | 
| 245 | 
            +
                        "error" : {
         | 
| 246 | 
            +
                          "type"  : "mapper_parsing_exception",
         | 
| 247 | 
            +
                          "reason" : "failed to parse"
         | 
| 248 | 
            +
                        }
         | 
| 249 | 
            +
                      }
         | 
| 250 | 
            +
                    }
         | 
| 251 | 
            +
                  ]
         | 
| 252 | 
            +
                 }))
         | 
| 253 | 
            +
                  chunk = MockChunk.new(records)
         | 
| 254 | 
            +
                  dummy_extracted_values = []
         | 
| 255 | 
            +
                  @handler.handle_error(response, 'atag', chunk, records.length, dummy_extracted_values)
         | 
| 256 | 
            +
                  assert_equal(0, @plugin.error_events.size)
         | 
| 257 | 
            +
                  assert_true(@plugin.error_events.empty?)
         | 
| 258 | 
            +
                end
         | 
| 259 | 
            +
              end
         | 
| 260 | 
            +
             | 
| 180 261 | 
             
              def test_nil_items_responses
         | 
| 181 262 | 
             
                records = [{time: 123, record: {"foo" => "bar", '_id' => 'abc'}}]
         | 
| 182 263 | 
             
                response = parse_response(%({
         | 
| @@ -2320,23 +2320,11 @@ class OpenSearchOutputTest < Test::Unit::TestCase | |
| 2320 2320 | 
             
              end
         | 
| 2321 2321 |  | 
| 2322 2322 | 
             
              data(
         | 
| 2323 | 
            -
                "OpenSearch default"=> {"os_version" => 1, "_type" => "_doc"},
         | 
| 2323 | 
            +
                "OpenSearch default"=> {"os_version" => 1, "_type" => "_doc", "suppress_type" => false},
         | 
| 2324 | 
            +
                "Suppressed type" => {"os_version" => 1, "_type" => nil, "suppress_type" => true},
         | 
| 2324 2325 | 
             
              )
         | 
| 2325 2326 | 
             
              def test_writes_to_speficied_type(data)
         | 
| 2326 | 
            -
                driver('', data["os_version"]).configure("")
         | 
| 2327 | 
            -
                stub_opensearch
         | 
| 2328 | 
            -
                stub_opensearch_info
         | 
| 2329 | 
            -
                driver.run(default_tag: 'test') do
         | 
| 2330 | 
            -
                  driver.feed(sample_record)
         | 
| 2331 | 
            -
                end
         | 
| 2332 | 
            -
                assert_equal(data['_type'], index_cmds.first['index']['_type'])
         | 
| 2333 | 
            -
              end
         | 
| 2334 | 
            -
             | 
| 2335 | 
            -
              data(
         | 
| 2336 | 
            -
                "OpenSearch 1"=> {"os_version" => 1, "_type" => "_doc"},
         | 
| 2337 | 
            -
              )
         | 
| 2338 | 
            -
              def test_writes_to_speficied_type_with_placeholders(data)
         | 
| 2339 | 
            -
                driver('', data["os_version"]).configure("")
         | 
| 2327 | 
            +
                driver('', data["os_version"]).configure("suppress_type_name #{data['suppress_type']}")
         | 
| 2340 2328 | 
             
                stub_opensearch
         | 
| 2341 2329 | 
             
                stub_opensearch_info
         | 
| 2342 2330 | 
             
                driver.run(default_tag: 'test') do
         | 
| @@ -9,7 +9,7 @@ class OpenSearchOutputDataStreamTest < Test::Unit::TestCase | |
| 9 9 | 
             
              include FlexMock::TestCase
         | 
| 10 10 | 
             
              include Fluent::Test::Helpers
         | 
| 11 11 |  | 
| 12 | 
            -
              attr_accessor :bulk_records
         | 
| 12 | 
            +
              attr_accessor :bulk_records, :index_cmds
         | 
| 13 13 |  | 
| 14 14 | 
             
              OPENSEARCH_DATA_STREAM_TYPE = "opensearch_data_stream"
         | 
| 15 15 |  | 
| @@ -96,12 +96,14 @@ class OpenSearchOutputDataStreamTest < Test::Unit::TestCase | |
| 96 96 | 
             
                  # {"create": {}}\nhttp://localhost:9200/_data_stream/foo_bar
         | 
| 97 97 | 
             
                  # {"@timestamp": ...}
         | 
| 98 98 | 
             
                  @bulk_records += req.body.split("\n").size / 2
         | 
| 99 | 
            +
                  @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
         | 
| 99 100 | 
             
                end
         | 
| 100 101 | 
             
                stub_request(:post, "http://#{url}#{template_name}/_bulk").with do |req|
         | 
| 101 102 | 
             
                  # bulk data must be pair of OP and records
         | 
| 102 103 | 
             
                  # {"create": {}}\nhttp://localhost:9200/_data_stream/foo_bar
         | 
| 103 104 | 
             
                  # {"@timestamp": ...}
         | 
| 104 105 | 
             
                  @bulk_records += req.body.split("\n").size / 2
         | 
| 106 | 
            +
                  @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
         | 
| 105 107 | 
             
                end
         | 
| 106 108 | 
             
              end
         | 
| 107 109 |  | 
| @@ -591,4 +593,72 @@ class OpenSearchOutputDataStreamTest < Test::Unit::TestCase | |
| 591 593 |  | 
| 592 594 | 
             
                assert_equal(4, connection_resets)
         | 
| 593 595 | 
             
              end
         | 
| 596 | 
            +
             | 
| 597 | 
            +
              def test_uses_custom_time_key
         | 
| 598 | 
            +
                stub_default
         | 
| 599 | 
            +
                stub_bulk_feed
         | 
| 600 | 
            +
                conf = config_element(
         | 
| 601 | 
            +
                  'ROOT', '', {
         | 
| 602 | 
            +
                  '@type' => OPENSEARCH_DATA_STREAM_TYPE,
         | 
| 603 | 
            +
                  'data_stream_name' => 'foo',
         | 
| 604 | 
            +
                  'data_stream_template_name' => 'foo_tpl',
         | 
| 605 | 
            +
                  'time_key' => 'vtm'
         | 
| 606 | 
            +
                })
         | 
| 607 | 
            +
             | 
| 608 | 
            +
                ts = DateTime.new(2021,2,3).iso8601(9)
         | 
| 609 | 
            +
                record = {
         | 
| 610 | 
            +
                  'vtm' => ts,
         | 
| 611 | 
            +
                  'message' => 'Sample Record'
         | 
| 612 | 
            +
                }
         | 
| 613 | 
            +
             | 
| 614 | 
            +
                driver(conf).run(default_tag: 'test') do
         | 
| 615 | 
            +
                  driver.feed(record)
         | 
| 616 | 
            +
                end
         | 
| 617 | 
            +
                assert(index_cmds[1].has_key? '@timestamp')
         | 
| 618 | 
            +
                assert_equal(ts, index_cmds[1]['@timestamp'])
         | 
| 619 | 
            +
              end
         | 
| 620 | 
            +
             | 
| 621 | 
            +
              def test_uses_custom_time_key_with_format
         | 
| 622 | 
            +
                stub_default
         | 
| 623 | 
            +
                stub_bulk_feed
         | 
| 624 | 
            +
                conf = config_element(
         | 
| 625 | 
            +
                  'ROOT', '', {
         | 
| 626 | 
            +
                  '@type' => OPENSEARCH_DATA_STREAM_TYPE,
         | 
| 627 | 
            +
                  'data_stream_name' => 'foo',
         | 
| 628 | 
            +
                  'data_stream_template_name' => 'foo_tpl',
         | 
| 629 | 
            +
                  'time_key' => 'vtm',
         | 
| 630 | 
            +
                  'time_key_format' => '%Y-%m-%d %H:%M:%S.%N%z'
         | 
| 631 | 
            +
                })
         | 
| 632 | 
            +
                ts = "2021-02-03 13:14:01.673+02:00"
         | 
| 633 | 
            +
                record = {
         | 
| 634 | 
            +
                  'vtm' => ts,
         | 
| 635 | 
            +
                  'message' => 'Sample Record'
         | 
| 636 | 
            +
                }
         | 
| 637 | 
            +
                driver(conf).run(default_tag: 'test') do
         | 
| 638 | 
            +
                  driver.feed(record)
         | 
| 639 | 
            +
                end
         | 
| 640 | 
            +
                assert(index_cmds[1].has_key? '@timestamp')
         | 
| 641 | 
            +
                assert_equal(DateTime.parse(ts).iso8601(9), index_cmds[1]['@timestamp'])
         | 
| 642 | 
            +
              end
         | 
| 643 | 
            +
             | 
| 644 | 
            +
              def test_record_no_timestamp
         | 
| 645 | 
            +
                stub_default
         | 
| 646 | 
            +
                stub_bulk_feed
         | 
| 647 | 
            +
                stub_default
         | 
| 648 | 
            +
                stub_bulk_feed
         | 
| 649 | 
            +
                conf = config_element(
         | 
| 650 | 
            +
                  'ROOT', '', {
         | 
| 651 | 
            +
                  '@type' => OPENSEARCH_DATA_STREAM_TYPE,
         | 
| 652 | 
            +
                  'data_stream_name' => 'foo',
         | 
| 653 | 
            +
                  'data_stream_template_name' => 'foo_tpl'
         | 
| 654 | 
            +
                })
         | 
| 655 | 
            +
                record = {
         | 
| 656 | 
            +
                  'message' => 'Sample Record'
         | 
| 657 | 
            +
                }
         | 
| 658 | 
            +
                driver(conf).run(default_tag: 'test') do
         | 
| 659 | 
            +
                  driver.feed(record)
         | 
| 660 | 
            +
                end
         | 
| 661 | 
            +
                assert(index_cmds[1].has_key? '@timestamp')
         | 
| 662 | 
            +
              end
         | 
| 663 | 
            +
             | 
| 594 664 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: fluent-plugin-opensearch
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0. | 
| 4 | 
            +
              version: 1.0.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Hiroshi Hatake
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022- | 
| 11 | 
            +
            date: 2022-03-25 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: fluentd
         |