Build Terminal Dashboards for Log Analytics in Bash
AI generated
$_
#!/
Bash · Log Analytics · Terminal · Monitoring
Build Terminal Dashboards for Log Analytics in Bash
metrics live in the terminal, no Grafana required

Not every team has Grafana or an ELK stack available when they need to know fast how many errors a service is producing right now. A Bash terminal dashboard built from watch, tput, and ANSI color codes shows error rates, request rates, and log levels live, right inside the SSH session, without a browser and without additional infrastructure.

19 min read watch · tput · ANSI colors · sparklines · awk aggregation Bash 4.x · 5.x · Linux

1. When a terminal dashboard makes more sense than Grafana

A Bash terminal dashboard solves a very specific problem: you are on a server over SSH, a service shows anomalies, and you want to see within seconds how the error rate or request rate is currently developing, without first opening a Grafana dashboard, setting up a VPN tunnel, or building a Kibana query. For exactly that moment, a Bash terminal dashboard that aggregates directly from the local log files is often ready to use faster than any browser based tool.

The second use case for a Bash terminal dashboard is the environment without central monitoring: small projects, staging servers, or client systems where no ELK stack runs and none should run, because the operational overhead is disproportionate to the number of servers. A script combining tail -f, awk, and a bit of ANSI formatting delivers, in such cases, a usable substitute for a full observability setup, without a single additional process running permanently.

This article shows how a Bash terminal dashboard grows from the simplest form using watch to a full live dashboard with color coding, sparklines, and its own refresh loop, specifically for analyzing log files.

2. Basics: watch as the simplest live refresh

The simplest entry point for a Bash terminal dashboard is the watch command. watch -n 5 './metrics.sh' runs a script every five seconds and completely replaces the screen output, creating the impression of a live update. For most log analytics use cases, this simple approach is already sufficient, because the update frequency rarely needs to be below one second.

watch also offers difference highlighting with -d, which colors changed values between two runs. For a Bash terminal dashboard showing error counters, this makes it immediately visible at a glance which metric changed since the last refresh, without you having to program a delta calculation yourself.


#!/usr/bin/env bash
# metrics.sh — collect and print current error rate metrics
set -euo pipefail

readonly LOG_FILE="/var/log/app/access.log"
readonly WINDOW_MINUTES=5

cutoff="$(date -d "-${WINDOW_MINUTES} minutes" '+%Y-%m-%dT%H:%M')"

total_requests=$(awk -v cutoff="$cutoff" '$1 >= cutoff' "$LOG_FILE" | wc -l)
error_requests=$(awk -v cutoff="$cutoff" '$1 >= cutoff && $2 >= 500' "$LOG_FILE" | wc -l)

error_rate="0.0"
if (( total_requests > 0 )); then
  error_rate=$(awk -v e="$error_requests" -v t="$total_requests" 'BEGIN { printf "%.2f", (e/t)*100 }')
fi

echo "=== Log Dashboard — last ${WINDOW_MINUTES} min ==="
echo "Total requests: $total_requests"
echo "5xx errors:     $error_requests"
echo "Error rate:     ${error_rate}%"

Run with watch -n 5 -d ./metrics.sh, this already produces a working Bash terminal dashboard that refreshes every five seconds and highlights changes. The next logical step is refining the aggregation logic so it produces not just a single total, but a breakdown over time.

3. Aggregating log lines: error rate per minute with awk

A Bash terminal dashboard that only shows a single overall count wastes information. A breakdown by time window is more useful, so a viewer immediately sees whether an error spike just started or has been ongoing for ten minutes already. awk is excellent for this kind of aggregation, because it can group and count log lines in a single pass without needing an external database.

The technique is an associative array in awk that keeps a counter per minute (or per status code, per endpoint). At the end of the pass, awk iterates over all collected keys and prints a sorted table. For a Bash terminal dashboard meant to show several metrics at once, it pays off to extract this aggregation into a reusable function.


