fluent/fluentd · error · Fluent::ConfigError

out_secondary_file: basename or directory has an incompatibl

Error message

out_secondary_file: basename or directory has an incompatible placeholder #{ph}, remove tag placeholder, like `${tag}`, from basename or directory

What it means

SecondaryFileOutput#configure found a ${tag} or ${tag[n]} placeholder in directory/basename (matched by PLACEHOLDER_REGEX) but the primary buffer has no `tag` chunk key (@chunk_key_tag false), so the tag value is unavailable when the secondary generates its file path — Fluent::ConfigError at configure time. Placeholders in the secondary must be a subset of what the primary buffer keys can supply.

Source

Thrown at lib/fluent/plugin/out_secondary_file.rb:102

            gz = Zlib::GzipWriter.new(f)
            chunk.write_to(gz)
            gz.close
          }
        end
      end
    end

    private

    def validate_compatible_with_primary_buffer!(path_without_suffix)
      placeholders = path_without_suffix.scan(PLACEHOLDER_REGEX).flat_map(&:first) # to trim suffix [\d+]

      if !@chunk_key_time && has_time_format?(path_without_suffix)
        raise Fluent::ConfigError, "out_secondary_file: basename or directory has an incompatible placeholder, remove time formats, like `%Y%m%d`, from basename or directory"
      end

      if !@chunk_key_tag && (ph = placeholders.find { |placeholder| placeholder.match?(/tag(\[\d+\])?/) })
        raise Fluent::ConfigError, "out_secondary_file: basename or directory has an incompatible placeholder #{ph}, remove tag placeholder, like `${tag}`, from basename or directory"
      end

      vars = placeholders.reject { |placeholder| placeholder.match?(/tag(\[\d+\])?/) || (placeholder == 'chunk_id') }

      if ph = vars.find { |v| !@chunk_keys.include?(v) }
        raise Fluent::ConfigError, "out_secondary_file: basename or directory has an incompatible placeholder #{ph}, remove variable placeholder, like `${varname}`, from basename or directory"
      end
    end

    def has_time_format?(str)
      str != Time.now.strftime(str)
    end

    def generate_path(path_without_suffix)
      if @append
        path = "#{path_without_suffix}#{@suffix}"
        synchronize_path(path) do
          yield path

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Add `tag` to the primary buffer keys: `<buffer tag>` or `<buffer time, tag>`
  2. Or remove the ${tag} placeholder from directory/basename (use ${chunk_id}, which is always allowed)
  3. Note ${tag[0]} etc. also require the tag key, not just a slice

Example fix

# before
<match **>
  @type forward
  <buffer time>
    timekey 1h
  </buffer>
  <secondary>
    @type secondary_file
    directory /var/log/dump/${tag}
  </secondary>
</match>

# after
<match **>
  @type forward
  <buffer time, tag>
    timekey 1h
  </buffer>
  <secondary>
    @type secondary_file
    directory /var/log/dump/${tag}
  </secondary>
</match>
Defensive patterns

Strategy: validation

Validate before calling

# Check: ${tag} placeholders require a tag chunk key
buffer = match_element.elements.find { |e| e.name == 'buffer' }
keys = buffer ? buffer.arg.to_s.split(',').map(&:strip) : []
path = File.join(directory, basename || 'dump.bin')
has_tag_ph = path.match?(/\$\{tag(\[\d+\])?}/)
raise '${tag} placeholder needs <buffer tag> in primary' if has_tag_ph && !keys.include?('tag')

Prevention

When it happens

Trigger: Primary `<buffer>` (empty, keyed only on time or variables) with secondary_file `directory /var/log/dump/${tag}` or `basename ${tag}.bin`.

Common situations: Adding tag-based file names for debugging without updating the primary's buffer keys; primary using `<buffer time>` only (common for aggregation setups where tag was dropped for chunk sharing).

Related errors


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