logstash-filter-grok 4.0.4 → 4.1.0
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/CHANGELOG.md +3 -0
- data/lib/logstash/filters/grok.rb +43 -21
- data/logstash-filter-grok.gemspec +1 -1
- data/spec/filters/grok_spec.rb +34 -18
- metadata +2 -4
- data/lib/logstash/filters/grok/timeout_enforcer.rb +0 -72
- data/lib/logstash/filters/grok/timeout_exception.rb +0 -21
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 228ed753ac1ef592e06994285f15a05f8000ef24b70e12b13176f64d9ac9761e
         | 
| 4 | 
            +
              data.tar.gz: 31c62f48ac6240644c0e4e0eebd55e80944b5f693769f2a204540b4cb05dfed2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: cb7923ffbcc68987bcee124bc4c85155db5e99ecbd74baf9ef27daa1e400f7de0c7072cfcfe460e1697230b4a1adbedeb98d3f5943e3a73d9f0cce5f152a0b63
         | 
| 7 | 
            +
              data.tar.gz: 5465de021ca4e73f95cd2ee3ca5d45bdf2cbc6e116ef16f436f89231911dd1332aeb26e37d3e16a409a8370c0072389d82ee5dcd69717c73fc2e4032cb1081d0
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,6 @@ | |
| 1 | 
            +
            ## 4.1.0
         | 