#!/usr/bin/env bash
set -euo pipefail

readonly LOG_FILE="/var/log/app/access.log"

# Aggregate error rate per minute using an associative array in awk
awk '
{
  minute = substr($1, 1, 16)   # YYYY-MM-DDTHH:MM
  total[minute]++
  if ($2 >= 500) errors[minute]++
}
END {
  for (m in total) {
    rate = (errors[m] > 0) ? (errors[m] / total[m]) * 100 : 0
    printf "%s  total=%-5d errors=%-4d rate=%.1f%%\n", m, total[m], errors[m]+0, rate
  }
}' "$LOG_FILE" | sort | tail -n 10

This aggregation pattern is the building block that turns a Bash terminal dashboard from a static number into a real time series. Combined with watch, it already produces a rolling view of the last ten minutes that updates automatically on every refresh.

4. Layout with tput: cursor positioning without flicker

The downside of watch is that it clears and redraws the entire screen on every run, which causes visible flicker on larger dashboards. For a calmer Bash terminal dashboard, tput takes more targeted control over cursor position and screen regions. With tput cup ROW COLUMN, the cursor jumps to a specific position without clearing the rest of the screen, allowing only the values that actually changed to be updated.

tput clear clears the entire screen once at startup, tput civis hides the blinking cursor during runtime, and tput cnorm restores it on exit. These three commands together with a custom refresh loop (see section seven) produce a Bash terminal dashboard that looks considerably more professional than a plain watch solution, because individual numbers are updated instead of clearing the whole screen.


#!/usr/bin/env bash
set -euo pipefail

readonly ROW_TOTAL=3
readonly ROW_ERRORS=4
readonly ROW_RATE=5

draw_static_layout() {
  tput clear
  tput cup 1 2; echo "=== Log Analytics Dashboard ==="
  tput cup "$ROW_TOTAL" 2; echo "Total requests:"
  tput cup "$ROW_ERRORS" 2; echo "5xx errors:"
  tput cup "$ROW_RATE" 2; echo "Error rate:"
}

update_value() {
  local row="$1" col="$2" value="$3"
  tput cup "$row" "$col"
  printf "%-20s" "$value"   # pad to overwrite any leftover characters
}

draw_static_layout
while true; do
  total=$(( RANDOM % 500 + 100 ))
  errors=$(( RANDOM % 20 ))
  rate=$(awk -v e="$errors" -v t="$total" 'BEGIN { printf "%.1f", (e/t)*100 }')

  update_value "$ROW_TOTAL" 20 "$total"
  update_value "$ROW_ERRORS" 20 "$errors"
  update_value "$ROW_RATE" 20 "${rate}%"

  sleep 2
done

5. ANSI colors for thresholds and status indicators

