fluent/fluentd · error · ArgumentError
tag must be a String: #{tag.class}
Error message
tag must be a String: #{tag.class} What it means
Output#metadata (lib/fluent/plugin/output.rb:938) is the factory for Fluent::Plugin::Metadata objects used as buffer-chunk keys. It validates its arguments and raises ArgumentError unless tag is nil or a String. Plugin authors and test code call metadata directly; core fluentd normally passes the tag string emitted by inputs/routers, so seeing this error means a non-String tag object reached a buffered output's chunking layer.
Source
Thrown at lib/fluent/plugin/output.rb:938
@emit_count_metrics.inc
begin
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_tagView on GitHub (pinned to dd45c6e18d)
Solutions
- Pass a real Fluentd tag String like 'app.logs' (dot-separated) or nil.
- Check argument order against metadata(tag, time, record) — note it differs from Metadata.new(timekey, tag, variables).
- In tests, provide a string tag when emitting events through the plugin test driver instead of nil/symbol shortcuts.
Example fix
# before
meta = output.metadata(record, time, nil) # record/Hash in tag slot -> ArgumentError
# after
meta = output.metadata('app.logs', time, record) Defensive patterns
Strategy: type-guard
Validate before calling
tag = tag.to_s unless tag.is_a?(String) || tag.nil? meta = output.metadata(tag, time, record)
Type guard
def valid_fluent_tag?(t) t.is_a?(String) || t.nil? end
Try / catch
rescue ArgumentError => e raise if e.message !~ /tag must be a String/ log.warn 'dropping event with malformed tag', tag_class: tag.class
Prevention
- Remember metadata(tag, time, record) argument order — it differs from Metadata.new(timekey, tag, variables).
- Always route events through router.emit(tag, time, record) which enforces types upstream.
- In plugin tests, pass real string tags.
When it happens
Trigger: A custom output/filter plugin calling router/output.metadata(:event, time, record) or metadata(Object.new, ...); passing an Integer/Array tag; calling metadata with argument order swapped (e.g. record first) so a Hash lands in the tag slot.
Common situations: Writing a custom buffered output plugin or plugin tests that construct Metadata manually; refactoring a filter so a variable holding nil-or-symbol is passed as tag; copy-pasting code that assumed the Metadata.new(timekey, tag, variables) argument order instead of the metadata(tag, time, record) order.
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
- <buffer> section is configured, but plugin '#{self.class}' d
- secondary plugin '#{self.class}' must support buffering, but
- Invalid path component detected in #{matched}: #{replace}
- Invalid path component detected, replaced to: #{rvalue}
- time must be a Fluent::EventTime (or Integer): #{time.class}
AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21).
Data as JSON: /api/errors/5b5e1066bdef6556.
Report an issue: GitHub.