Why JSON‑friendly syslog matters
Syslog is the default source for almost every Linux service. When an error happens, the kernel or a daemon writes a line that looks like this:
Oct 6 12:34:56 myhost kernel: [12345.678901] EXT4-fs error (device sda1): ext4_find_entry:1073: inode #123456: comm: myapp: file name too long
Grafana loves structured data. A plain text file is hard to query, filter, or aggregate. By converting the error lines to JSON you can:
- Filter by severity (
error,warning,info) in a single query. - Index timestamps for time‑series charts.
- Join with other metrics (e.g. CPU load) without complex parsing.
- Export to other tools (Prometheus, Loki, Elastic) with minimal friction.
The real trick is keeping the pipeline lightweight so you don’t add latency to log ingestion or chew up too much disk space.
Typical syslog layout
The format is defined by the rsyslog or systemd-journald configuration. A minimal line looks like:
TIMESTAMP HOSTNAME TAG[PID]: MESSAGE
TIMESTAMP– usuallyMmm dd hh:mm:ssor ISO‑8601 ifsystemd-journaldis used.HOSTNAME– the machine name.TAG[PID]– the program name and optional PID.MESSAGE– the actual log text.
If you’re on a recent Debian/Ubuntu system, systemd-journald writes to /var/log/syslog in the same format. For reference, see the systemd documentation.
Extracting error lines
The first step is to isolate the lines that contain an error. grep is fast, but it can be noisy if the message contains the word “error” in a different context. A safer approach is to use awk to match the priority field that rsyslog injects when configured with the RSYSLOG_SyslogProtocol23Format template:
$ sudo tail -n 1000 /var/log/syslog | awk '$0 ~ /error|err|critical|fail/i'
If you’re using systemd-journald, the priority is in the PRIORITY= field. You can extract it with journalctl:
$ sudo journalctl -p err -n 1000
For the rest of this article we’ll assume the plain text file and use grep with a case‑insensitive pattern.
Building a JSON stream
jq is the de‑facto JSON processor for the shell. It can read a stream of text, split it into fields, and output JSON objects. The following one‑liner turns a syslog line into a JSON object:
grep -iE 'error|critical|fail' /var/log/syslog |
awk '{
# split the line into timestamp, host, tag, and message
match($0, /^([A-Z][a-z]{2} +[0-9]{1,2} +[0-9]{2}:[0-9]{2}:[0-9]{2}) ([^ ]+) ([^:]+): (.*)$/, m)
if (m[1]) {
# convert to ISO‑8601 for Grafana
cmd = "date -d \"" m[1] " " strftime(\"%Y\") "\" +%Y-%m-%dT%H:%M:%S"
cmd | getline iso
close(cmd)
printf "{\"timestamp\":\"%s\",\"host\":\"%s\",\"tag\":\"%s\",\"msg\":\"%s\"}\n",
iso, m[2], m[3], m[4]
}
}' | jq -c .
- The
awkpart parses the line with a regular expression. dateconverts the month/day/time to ISO‑8601; we append the current year because syslog omits it.jq -c .prints each object on its own line, ready for ingestion.
You can pipe the output to a file:
./syslog_to_json.sh > /var/log/syslog_errors.json
or stream it directly to Grafana’s JSON data source.
Feeding Grafana
Grafana’s JSON API data source (available as a plugin) expects a JSON array or a newline‑delimited stream. The simplest setup:
Install the plugin (Grafana 10+):
grafana-cli plugins install grafana-json-datasource systemctl restart grafana-serverCreate a data source in Grafana pointing to the file or a local HTTP endpoint that serves the stream. If you prefer a file, use the File data source type:
- URL:
file:///var/log/syslog_errors.json - Format:
JSON Lines
- URL:
Build a panel that queries the field
msgand groups bytag. Example query:{ "query": "SELECT msg, tag FROM \"syslog_errors\" GROUP BY tag" }Add a time filter using the
timestampfield to plot error counts over time.
If you want real‑time updates, run the conversion script as a systemd service that tails /var/log/syslog and writes to a named pipe:
[Unit]
Description=Stream syslog errors to JSON for Grafana
After=network.target
[Service]
ExecStart=/usr/local/bin/syslog_to_json.sh
StandardOutput=append:/var/run/syslog_errors.json
Restart=on-failure
[Install]
WantedBy=multi-user.target
Then point Grafana to /var/run/syslog_errors.json. The pipe ensures the file is always fresh.
Performance and rotation
Disk usage: JSON objects are larger than plain text. For a busy server, a 10 GB syslog file can become 20 GB of JSON. Mitigate by:
Compressing the output (
gzip -c > errors.json.gz) and using Grafana’s Compressed JSON support.Rotating the JSON file with
logrotate:/var/log/syslog_errors.json { daily rotate 7 compress missingok create 0640 root adm }
CPU load: awk + date + jq is lightweight for a few thousand lines per second. If you hit a bottleneck, consider:
- Using
systemd-journald’s native JSON export (journalctl -o json). - Writing a small Go or Rust program that parses syslog more efficiently.
Memory: jq -c . streams objects, so it never buffers the whole file. The main memory hog is the awk date
See also
- How to pull a single file from a Borg backup without unpacking the entire archive
- Why ssh keeps asking for a password after adding a key and how to correct the PubkeyAuthentication setting
- When systemd‑resolved overrides /etc/hosts: a quick fix
- Fixing “Host key verification failed” After Renaming a Jump Host Server
- How to Stop a systemd Timer from Failing After a Reboot Due to an Unset $USER Variable