Fluent Bit is a super fast, lightweight, and highly scalable logging and metrics processor and forwarder.
In this article we’ll see how we can configure FluentBit to send metrics and logs into Hydrolix and how to visualise those into Grafana.
Table of Contents
Deploying and configuring Fluent Bit
In this example I’ll setup FluentBit into AWS Linux EC2 machine. To do so I have followed this guide.
Installing on Linux is straightforward:
1 |
curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh |
Once FluentBit is installed the configuration is pretty basic, I have enabled a couple of [INPUTS]
The FluentBit configuration file is on /etc/fluent-bit/fluent-bit.conf
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
[INPUT] name cpu tag cpu interval_sec 1 [INPUT] name mem tag mem interval_sec 1 [INPUT] name netif tag netif interval_sec 1 interface ens5 [INPUT] name disk tag disk interval_sec 1 [INPUT] name systemd tag host.* [FILTER] Name nest Match mem Operation nest Wildcard Mem.* Nest_under memstats Remove_prefix Mem. [FILTER] Name nest Match mem Operation nest Wildcard Swap.* Nest_under swapstats Remove_prefix Swap. [FILTER] Name nest Match cpu Operation nest Wildcard * Nest_under cpu [FILTER] Name nest Match netif Operation nest Wildcard * Nest_under network [FILTER] Name nest Match disk Operation nest Wildcard * Nest_under disk [FILTER] Name aws Match * imds_version v1 az true ec2_instance_id true ec2_instance_type true private_ip true ami_id true account_id true hostname true vpc_id true [OUTPUT] name http match * host hostname.hydrolix.net port 443 URI /ingest/event?table=demo.fluentbit&transform=fluentbit Format json_lines json_date_key timestamp json_date_format iso8601 tls on compress gzip |
This configuration allows to retrieve cpu, memory, network, disk usage, systemd logs and AWS metadata information.
To keep the data cleaner I’m using the filter nest which allows the data to be nested and modified into a JSON blob.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
{ "MESSAGE": null, "PRIORITY": null, "SYSLOG_FACILITY": null, "SYSLOG_IDENTIFIER": null, "_BOOT_ID": null, "_CAP_EFFECTIVE": null, "_CMDLINE": null, "_COMM": null, "_EXE": null, "_GID": null, "_SYSTEMD_CGROUP": null, "_SYSTEMD_UNIT": null, "_UID": null, "account_id": "209166775408", "ami_id": "ami-08e6b682a466887dd", "az": "us-east-2c", "cpu": {"cpu0.p_cpu":1,"cpu0.p_system":0,"cpu0.p_user":1,"cpu1.p_cpu":7,"cpu1.p_system":1,"cpu1.p_user":6,"cpu2.p_cpu":5,"cpu2.p_system":0,"cpu2.p_user":5,"cpu3.p_cpu":8,"cpu3.p_system":0,"cpu3.p_user":8,"cpu4.p_cpu":10,"cpu4.p_system":2,"cpu4.p_user":8,"cpu5.p_cpu":5,"cpu5.p_system":1,"cpu5.p_user":4,"cpu6.p_cpu":10,"cpu6.p_system":1,"cpu6.p_user":9,"cpu7.p_cpu":5,"cpu7.p_system":1,"cpu7.p_user":4,"cpu_p":6,"system_p":0.75,"user_p":5.25}, "custom": {}, "disk": {}, "ec2_instance_id": "i-0df7c9352e02f8c19", "ec2_instance_type": "t4g.2xlarge", "hostname": "ip-172-31-34-182.us-east-2.compute.internal", "memstats": {}, "network": {}, "private_ip": "172.31.34.182", "swapstats": {}, "test": null, "timestamp": "2022-09-23 12:29:59.083", "vpc_id": "vpc-db549db0" }, { "MESSAGE": null, "PRIORITY": null, "SYSLOG_FACILITY": null, "SYSLOG_IDENTIFIER": null, "_BOOT_ID": null, "_CAP_EFFECTIVE": null, "_CMDLINE": null, "_COMM": null, "_EXE": null, "_GID": null, "_SYSTEMD_CGROUP": null, "_SYSTEMD_UNIT": null, "_UID": null, "account_id": "209166775408", "ami_id": "ami-08e6b682a466887dd", "az": "us-east-2c", "cpu": {}, "custom": {}, "disk": {"read_size":0,"write_size":0}, "ec2_instance_id": "i-0df7c9352e02f8c19", "ec2_instance_type": "t4g.2xlarge", "hostname": "ip-172-31-34-182.us-east-2.compute.internal", "memstats": {}, "network": {}, "private_ip": "172.31.34.182", "swapstats": {}, "test": null, "timestamp": "2022-09-23 12:29:59.083", "vpc_id": "vpc-db549db0" }, { "MESSAGE": null, "PRIORITY": null, "SYSLOG_FACILITY": null, "SYSLOG_IDENTIFIER": null, "_BOOT_ID": null, "_CAP_EFFECTIVE": null, "_CMDLINE": null, "_COMM": null, "_EXE": null, "_GID": null, "_SYSTEMD_CGROUP": null, "_SYSTEMD_UNIT": null, "_UID": null, "account_id": "209166775408", "ami_id": "ami-08e6b682a466887dd", "az": "us-east-2c", "cpu": {}, "custom": {}, "disk": {}, "ec2_instance_id": "i-0df7c9352e02f8c19", "ec2_instance_type": "t4g.2xlarge", "hostname": "ip-172-31-34-182.us-east-2.compute.internal", "memstats": {}, "network": {"ens5.rx.bytes":914588,"ens5.rx.errors":0,"ens5.rx.packets":751,"ens5.tx.bytes":45974,"ens5.tx.errors":0,"ens5.tx.packets":504}, "private_ip": "172.31.34.182", "swapstats": {}, "test": null, "timestamp": "2022-09-23 12:29:59.083", "vpc_id": "vpc-db549db0" }, { "MESSAGE": "12:29:58.759 [warn] Description: 'Authenticity is not established by certificate path validation'", "PRIORITY": 6, "SYSLOG_FACILITY": 3, "SYSLOG_IDENTIFIER": "mix", "_BOOT_ID": "5cd29b77348f490987162e1d02d88bd9", "_CAP_EFFECTIVE": "0", "_CMDLINE": "/usr/lib/erlang/erts-12.0.3/bin/beam.smp -- -root /usr/lib/erlang -progname erl -- -home /home/ubuntu -- -pa /usr/lib/elixir/bin/../lib/eex/ebin /usr/lib/elixir/bin/../lib/elixir/ebin /usr/lib/elixir/bin/../lib/ex_unit/ebin /usr/lib/elixir/bin/../lib/iex/ebin /usr/lib/elixir/bin/../lib/logger/ebin /usr/lib/elixir/bin/../lib/mix/ebin -noshell -s elixir start_cli -extra /usr/bin/mix run --no-halt", "_COMM": "beam.smp", "_EXE": "/usr/lib/erlang/erts-12.0.3/bin/beam.smp", "_GID": 1000, "_SYSTEMD_CGROUP": "/system.slice/ctspull.service", "_SYSTEMD_UNIT": "ctspull.service", "_UID": 1000, "account_id": "209166775408", "ami_id": "ami-08e6b682a466887dd", "az": "us-east-2c", "cpu": {}, "custom": {}, "disk": {}, "ec2_instance_id": "i-0df7c9352e02f8c19", "ec2_instance_type": "t4g.2xlarge", "hostname": "ip-172-31-34-182.us-east-2.compute.internal", "memstats": {}, "network": {}, "private_ip": "172.31.34.182", "swapstats": {}, "test": null, "timestamp": "2022-09-23 12:29:58.760", "vpc_id": "vpc-db549db0" } |
We can index those leveraging the following transform:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
{ "is_default": true, "output_columns": [ { "name": "timestamp", "datatype": { "type": "datetime", "index": false, "primary": true, "format": "2006-01-02T15:04:05.999999Z", "resolution": "ms" } }, { "name": "account_id", "datatype": { "type": "string" } }, { "name": "MESSAGE", "datatype": { "type": "string", "index": true, "index_options": { "fulltext": true } } }, { "name": "PRIORITY", "datatype": { "type": "uint8" } }, { "name": "SYSLOG_FACILITY", "datatype": { "type": "uint8" } }, { "name": "SYSLOG_IDENTIFIER", "datatype": { "type": "string" } }, { "name": "_BOOT_ID", "datatype": { "type": "string" } }, { "name": "_CAP_EFFECTIVE", "datatype": { "type": "string" } }, { "name": "_CMDLINE", "datatype": { "type": "string" } }, { "name": "_COMM", "datatype": { "type": "string" } }, { "name": "_EXE", "datatype": { "type": "string" } }, { "name": "_GID", "datatype": { "type": "uint32" } }, { "name": "_SYSTEMD_CGROUP", "datatype": { "type": "string" } }, { "name": "_SYSTEMD_UNIT", "datatype": { "type": "string" } }, { "name": "_UID", "datatype": { "type": "uint32" } }, { "name": "ami_id", "datatype": { "type": "string" } }, { "name": "az", "datatype": { "type": "string" } }, { "name": "ec2_instance_id", "datatype": { "type": "string" } }, { "name": "ec2_instance_type", "datatype": { "type": "string" } }, { "name": "hostname", "datatype": { "type": "string" } }, { "name": "private_ip", "datatype": { "type": "string" } }, { "name": "vpc_id", "datatype": { "type": "string" } }, { "name": "memstats", "datatype": { "type": "map", "elements": [ { "type": "string" }, { "type": "uint32" } ] } }, { "name": "swapstats", "datatype": { "type": "map", "elements": [ { "type": "string" }, { "type": "uint32" } ] } }, { "name": "cpu", "datatype": { "type": "map", "elements": [ { "type": "string" }, { "type": "double" } ] } }, { "name": "network", "datatype": { "type": "map", "elements": [ { "type": "string" }, { "type": "double" } ] } }, { "name": "disk", "datatype": { "type": "map", "elements": [ { "type": "string" }, { "type": "double" } ] } } ], "compression": "none", "format_details": { "flattening": { "active": false } } } |
An important feature to note is that we are going to use Hydrolix’s new full text search capability.
This capability is applied to the MESSAGE json and allows the indexing of words within message.
The addition of this feature allows us to search for specific occurances of a word at query time without doing full column scans and ensuring we only get rows (or blocks) that include the keyword(s).
By using this feature for the MESSAGE column, it allows for faster query response times, better CPU utilisation and reduced bandwidth consumption.
By default our full text search create a separate index and we are splitting the column leveraging the following separator:
1 |
[ ] < > ( ) { } | ! ; , ' " * \n \r \s \t & ? + / : = @ . - $ # % \ _ |
So a SQL query like the following will look for *error* in the log message:
1 |
MESSAGE LIKE '%error%' |
Hydrolix leverages its own compression algorithm, even with fulltext search (and the additional string dictionaries in the indices that may involve), the system has excellent compression ratios:
- “total_rows”: “266.55 million”
- “raw_data_size”: “68.15 GiB” -> size of the data sent by fluentbit
- “hdx_data_size”: “1.88 GiB” -> HDX data format
- “hdx_index_size”: “570.80 MiB” -> HDX index data
- “compression_ratio”: 27.9

