fluent/fluentd · error · Fluent::ConfigError
secondary plugin '#{self.class}' must support buffering, but
Error message
secondary plugin '#{self.class}' must support buffering, but doesn't. What it means
Fluent::Plugin::Output#configure raises this Fluent::ConfigError when a plugin used inside `<secondary>` supports only synchronous processing (implements #process but neither #write nor #try_write). A secondary plugin receives already-buffered chunks from the failed primary and must flush them itself via #write, so a synchronous-only plugin cannot fulfill the role — unlike error 198, this fires even without a <buffer> section in the secondary itself, because secondaries always operate in buffered mode without their own buffer instance.
Source
Thrown at lib/fluent/plugin/output.rb:277
@emit_records_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "emit_records", help_text: "Number of emit records")
@emit_size_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "emit_size", help_text: "Total size of emit events")
@write_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "write_count", help_text: "Number of writing events")
@write_secondary_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "write_secondary_count", help_text: "Number of writing events in secondary")
@rollback_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "rollback_count", help_text: "Number of rollbacking operations")
@flush_time_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "flush_time_count", help_text: "Count of flush time")
@slow_flush_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "slow_flush_count", help_text: "Count of slow flush occurred time(s)")
@drop_oldest_chunk_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "drop_oldest_chunk_count", help_text: "Number of count that old chunk were discarded with drop_oldest_chunk")
if has_buffer_section
unless implement?(:buffered) || implement?(:delayed_commit)
raise Fluent::ConfigError, "<buffer> section is configured, but plugin '#{self.class}' doesn't support buffering"
end
@buffering = true
else # no buffer sections
if implement?(:synchronous)
if !implement?(:buffered) && !implement?(:delayed_commit)
if @as_secondary
raise Fluent::ConfigError, "secondary plugin '#{self.class}' must support buffering, but doesn't."
end
@buffering = false
else
if @as_secondary
# secondary plugin always works as buffered plugin without buffer instance
@buffering = true
else
# @buffering.nil? shows that enabling buffering or not will be decided in lazy way in #start
@buffering = nil
end
end
else # buffered or delayed_commit is supported by `unless` of first line in this method
@buffering = true
end
end
# Enable to update record size metrics or not
@enable_size_metrics = !!system_config.enable_size_metrics
View on GitHub (pinned to dd45c6e18d)
Solutions
- Use a buffering-capable plugin in <secondary>: @type file, @type secondary_file (the purpose-built one), or a network output implementing #write
- To fan out failed chunks, make the primary's normal path handle fan-out (copy in the <match>) and keep the secondary simple
- Plugin authors: implement #write(chunk) so the plugin qualifies as buffered
Example fix
# before <secondary> @type copy # copy is process-only; secondary must implement write <store> @type stdout </store> </secondary> # after <secondary> @type file path /var/log/fluent/failed </secondary>
Defensive patterns
Strategy: type-guard
Validate before calling
# Validate secondary candidates before deploying config
secondary_type = secondary_elem['@type']
plugin = Fluent::Plugin.new_output(secondary_type)
unless plugin.implement?(:buffered) || plugin.implement?(:delayed_commit)
raise "#{secondary_type} cannot be used in <secondary>: it does not support buffering"
end Type guard
# Type guard for secondary eligibility (Ruby)
def secondary_capable?(output_type)
p = Fluent::Plugin.new_output(output_type)
p.implement?(:buffered) || p.implement?(:delayed_commit)
end
secondary_capable?('file') # => true
secondary_capable?('secondary_file') # => true
secondary_capable?('copy') # => false (process-only) Try / catch
# In config tests:
begin
driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::ForwardOutput)
driver.configure(fluent_conf_with_secondary)
rescue Fluent::ConfigError => e
fail "secondary rejected: #{e.message}"
end Prevention
- Reserve <secondary> for buffered-capable outputs (file, secondary_file, forward, http...); never copy
- Smoke-test full <match> blocks including <secondary> with `fluentd --dry-run` in CI
- Plugin authors: implementing #write(chunk) makes your plugin secondary-eligible; verify with an as_secondary configure test
When it happens
Trigger: `<secondary> @type copy ... </secondary>` (out_copy defines only #process — the canonical trigger), or any process-only custom plugin placed in <secondary>. Note @type file, secondary_file, stdout, null are buffered-capable and fine.
Common situations: Trying to fan out failed chunks to multiple destinations with copy inside <secondary>; promoting a custom sync plugin to secondary duty during incident response; misreading the secondary docs' plugin list.
Related errors
- <buffer> section is configured, but plugin '#{self.class}' d
- secondary plugin '#{self.class}' must support buffering, but
- Invalid <secondary> section for non-buffered plugin
- <secondary> section cannot have <buffer> section
- buffer path is not configured. specify 'path' in <buffer>
AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21).
Data as JSON: /api/errors/d9d4e5182a7a334e.
Report an issue: GitHub.