Fluent Bit’s tag rewriting is how you dynamically route records by changing their tag field.
Here’s Fluent Bit processing logs from two different applications, app1 and app2, and sending them to different Elasticsearch indices based on their original tags.
[SERVICE]
Flush 1
Daemon off
Log_Level info
[INPUT]
Name tail
Path /var/log/app1.log
Tag app1.log
Parser docker
[INPUT]
Name tail
Path /var/log/app2.log
Tag app2.log
Parser docker
[FILTER]
Name rewrite_tag
Match app1.*
Rule set tag ${tag}.processed
Emitter_Name rewrite_app1
[FILTER]
Name rewrite_tag
Match app2.*
Rule set tag ${tag}.processed
Emitter_Name rewrite_app2
[OUTPUT]
Name es
Match app1.log.processed
Host elasticsearch.example.com
Port 9200
Index app1-logs-${TAG_YEAR}-${TAG_MONTH}-${TAG_DAY}
Logstash_Format on
[OUTPUT]
Name es
Match app2.log.processed
Host elasticsearch.example.com
Port 9200
Index app2-logs-${TAG_YEAR}-${TAG_MONTH}-${TAG_DAY}
Logstash_Format on
This configuration does the following:
- Input
app1.log: It tails/var/log/app1.log. All records read from this file are initially taggedapp1.log. - Input
app2.log: It tails/var/log/app2.log. All records read from this file are initially taggedapp2.log. - Filter
rewrite_app1: This filter matches any record with a tag starting withapp1.. It then rewrites the tag by appending.processedto it. So,app1.logbecomesapp1.log.processed. TheEmitter_Namehelps debug by indicating which filter processed the record. - Filter
rewrite_app2: Similarly, this filter matches tags starting withapp2.and appends.processed, turningapp2.logintoapp2.log.processed. - Output
app1to Elasticsearch: This output plugin is configured to match records with the tagapp1.log.processed. It sends these logs to Elasticsearch, creating an index namedapp1-logs-YYYY-MM-DD. - Output
app2to Elasticsearch: This output plugin matches records taggedapp2.log.processedand sends them to Elasticsearch, creating an index namedapp2-logs-YYYY-MM-DD.
The core problem rewrite_tag solves is the need for conditional routing of logs based on their content or origin, without needing multiple identical input plugins or complex external logic. Instead of having separate output destinations for every minute variation of a log source, you can group them by a common characteristic and then apply specific routing rules.
Internally, Fluent Bit processes records in a pipeline. When a rewrite_tag filter is encountered, it intercepts the record. It checks the Match pattern against the record’s current tag. If it matches, it applies the Rule. The Rule can modify the tag in various ways: setting it to a static string, using Lua scripting for complex logic, or using pattern substitutions. Crucially, after a tag is rewritten, the record is re-emitted. This means it goes through the entire pipeline again, including potentially other filters and, importantly, the output plugins. The output plugins then match against the new tag.
The Emitter_Name is a powerful debugging tool. When you have multiple rewrite_tag filters, or complex routing, it adds a field to the record indicating which filter modified the tag. This can be invaluable for tracing the path of a log record through your Fluent Bit configuration.
If you set Rule set tag ${tag}.processed, and the original tag was my.app.log, the new tag will be my.app.log.processed. This is a simple string concatenation. You can also use more advanced rules. For example, to extract a field service_name from a JSON log and use it in the tag:
[FILTER]
Name rewrite_tag
Match *
Rule set tag ${service_name}.${tag}
Emitter_Name add_service_to_tag
This rule, applied to any record (*), will prepend the value of the service_name field (if it exists in the record) to the existing tag. If a record had tag production.logs and a field {"service_name": "webserver"}, the new tag becomes webserver.production.logs. This requires the service_name field to be parsed and available in the record before this filter runs, typically by a preceding parser or another filter.
The rewrite_tag filter supports Lua scripting for very complex transformations, allowing you to access and manipulate record fields, context, and even external data sources to determine the new tag. However, for most common routing scenarios, simple pattern matching and string manipulation suffice.
When using rewrite_tag with multiple output plugins, the order of Match clauses in your output plugins becomes critical. A record can match multiple output plugins if its rewritten tag satisfies their respective Match patterns. Fluent Bit processes output plugins in the order they appear in the configuration. If a record matches an earlier output plugin, it will be sent there and typically not processed by subsequent output plugins unless you configure specific behavior (like http_User-Agent for HTTP outputs, which is a separate topic). This means you often structure your output plugins from most specific Match to most general, or ensure your rewritten tags are unique enough to target the correct destination.
The rewrite_tag plugin can cause a record to be processed multiple times by subsequent filters and outputs. If you have a rewrite_tag filter that matches app.* and rewrites the tag to processed.app.*, and then another filter later in the chain that also matches app.*, that second filter will not see the original app.* tag again. It will only see tags that currently match app.* at the point it’s evaluated. The re-emission means the record re-enters the processing chain from the beginning of the filters, not from the point after the rewrite_tag filter.
The next concept you’ll run into is managing the lifecycle of rewritten tags, especially when you have multiple rewrite stages or complex dependencies between tags and output matching.