Grafana Visualisation
After deploying FluentBit into your infrastructure you can use Grafana for data visualisation and alerting.

In this dashboard we are using the Clickhouse plugin for Grafana which allows us to write SQL statements and get their results. For more information on how to set-up Hydrolix with Grafana please have a look here
To create the above dashboard we create a new Dashboard within Grafana and configure the following 3 variables::
- List of EC2 instance ID
- List of network interface for the EC2 instance selected
- And finally a text box to look for text pattern in the logs.

This SQL query uses a built-in filter to limit the execution of the statement to the time range of the dashboard.
For example if your dashboard is set to the last 1h, and your time column is called timestamp the variable $__timeFilter(timestamp)
will be replaced with:WHERE timestamp >= '1664800456' AND timestamp <= '1664804056'

The second filter is to select the network interface, technically the network interface is a map(network_interface, value) so here we are retrieve all the network interface keys.
We use another built-in function to optimize any calls to the Database limiting columns to scan when ALL is selected – so:AND $__conditionalAll(ec2_instance_id IN ( ${ec2_instance_id:singlequote} ), $ec2_instance_id)
Means that if my variable $ec2_instance_id
is ‘all’ this filter will be replaced by AND 1=1
.
If it has a value selected by the user then the query predicate will look likeec2_instance_id IN ('$ec2_instance_id')
For example to use these settings in a query that retrieves the avg cpu usage of every host, we would specificy it as:

Here in this query is getting the avg cpu usage of every host unless the user drill-down to specific EC2 instance.
And finally we need to create the fulltest search for the MESSAGE column:

The SQL Statement above retrieves all the MESSAGE data. We use If
Clickhouse function to optimise the search and don’t apply the predicate if the default “all” string is supplied.
AND if('${log:text}' = 'all', true, MESSAGE LIKE '%${log:text}%')
This means that if the variable log
contains the chain of characters that is equal to ‘all’ we do not apply the rest of the statement, i.e. MESSAGE LIKE '%${log:text}%'
is ignored.
This is so we don’t search the MESSAGE for ‘all’.
However, if the value is something other than ‘all’ we apply the predicate MESSAGE LIKE ‘the text in the box’.
For example, if we want to search for 'error'

This provides us the output of a fulltext search looking for ‘error’ in the message field.
By using Hydrolix’s fulltextsearch capabilities these kinds data interactions (e.g. debugging or analysis of log data) become significantly faster and more efficient than they have previously.