fluent/fluentd · error · Fluent::TimeParser::TimeParseError

value must be string: #{value}

Error message

value must be string: #{value}

What it means

Fluent::TimeParseError raised by TimeParser#parse (lib/fluent/time.rb:270) when the value passed is not a Ruby String. StringTimeParser (and strptime) can only handle strings, so an Integer, Float, Hash, or nil arriving at parse() is a hard type error, distinct from an unparseable-but-string value which produces 'invalid time format' instead.

Source

Thrown at lib/fluent/time.rb:270

                 else
                   ->(v) { t = strptime.exec(v); Fluent::EventTime.new(t.to_i + t.utc_offset - utc_offset, t.nsec) }
                 end
               when format then
                 if utc_offset.nil?
                   ->(v){ t = Time.strptime(v, format); Fluent::EventTime.new(t.to_i, t.nsec) }
                 elsif utc_offset.respond_to?(:call)
                   ->(v){ t = Time.strptime(v, format); Fluent::EventTime.new(t.to_i + t.utc_offset - utc_offset.call(t), t.nsec) }
                 else
                   ->(v){ t = Time.strptime(v, format); Fluent::EventTime.new(t.to_i + t.utc_offset - utc_offset, t.nsec) }
                 end
               else ->(v){ Fluent::EventTime.parse(v) }
               end
    end

    # TODO: new cache mechanism using format string
    def parse(value)
      unless value.is_a?(String)
        raise TimeParseError, "value must be string: #{value}"
      end

      if @cache1_key == value
        return @cache1_time
      elsif @cache2_key == value
        return @cache2_time
      else
        begin
          time = @parse.call(value)
        rescue => e
          raise TimeParseError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
        end
        @cache1_key = @cache2_key
        @cache1_time = @cache2_time
        @cache2_key = value
        @cache2_time = time
        return time
      end

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. If timestamps may be numeric, set time_type float or unixtime (NumericTimeParser accepts String and Numeric), or time_type mixed with unixtime in the fallbacks
  2. Normalize the record before parsing: convert the field with value.to_s (or skip when nil) in a record_transformer/filter or custom parser
  3. Ensure the time_key actually exists and is a string in the incoming records (fix the producer or an upstream parser)

Example fix

# before
<parse>
  @type json
  time_key time     # producer now sends "time": 1629489132 (integer) -> TimeParseError
</parse>

# after
<parse>
  @type json
  time_key time
  time_type mixed
  time_format_fallbacks unixtime, %iso8601
</parse>
Defensive patterns

Strategy: type-guard

Validate before calling

value = record['time']
next unless value.is_a?(String)   # skip or convert non-strings before parsing

Type guard

def parsable_string?(v)
  v.is_a?(String) && !v.empty?
end

raise Fluent::TimeParseError unless parsable_string?(value)

Try / catch

begin
  time = parser.parse(value)
rescue Fluent::TimeParseError => e
  log.warn 'skipping record with non-string time', value: value.inspect, error: e.message
  next   # or re-tag to a dead-letter stream
end

Prevention

When it happens

Trigger: Calling parser.parse(1629489132) or parser.parse(nil) on a Fluent::TimeParser configured for string formats; feeding a JSON payload where the time field is a JSON number into a parser built from time_type :string (the default) — e.g. in_http with record time as epoch integer and no time_type float/unixtime/mixed setting.

Common situations: Upstream producer changes a log field from string timestamp to epoch integer; a filter mutates the time field to a number before the parser runs; nil time field when a record lacks the time_key; using a parser plugin API directly with non-string input.

Related errors


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