Plain numbers in a Bash terminal dashboard force the viewer to mentally check every value against a threshold. Color takes over that assessment visually and instantly recognizable. ANSI escape codes such as \033[31m for red, \033[33m for yellow, and \033[32m for green, followed by \033[0m to reset, are enough to color every value in the Bash terminal dashboard by threshold.

What matters is bundling this color logic in a function instead of scattering escape codes throughout the script. A colorize function that takes a value and two thresholds and returns the matching color makes a Bash terminal dashboard maintainable and consistent, because the threshold logic is defined in a single place.


#!/usr/bin/env bash
set -euo pipefail

readonly RED=$'\033[31m'
readonly YELLOW=$'\033[33m'
readonly GREEN=$'\033[32m'
readonly RESET=$'\033[0m'

colorize_rate() {
  local rate="$1"
  local color="$GREEN"
  awk -v r="$rate" 'BEGIN { exit !(r >= 10) }' && color="$RED"
  awk -v r="$rate" 'BEGIN { exit !(r >= 3 && r < 10) }' && color="$YELLOW"
  printf "%s%s%%%s" "$color" "$rate" "$RESET"
}

error_rate="7.4"
echo "Error rate: $(colorize_rate "$error_rate")"

This color coding, applied consistently to every metric in the Bash terminal dashboard, turns a column of numbers into an instantly readable traffic light system, without a browser or a graphics library.

6. Drawing sparklines and mini trends in the terminal

A single current value reveals nothing about the trend. A sparkline, a tiny trend line built from Unicode block characters, shows the course of the last measurements in a single line of text. For a Bash terminal dashboard, this is the most compact way to display a time series without drawing a full chart.

The technique: an array of eight Unicode block characters of increasing height (▁▂▃▄▅▆▇█) is selected by index based on the normalized value. For a series of measurements, each value is mapped to an index between zero and seven, and the corresponding characters strung together form the sparkline.


#!/usr/bin/env bash
set -euo pipefail

readonly BLOCKS=("▁" "▂" "▃" "▄" "▅" "▆" "▇" "█")

render_sparkline() {
  local values=("$@")
  local max=0
  for v in "${values[@]}"; do (( v > max )) && max=$v; done
  (( max == 0 )) && max=1

  local sparkline=""
  for v in "${values[@]}"; do
    local index=$(( v * 7 / max ))
    sparkline+="${BLOCKS[$index]}"
  done
  echo "$sparkline"
}

# Sample: error counts for the last 10 monitoring windows
error_history=(2 3 1 5 8 12 9 4 2 1)
echo "Error trend: $(render_sparkline "${error_history[@]}")"

A sparkline in a Bash terminal dashboard combines well with the aggregation pattern from section three: instead of just printing the last ten minutes as a table, the same data series is additionally rendered as a sparkline next to the metric, making the trend recognizable at a glance.

7. A custom refresh loop instead of watch for more control

Once a Bash terminal dashboard combines several data sources, needs to handle keyboard input (for example pausing with the space bar), or requires a variable refresh rate depending on system load, watch reaches its limits. A custom infinite loop with trap for clean termination and read -t for non-blocking keyboard input gives full control over the dashboard's behavior.

The cursor is hidden at startup with tput civis and reliably restored on exit via a trap on EXIT with tput cnorm, even if the script is interrupted with Ctrl+C. This matters especially for a Bash terminal dashboard, because a vanished cursor after an aborted script is a common and annoying nuisance for the user.


#!/usr/bin/env bash
set -euo pipefail

cleanup() {
  tput cnorm   # restore cursor visibility
  tput sgr0    # reset all text attributes
  echo
}
trap cleanup EXIT INT TERM

tput civis
tput clear

refresh_interval=2
paused=false

while true; do
  if [[ "$paused" == false ]]; then
    tput cup 0 0
    echo "Dashboard running — press 'p' to pause, 'q' to quit"
    # ... render metrics here ...
  fi

  # Non-blocking read: wait up to $refresh_interval seconds for a keypress
  if read -r -t "$refresh_interval" -n 1 key; then
    case "$key" in
      p|P) paused=true ;;
      r|R) paused=false ;;
      q|Q) break ;;
    esac
  fi
done

8. Keeping performance in check with growing log files

A Bash terminal dashboard that rereads the entire log file on every refresh gets noticeably slower for large files, the longer the service runs. For files in the gigabyte range, a full scan every few seconds is impractical. The solution is reading only the part of the file that has been newly added since the last run, similar to how tail -f works internally, but with custom byte offset tracking.

The byte position is saved to a state file after every run, and on the next run, dd or tail -c +OFFSET reads only the new section. For a Bash terminal dashboard that runs continuously, this incremental approach is the difference between constant response time and a slowdown that grows linearly with the size of the log file.


#!/usr/bin/env bash
set -euo pipefail

readonly LOG_FILE="/var/log/app/access.log"
readonly STATE_FILE="/tmp/dashboard-offset.state"

last_offset=0
[[ -f "$STATE_FILE" ]] && last_offset="$(cat "$STATE_FILE")"

current_size="$(stat -c%s "$LOG_FILE")"

# Log rotated (file shrank) — restart from the beginning
(( current_size < last_offset )) && last_offset=0

new_lines="$(tail -c +$((last_offset + 1)) "$LOG_FILE")"
echo "$current_size" > "$STATE_FILE"

new_error_count="$(echo "$new_lines" | awk '$2 >= 500' | wc -l)"
echo "New errors since last check: $new_error_count"

9. Terminal dashboard compared to Grafana and ELK

A Bash terminal dashboard does not compete directly with a full observability stack, but covers a different niche: fast, local diagnostics with no setup overhead. The following table compares the key differences.

Criterion Bash terminal dashboard Grafana ELK stack
Setup time Seconds Hours Days
SSH access sufficient Yes No, browser required No, browser required
Historical data, retention Not intended Fully supported Fully supported
Multiple servers at once Only one host per session Central view Central view
Operational overhead None Medium High

For fast diagnostics on a single server during an incident, a Bash terminal dashboard is practically unbeatable in terms of readiness. For long term trends across many servers, a central observability tool remains the right choice, and both approaches do not exclude each other.

Mironsoft

Shell automation, monitoring, and deployment infrastructure

Log diagnostics in seconds instead of through a browser?

We build custom terminal dashboards for your log analytics, with color coding, sparklines, and efficient processing even of large log files, ready to use in any SSH session.

Dashboard design

tput layouts with color coding and sparklines for your metrics

Efficient log processing

Incremental reading of large files, stable even across log rotation

No additional operations

Pure Bash scripts with no permanently running extra services

10. Summary

A Bash terminal dashboard for log analytics starts with the simple watch command, which repeats a script at fixed intervals. For calmer, more professional presentations, tput takes over targeted cursor positioning, so only changed values get redrawn instead of the entire screen. ANSI color codes turn bare numbers into an instantly readable traffic light system, sparklines built from Unicode block characters show trends in a single line of text.

For more demanding requirements like keyboard input or variable refresh rates, a custom infinite loop with trap and read -t replaces the rigid watch command. With growing log files, incremental reading with byte offset tracking prevents performance from degrading linearly with file size. A Bash terminal dashboard does not replace a central observability tool for long term trends across many servers, but is often the most pragmatic solution for fast diagnostics on a single system.

Terminal Dashboards for Log Analytics: the essentials at a glance

Getting started

watch -n 5 -d ./script.sh already delivers a working live dashboard with delta highlighting.

Layout without flicker

tput cup positions the cursor precisely, only changed values get rewritten.

Visualization

ANSI colors for thresholds, sparklines built from Unicode blocks for trends in one line.

Scaling

Byte offset tracking for incremental reads, so large log files don't slow things down.

11. FAQ: Terminal Dashboards for Log Analytics

1When is a terminal dashboard worth it over Grafana?
For fast SSH diagnostics without a browser or when no central monitoring exists.
2Start the simplest dashboard?
watch -n 5 ./script.sh, with -d additionally highlighting changes in color.
3Why does watch flicker?
Clears the whole screen on every run. tput cup updates individual values precisely.
4Aggregate log lines per minute?
Associative array in awk with a counter per time window, printed sorted at the end.
5Color values by threshold?
ANSI escape codes like \033[31m for red, \033[0m to reset, in a central function.
6What is a sparkline?
A trend shown as a sequence of Unicode block characters, each value normalized to an index between zero and seven.
7When a custom refresh loop instead of watch?
With keyboard input or variable refresh rate. read -t with a timeout replaces the fixed interval logic.
8Performance with large log files?
Incremental reading with a saved byte offset instead of a full rescan on every run.
9What happens during log rotation?
If the file size is smaller than the saved offset, the offset is reset to zero.
10Prevent a vanished cursor after Ctrl+C?
trap cleanup EXIT INT TERM with tput cnorm in the cleanup function reliably restores the cursor.