My lab has a few Docker containers as follows:
name | Docker image |
---|---|
Fluentd | fluent/fluentd:v1.16-1 |
Fluent-bit | cr.fluentbit.io/fluent/fluent-bit |
Loki | grafana/loki |
Grafana | grafana/grafana-enterprise |
Caddy | caddy:builder |
My goal is to collect Caddy logs and visualize them in Grafana.
Scenario: Fluent-bit tails the logs and sends them to Fluentd. Then Fluentd pushes the logs to Loki. My aim is to use Fluentd as the central log collector.
The problem is the parsing of those logs Grafana-side.
The Caddy logs are in (nested) JSON format. Sample:
{"level":"info","ts":1712949034.535184,"logger":"http.log.access.log1","msg":"handled request","request":{"remote_ip":"172.18.0.1","remote_port":"39664","client_ip":"172.18.0.1","proto":"HTTP/1.1","method":"POST","host":"grafana.darknet.com","uri":"/api/short-urls","headers":{"Content-Length":["580"],"Origin":["http://grafana.darknet.com"],"Content-Type":["application/json"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0"],"Accept":["application/json, text/plain, */*"],"X-Grafana-Org-Id":["1"],"Connection":["keep-alive"],"Accept-Language":["en-US,en;q=0.5"],"Accept-Encoding":["gzip, deflate"],"Referer":["http://grafana.darknet.com/explore?schemaVersion=1&panes=%7B%22Efb%22:%7B%22datasource%22:%22f779c221-7bd2-468d-9f9c-96e069b869f8%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%7Bjob%3D%5C%22caddy.log.loki%5C%22%7D%20%7C%20json%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22f779c221-7bd2-468d-9f9c-96e069b869f8%22%7D,%22editorMode%22:%22code%22%7D%5D,%22range%22:%7B%22from%22:%22now-1m%22,%22to%22:%22now%22%7D%7D%7D&orgId=1"],"X-Grafana-Device-Id":["f343e938e74b3a57997faff69d24de8a"],"Cookie":[]}},"bytes_read":580,"user_id":"","duration":0.011267887,"size":72,"status":200,"resp_headers":{"X-Xss-Protection":["1; mode=block"],"Date":["Fri, 12 Apr 2024 19:10:34 GMT"],"Content-Length":["72"],"Server":["Caddy"],"Cache-Control":["no-store"],"Content-Type":["application/json"],"X-Content-Type-Options":["nosniff"],"X-Frame-Options":["deny"]}}
I have tried two different configurations so far:
Have Fluent-bit send the logs to Fluentd, then Fluentd forwards the logs to Loki (tagged as
caddy.log
)
Schema:Cady --> Fluent-bit --> Fluentd --> Loki
Have Fluent-bit send the logs straight to Loki (tagged as
caddy.log.loki
)
Schema:Cady --> Fluent-bit --> Loki
Here I have the following Fluent-bit config to send logs to both Loki and Fluentd at the same time, with different tags:
[INPUT]
Name tail
Path /var/log/caddy/*.log
Parser json
Tag caddy.log
Path_Key log_filename
# send logs to Fluentd
[OUTPUT]
Name forward
Host fluentd
Port 24224
Match caddy.*
# send logs straight to Loki
[OUTPUT]
name loki
match caddy.*
host loki
port 3100
labels job=caddy.log.loki
Fluentd config:
<source>
@type forward
</source>
<match caddy.*>
@type loki
url "http://loki:3100"
extra_labels {"job": "caddy.log"}
<buffer>
flush_interval 5s
flush_at_shutdown true
</buffer>
</match>
Then in Grafana I can browse the logs and I have the two labels available in the Explore window.
If I choose the tag caddy.log.loki
the logs are displayed in plain JSON as shown below. With this expression I can parse them: {job="caddy.log.loki"} | json
. Some of the nested JSON is extracted eg: request_client_ip
but not all of it, for example request.headers
is missing but I can live with that.
If I choose the tag caddy.log
then the logs are displayed in a "mixed" format:
It appears that some transformation took place but I am not sure where.
I can use logfmt
to parse the lines. But I am still left with some unparsed fields (request
, resp_headers
) as shown below:
Questions:
- why is that the logs are not rendered in plain JSON anymore if I add the Fluentd step?
- what would be the best way to ship and parse nested JSON logs in Loki/Grafana with Fluentd?