fluent/fluentd · error · ArgumentError

record must be a Hash: #{record.class}

Error message

record must be a Hash: #{record.class}

What it means

The third argument guard in Output#metadata (lib/fluent/plugin/output.rb:940): the record must be nil or a Hash, otherwise ArgumentError. Records are the fundamental key-value unit in fluentd, and the chunking layer keys buffers by record contents, so a non-Hash cannot be chunked. Most often this indicates a structural bug (wrong variable passed) rather than bad data.

Source

Thrown at lib/fluent/plugin/output.rb:940

          execute_chunking(tag, es, enqueue: (@flush_mode == :immediate))
          if !@retry && @buffer.queued?(nil, optimistic: true)
            submit_flush_once
          end
        rescue
          # TODO: separate number of errors into emit errors and write/flush errors
          @num_errors_metrics.inc
          raise
        end
      end

      # TODO: optimize this code
      def metadata(tag, time, record)
        # this arguments are ordered in output plugin's rule
        # Metadata 's argument order is different from this one (timekey, tag, variables)

        raise ArgumentError, "tag must be a String: #{tag.class}" unless tag.nil? || tag.is_a?(String)
        raise ArgumentError, "time must be a Fluent::EventTime (or Integer): #{time.class}" unless time.nil? || time.is_a?(Fluent::EventTime) || time.is_a?(Integer)
        raise ArgumentError, "record must be a Hash: #{record.class}" unless record.nil? || record.is_a?(Hash)

        if @chunk_keys.nil? && @chunk_key_time.nil? && @chunk_key_tag.nil?
          # for tests
          return Struct.new(:timekey, :tag, :variables).new
        end

        timekey = @chunk_key_time ? calculate_timekey(time) : nil
        @_metadata_cache ||= MetadataCache.new

        # timekey is int from epoch, and `timekey - timekey % 60` is assumed to mach with 0s of each minutes.
        # it's wrong if timezone is configured as one which supports leap second, but it's very rare and
        # we can ignore it (especially in production systems).
        if @chunk_keys.empty?
          return @_metadata_cache.metadata if @_metadata_cache.cached?(timekey: timekey, tag: tag)

          meta = if @chunk_key_time && @chunk_key_tag
                   @buffer.metadata(timekey: timekey, tag: tag)
                 elsif @chunk_key_time

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Pass the single record Hash (the third element of the usual tag/time/record triple).
  2. If iterating an array like [tag, time, record], destructure with tag, time, record = entry instead of passing the entry wholesale.
  3. Add an assertion/guard in custom plugins: raise or skip unless record.is_a?(Hash) before calling metadata.

Example fix

# before
entry = ['a.b', time, {'k' => 1}]
meta = output.metadata('a.b', time, entry)   # Array in record slot -> ArgumentError
# after
_tag, _time, record = entry
meta = output.metadata('a.b', time, record)
Defensive patterns

Strategy: type-guard

Validate before calling

unless record.is_a?(Hash) || record.nil?
  record = {'value' => record.to_s}
end
meta = output.metadata(tag, time, record)

Type guard

def fluent_record?(r)
  r.is_a?(Hash) || r.nil?
end

Try / catch

rescue ArgumentError => e
  raise unless e.message =~ /record must be a Hash/
  log.error 'structural bug: non-Hash record reached chunking', record_class: record.class

Prevention

When it happens

Trigger: Calling output.metadata(tag, time, 'raw string') or metadata(tag, time, [time, record]) (passing the MessagePackEventStream entry pair instead of the record); passing an Array of records or an OpenStruct; a custom filter returning non-Hash entries from its mutators.

Common situations: Custom output plugin development and unit tests constructing Metadata by hand; porting v0-era plugin code where arrays of [tag, time, record] were iterated incorrectly; feeding a OneEventStream's unpacked tuple into the wrong parameter.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21). Data as JSON: /api/errors/29994e8a8b9d248e. Report an issue: GitHub.