fluent/fluentd · error · ArgumentError

key must be a string (or symbol for to_s)

Error message

key must be a string (or symbol for to_s)

What it means

Fluent::Plugin::Storage.validate_key (lib/fluent/plugin/storage.rb:35) raises ArgumentError unless the key is a String or Symbol (it returns key.to_s on success). It is the contract helper that storage plugin implementations (and code using plugin storage, e.g. storage_local or counter plugins) should apply before touching the backing store, keeping the on-disk/hash namespace string-keyed. Integer keys, nil, or arbitrary objects fail fast here rather than corrupting storage.

Source

Thrown at lib/fluent/plugin/storage.rb:35

require 'fluent/plugin/base'
require 'fluent/plugin/owned_by_mixin'

module Fluent
  module Plugin
    class Storage < Base
      include OwnedByMixin

      DEFAULT_TYPE = 'local'

      configured_in :storage

      config_param :persistent,        :bool, default: false # load/save with all operations
      config_param :autosave,          :bool, default: true
      config_param :autosave_interval, :time, default: 10
      config_param :save_at_shutdown,  :bool, default: true

      def self.validate_key(key)
        raise ArgumentError, "key must be a string (or symbol for to_s)" unless key.is_a?(String) || key.is_a?(Symbol)
        key.to_s
      end

      attr_accessor :log

      def persistent_always?
        false
      end

      def synchronized?
        false
      end

      def implementation
        self
      end

      def load

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Convert keys at the boundary: storage.put(key.to_s, value) or use String keys from the start.
  2. Guard nil: key = something || 'default' before use.
  3. In your storage plugin subclass, keep calling validate_key — it enforces the contract — but normalize callers upstream.
  4. For numeric identifiers, use explicit string formatting ('status_200').

Example fix

# before
@storage.put(record['status'], 1)   # Integer key -> ArgumentError
# after
@storage.put("status_#{record['status']}", 1)
Defensive patterns

Strategy: type-guard

Validate before calling

key = key.to_s if key.is_a?(Symbol)
key = "key_#{key}" unless key.is_a?(String)
@storage.put(key, value)

Type guard

def valid_storage_key?(k)
  k.is_a?(String) || k.is_a?(Symbol)
end

Try / catch

rescue ArgumentError => e
  raise unless e.message =~ /key must be a string/
  retry with key.to_s

Prevention

When it happens

Trigger: A custom storage plugin calling Storage.validate_key(key) in get/put/fetch and receiving 1, nil, or an object from record data; passing a JSON-parsed integer (e.g. response codes) as a key; nil keys from Hash#[] misses used unchecked.

Common situations: Custom storage plugin development; using plugin storage to count per-status events with integer status codes as keys; interpolating nil when a lookup misses; porting v0-era code that assumed auto-conversion.

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/cba199ec48929b24. Report an issue: GitHub.