| 2 | 
            +
             - Changed timeout handling using the Timeout class [#147](https://github.com/logstash-plugins/logstash-filter-grok/pull/147)
         | 
| 3 | 
            +
             | 
| 1 4 | 
             
            ## 4.0.4
         | 
| 2 5 | 
             
              - Added info and link to documentation for logstash-filter-dissect as another option for extracting unstructured event data into fields
         | 
| 3 6 | 
             
              [#144](https://github.com/logstash-plugins/logstash-filter-grok/issues/144)
         | 
| @@ -5,6 +5,7 @@ | |
| 5 5 | 
             
              require "logstash/patterns/core"
         | 
| 6 6 | 
             
              require "grok-pure" # rubygem 'jls-grok'
         | 
| 7 7 | 
             
              require "set"
         | 
| 8 | 
            +
              require "timeout"
         | 
| 8 9 |  | 
| 9 10 | 
             
              # Parse arbitrary text and structure it.
         | 
| 10 11 | 
             
              #
         | 
| @@ -144,8 +145,6 @@ | |
| 144 145 | 
             
              #
         | 
| 145 146 | 
             
              class LogStash::Filters::Grok < LogStash::Filters::Base
         | 
| 146 147 | 
             
                config_name "grok"
         | 
| 147 | 
            -
                require "logstash/filters/grok/timeout_enforcer"
         | 
| 148 | 
            -
                require "logstash/filters/grok/timeout_exception"
         | 
| 149 148 |  | 
| 150 149 | 
             
                # A hash of matches of field => value
         | 
| 151 150 | 
             
                #
         | 
| @@ -237,8 +236,6 @@ | |
| 237 236 | 
             
                # will be parsed and `hello world` will overwrite the original message.
         | 
| 238 237 | 
             
                config :overwrite, :validate => :array, :default => []
         | 
| 239 238 |  | 
| 240 | 
            -
                attr_reader :timeout_enforcer
         | 
| 241 | 
            -
                
         | 
| 242 239 | 
             
                # Register default pattern paths
         | 
| 243 240 | 
             
                @@patterns_path ||= Set.new
         | 
| 244 241 | 
             
                @@patterns_path += [
         | 
| @@ -246,14 +243,10 @@ | |
| 246 243 | 
             
                  LogStash::Environment.pattern_path("*")
         | 
| 247 244 | 
             
                ]
         | 
| 248 245 |  | 
| 249 | 
            -
                public
         | 
| 250 246 | 
             
                def register
         | 
| 251 247 | 
             
                  # a cache of capture name handler methods.
         | 
| 252 248 | 
             
                  @handlers = {}
         | 
| 253 249 |  | 
| 254 | 
            -
                  @timeout_enforcer = TimeoutEnforcer.new(@logger, @timeout_millis * 1000000)
         | 
| 255 | 
            -
                  @timeout_enforcer.start! unless @timeout_millis == 0
         | 
| 256 | 
            -
             | 
| 257 250 | 
             
                  @patternfiles = []
         | 
| 258 251 |  | 
| 259 252 | 
             
                  # Have @@patterns_path show first. Last-in pattern definitions win; this
         | 
| @@ -284,9 +277,13 @@ | |
| 284 277 | 
             
                  end # @match.each
         | 
| 285 278 | 
             
                  @match_counter = metric.counter(:matches)
         | 
| 286 279 | 
             
                  @failure_counter = metric.counter(:failures)
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                  # divide by float to allow fractionnal seconds, the Timeout class timeout value is in seconds but the underlying
         | 
| 282 | 
            +
                  # executor resolution is in microseconds so fractionnal second parameter down to microseconds is possible.
         | 
| 283 | 
            +
                  # see https://github.com/jruby/jruby/blob/9.2.7.0/core/src/main/java/org/jruby/ext/timeout/Timeout.java#L125
         | 
| 284 | 
            +
                  @timeout_seconds = @timeout_millis / 1000.0
         | 
| 287 285 | 
             
                end # def register
         | 
| 288 286 |  | 
| 289 | 
            -
                public
         | 
| 290 287 | 
             
                def filter(event)
         | 
| 291 288 | 
             
                  matched = false
         | 
| 292 289 |  | 
| @@ -309,13 +306,17 @@ | |
| 309 306 | 
             
                  end
         | 
| 310 307 |  | 
| 311 308 | 
             
                  @logger.debug? and @logger.debug("Event now: ", :event => event)
         | 
| 312 | 
            -
                rescue  | 
| 309 | 
            +
                rescue GrokTimeoutException => e
         | 
| 313 310 | 
             
                  @logger.warn(e.message)
         | 
| 314 311 | 
             
                  metric.increment(:timeouts)
         | 
| 315 312 | 
             
                  event.tag(@tag_on_timeout)
         | 
| 316 313 | 
             
                end # def filter
         | 
| 317 314 |  | 
| 315 | 
            +
                def close
         | 
| 316 | 
            +
                end
         | 
| 317 | 
            +
             | 
| 318 318 | 
             
                private
         | 
| 319 | 
            +
             | 
| 319 320 | 
             
                def match(groks, field, event)
         | 
| 320 321 | 
             
                  input = event.get(field)
         | 
| 321 322 | 
             
                  if input.is_a?(Array)
         | 
| @@ -331,15 +332,13 @@ | |
| 331 332 | 
             
                  @logger.warn("Grok regexp threw exception", :exception => e.message, :backtrace => e.backtrace, :class => e.class.name)
         | 
| 332 333 | 
             
                  return false
         | 
| 333 334 | 
             
                end
         | 
| 334 | 
            -
             | 
| 335 | 
            -
                private
         | 
| 335 | 
            +
             | 
| 336 336 | 
             
                def match_against_groks(groks, field, input, event)
         | 
| 337 337 | 
             
                  input = input.to_s
         | 
| 338 338 | 
             
                  matched = false
         | 
| 339 339 | 
             
                  groks.each do |grok|
         | 
| 340 340 | 
             
                    # Convert anything else to string (number, hash, etc)
         | 
| 341 | 
            -
             | 
| 342 | 
            -
                    matched = @timeout_enforcer.grok_till_timeout(grok, field, input)
         | 
| 341 | 
            +
                    matched = grok_till_timeout(grok, field, input)
         | 
| 343 342 | 
             
                    if matched
         | 
| 344 343 | 
             
                      grok.capture(matched) {|field, value| handle(field, value, event)}
         | 
| 345 344 | 
             
                      break if @break_on_match
         | 
| @@ -349,7 +348,14 @@ | |
| 349 348 | 
             
                  matched
         | 
| 350 349 | 
             
                end
         | 
| 351 350 |  | 
| 352 | 
            -
                 | 
| 351 | 
            +
                def grok_till_timeout(grok, field, value)
         | 
| 352 | 
            +
                  begin
         | 
| 353 | 
            +
                    @timeout_seconds > 0.0 ? Timeout.timeout(@timeout_seconds, TimeoutError) { grok.execute(value) } : grok.execute(value)
         | 
| 354 | 
            +
                  rescue TimeoutError
         | 
| 355 | 
            +
                    raise GrokTimeoutException.new(grok, field, value)
         | 
| 356 | 
            +
                  end
         | 
| 357 | 
            +
                end
         | 
| 358 | 
            +
             | 
| 353 359 | 
             
                def handle(field, value, event)
         | 
| 354 360 | 
             
                  return if (value.nil? || (value.is_a?(String) && value.empty?)) unless @keep_empty_captures
         | 
| 355 361 |  | 
| @@ -373,7 +379,6 @@ | |
| 373 379 | 
             
                  end
         | 
| 374 380 | 
             
                end
         | 
| 375 381 |  | 
| 376 | 
            -
                private
         | 
| 377 382 | 
             
                def patterns_files_from_paths(paths, glob)
         | 
| 378 383 | 
             
                  patternfiles = []
         | 
| 379 384 | 
             
                  @logger.debug("Grok patterns path", :paths => paths)
         | 
| @@ -394,7 +399,6 @@ | |
| 394 399 | 
             
                  patternfiles
         | 
| 395 400 | 
             
                end # def patterns_files_from_paths
         | 
| 396 401 |  | 
| 397 | 
            -
                private
         | 
| 398 402 | 
             
                def add_patterns_from_files(paths, grok)
         | 
| 399 403 | 
             
                  paths.each do |path|
         | 
| 400 404 | 
             
                    if !File.exists?(path)
         | 
| @@ -404,7 +408,6 @@ | |
| 404 408 | 
             
                  end
         | 
| 405 409 | 
             
                end # def add_patterns_from_files
         | 
| 406 410 |  | 
| 407 | 
            -
                private
         | 
| 408 411 | 
             
                def add_patterns_from_inline_definition(pattern_definitions, grok)
         | 
| 409 412 | 
             
                  pattern_definitions.each do |name, pattern|
         | 
| 410 413 | 
             
                    next if pattern.nil?
         | 
| @@ -412,8 +415,27 @@ | |
| 412 415 | 
             
                  end
         | 
| 413 416 | 
             
                end
         | 
| 414 417 |  | 
| 415 | 
            -
                 | 
| 416 | 
            -
             | 
| 417 | 
            -
                 | 
| 418 | 
            +
                class TimeoutError < RuntimeError; end
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                class GrokTimeoutException < Exception
         | 
| 421 | 
            +
                  attr_reader :grok, :field, :value
         | 
| 418 422 |  | 
| 423 | 
            +
                  def initialize(grok, field, value)
         | 
| 424 | 
            +
                    @grok = grok
         | 
| 425 | 
            +
                    @field = field
         | 
| 426 | 
            +
                    @value = value
         | 
| 427 | 
            +
                  end
         | 
| 428 | 
            +
             | 
| 429 | 
            +
                  def message
         | 
| 430 | 
            +
                    "Timeout executing grok '#{@grok.pattern}' against field '#{field}' with value '#{trunc_value}'!"
         | 
| 431 | 
            +
                  end
         | 
| 432 | 
            +
             | 
| 433 | 
            +
                  def trunc_value
         | 
| 434 | 
            +
                    if value.size <= 255 # If no more than 255 chars
         | 
| 435 | 
            +
                      value
         | 
| 436 | 
            +
                    else
         | 
| 437 | 
            +
                      "Value too large to output (#{value.bytesize} bytes)! First 255 chars are: #{value[0..255]}"
         | 
| 438 | 
            +
                    end
         | 
| 439 | 
            +
                  end
         | 
| 440 | 
            +
                end
         | 
| 419 441 | 
             
              end # class LogStash::Filters::Grok
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            Gem::Specification.new do |s|
         | 
| 2 2 |  | 
| 3 3 | 
             
              s.name            = 'logstash-filter-grok'
         | 
| 4 | 
            -
              s.version         = '4.0 | 
| 4 | 
            +
              s.version         = '4.1.0'
         | 
| 5 5 | 
             
              s.licenses        = ['Apache License (2.0)']
         | 
| 6 6 | 
             
              s.summary         = "Parses unstructured event data into fields"
         | 
| 7 7 | 
             
              s.description     = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
         | 
    
        data/spec/filters/grok_spec.rb
    CHANGED
    
    | @@ -850,26 +850,9 @@ describe LogStash::Filters::Grok do | |
| 850 850 | 
             
                  plugin.register
         | 
| 851 851 | 
             
                end
         | 
| 852 852 |  | 
| 853 | 
            -
                it "should start the timeout enforcer" do
         | 
| 854 | 
            -
                  expect(plugin.timeout_enforcer.running).to be true
         | 
| 855 | 
            -
                end
         | 
| 856 | 
            -
             | 
| 857 | 
            -
                context "with the timeout enforcer disabled" do
         | 
| 858 | 
            -
                  let(:config) { super.merge("timeout_millis" => 0) }
         | 
| 859 | 
            -
             | 
| 860 | 
            -
                  it "should not start the timeout enforcer" do
         | 
| 861 | 
            -
                    expect(plugin.timeout_enforcer.running).to be false
         | 
| 862 | 
            -
                  end
         | 
| 863 | 
            -
                end
         | 
| 864 | 
            -
             | 
| 865 853 | 
             
                it "should close cleanly" do
         | 
| 866 854 | 
             
                  expect { plugin.do_close }.not_to raise_error
         | 
| 867 855 | 
             
                end
         | 
| 868 | 
            -
             | 
| 869 | 
            -
                it "should stop the timeout enforcer" do
         | 
| 870 | 
            -
                  plugin.do_close
         | 
| 871 | 
            -
                  expect(plugin.timeout_enforcer.running).to be false
         | 
| 872 | 
            -
                end
         | 
| 873 856 | 
             
              end
         | 
| 874 857 |  | 
| 875 858 | 
             
              describe "after grok when the event is JSON serialised the field values are unchanged" do
         | 
| @@ -929,4 +912,37 @@ describe LogStash::Filters::Grok do | |
| 929 912 | 
             
                end
         | 
| 930 913 | 
             
              end
         | 
| 931 914 |  | 
| 932 | 
            -
             | 
| 915 | 
            +
             | 
| 916 | 
            +
              describe "direct plugin testing" do
         | 
| 917 | 
            +
                subject do
         | 
| 918 | 
            +
                  plugin = LogStash::Filters::Grok.new(options)
         | 
| 919 | 
            +
                  plugin.register
         | 
| 920 | 
            +
                  plugin
         | 
| 921 | 
            +
                end
         | 
| 922 | 
            +
             | 
| 923 | 
            +
                let(:data) { {"message" => message} }
         | 
| 924 | 
            +
                let(:event) { LogStash::Event.new(data) }
         | 
| 925 | 
            +
             | 
| 926 | 
            +
                context 'when timeouts are explicitly disabled' do
         | 
| 927 | 
            +
                  let(:options) do
         | 
| 928 | 
            +
                    {
         | 
| 929 | 
            +
                      "timeout_millis" => 0
         | 
| 930 | 
            +
                    }
         | 
| 931 | 
            +
                  end
         | 
| 932 | 
            +
             | 
| 933 | 
            +
                  context 'when given a pathological input' do
         | 
| 934 | 
            +
                    let(:message) { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
         | 
| 935 | 
            +
                    let(:options) { super().merge("match" => { "message" => "(.*a){30}" }) }
         | 
| 936 | 
            +
             | 
| 937 | 
            +
                    it 'blocks for at least 3 seconds' do
         | 
| 938 | 
            +
                      blocking_exception_class = Class.new(::Exception) # avoid RuntimeError
         | 
| 939 | 
            +
                      expect do
         | 
| 940 | 
            +
                        Timeout.timeout(3, blocking_exception_class) do
         | 
| 941 | 
            +
                          subject.filter(event)
         | 
| 942 | 
            +
                        end
         | 
| 943 | 
            +
                      end.to raise_exception(blocking_exception_class)
         | 
| 944 | 
            +
                    end
         | 
| 945 | 
            +
                  end
         | 
| 946 | 
            +
                end
         | 
| 947 | 
            +
              end
         | 
| 948 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: logstash-filter-grok
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 4.0 | 
| 4 | 
            +
              version: 4.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Elastic
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2019-07-22 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -116,8 +116,6 @@ files: | |
| 116 116 | 
             
            - README.md
         | 
| 117 117 | 
             
            - docs/index.asciidoc
         | 
| 118 118 | 
             
            - lib/logstash/filters/grok.rb
         | 
| 119 | 
            -
            - lib/logstash/filters/grok/timeout_enforcer.rb
         | 
| 120 | 
            -
            - lib/logstash/filters/grok/timeout_exception.rb
         | 
| 121 119 | 
             
            - logstash-filter-grok.gemspec
         | 
| 122 120 | 
             
            - spec/filters/grok_spec.rb
         | 
| 123 121 | 
             
            homepage: http://www.elastic.co/guide/en/logstash/current/index.html
         | 
| @@ -1,72 +0,0 @@ | |
| 1 | 
            -
            class LogStash::Filters::Grok::TimeoutEnforcer
         | 
| 2 | 
            -
              def initialize(logger, timeout_nanos)
         | 
| 3 | 
            -
                @logger = logger
         | 
| 4 | 
            -
                @running = java.util.concurrent.atomic.AtomicBoolean.new(false)
         | 
| 5 | 
            -
                @timeout_nanos = timeout_nanos
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                # Stores running matches with their start time, this is used to cancel long running matches
         | 
| 8 | 
            -
                # Is a map of Thread => start_time
         | 
| 9 | 
            -
                @threads_to_start_time = java.util.concurrent.ConcurrentHashMap.new
         | 
| 10 | 
            -
              end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              def running
         | 
| 13 | 
            -
                @running.get()
         | 
| 14 | 
            -
              end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              def grok_till_timeout(grok, field, value)
         | 
| 17 | 
            -
                begin
         | 
| 18 | 
            -
                  thread = java.lang.Thread.currentThread()
         | 
| 19 | 
            -
                  @threads_to_start_time.put(thread, java.lang.System.nanoTime)
         | 
| 20 | 
            -
                  grok.execute(value)
         | 
| 21 | 
            -
                rescue InterruptedRegexpError, java.lang.InterruptedException => e
         | 
| 22 | 
            -
                  raise ::LogStash::Filters::Grok::TimeoutException.new(grok, field, value)
         | 
| 23 | 
            -
                ensure
         | 
| 24 | 
            -
                  # If the regexp finished, but interrupt was called after, we'll want to
         | 
| 25 | 
            -
                  # clear the interrupted status anyway
         | 
| 26 | 
            -
                  @threads_to_start_time.remove(thread)
         | 
| 27 | 
            -
                  thread.interrupted
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
              end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
              def start!
         | 
| 32 | 
            -
                @running.set(true)
         | 
| 33 | 
            -
                @timer_thread = Thread.new do
         | 
| 34 | 
            -
                  while @running.get()
         | 
| 35 | 
            -
                    begin
         | 
| 36 | 
            -
                      cancel_timed_out!
         | 
| 37 | 
            -
                    rescue Exception => e
         | 
| 38 | 
            -
                      @logger.error("Error while attempting to check/cancel excessively long grok patterns",
         | 
| 39 | 
            -
                                    :message => e.message,
         | 
| 40 | 
            -
                                    :class => e.class.name,
         | 
| 41 | 
            -
                                    :backtrace => e.backtrace
         | 
| 42 | 
            -
                                   )
         | 
| 43 | 
            -
                    end
         | 
| 44 | 
            -
                    sleep 0.25
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
                end
         | 
| 47 | 
            -
              end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
              def stop!
         | 
| 50 | 
            -
                @running.set(false)
         | 
| 51 | 
            -
                # Check for the thread mostly for a fast start/shutdown scenario
         | 
| 52 | 
            -
                @timer_thread.join if @timer_thread
         | 
| 53 | 
            -
              end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
              private
         | 
| 56 | 
            -
             | 
| 57 | 
            -
              def cancel_timed_out!
         | 
| 58 | 
            -
                now = java.lang.System.nanoTime # save ourselves some nanotime calls
         | 
| 59 | 
            -
                @threads_to_start_time.keySet.each do |thread|
         | 
| 60 | 
            -
                  # Use compute to lock this value
         | 
| 61 | 
            -
                  @threads_to_start_time.computeIfPresent(thread) do |thread, start_time|
         | 
| 62 | 
            -
                    if start_time < now && now - start_time > @timeout_nanos
         | 
| 63 | 
            -
                      thread.interrupt
         | 
| 64 | 
            -
                      nil # Delete the key
         | 
| 65 | 
            -
                    else
         | 
| 66 | 
            -
                      start_time # preserve the key
         | 
| 67 | 
            -
                    end
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
                end
         | 
| 70 | 
            -
              end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
            end
         | 
| @@ -1,21 +0,0 @@ | |
| 1 | 
            -
            class LogStash::Filters::Grok::TimeoutException < Exception
         | 
| 2 | 
            -
              attr_reader :grok, :field, :value
         | 
| 3 | 
            -
              
         | 
| 4 | 
            -
              def initialize(grok=nil, field=nil, value=nil)
         | 
| 5 | 
            -
                @field = field
         | 
| 6 | 
            -
                @value = value
         | 
| 7 | 
            -
                @grok = grok
         | 
| 8 | 
            -
              end
         | 
| 9 | 
            -
             | 
| 10 | 
            -
              def message
         | 
| 11 | 
            -
                "Timeout executing grok '#{@grok.pattern}' against field '#{field}' with value '#{trunc_value}'!"
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              def trunc_value
         | 
| 15 | 
            -
                if value.size <= 255 # If no more than 255 chars
         | 
| 16 | 
            -
                  value
         | 
| 17 | 
            -
                else
         | 
| 18 | 
            -
                  "Value too large to output (#{value.bytesize} bytes)! First 255 chars are: #{value[0..255]}"
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
              end
         | 
| 21 | 
            -
            end
         |