Skip to content

Instantly share code, notes, and snippets.

@xram64
Last active August 7, 2025 19:20
Show Gist options
  • Select an option

  • Save xram64/97435ddac2c1418545ee6e34c3391a3a to your computer and use it in GitHub Desktop.

Select an option

Save xram64/97435ddac2c1418545ee6e34c3391a3a to your computer and use it in GitHub Desktop.
Nginx live formatted access log viewer
#!/bin/bash
## Nginx live formatted access log viewer
## xram | 1/30/24 | v3 (8/7/25)
# Notes:
# - The regex below must match the log format defined in `/etc/nginx/nginx.conf`.
# - `gawk` version of `awk` must be installed for `match` statement syntax to work.
# TODO:
# - Create options for printing unused log values (currently `bytes_sent`, `request_length`, and `http_referer`).
# Nginx log format:
# ```
# log_format main '$remote_addr - [$time_local] '
# '<$bytes_sent:$request_length> $status '
# '($host) "$request" | $http_referer | '
# '"$http_user_agent"';
# ```
# ============================================================================ #
# Nginx log file
LOG_FILE="/var/log/nginx/access.log"
# Parse options in args using getopts
OPT_USER_AGENT=false
n_lines=0
# Get system timezone code to pass into `gawk`
t_tz=$(date +"%Z")
HELP_TEXT='Prints aligned Nginx `access.log` output based on a custom log format.\n'
HELP_TEXT+='Options:\n'
HELP_TEXT+=' [-n LINES] Number of initial lines to print from tail of log. (Default: 0)\n'
HELP_TEXT+=' [-u] Print a user agent line with each request.\n'
HELP_TEXT+=' [-h] Show help text.\n'
while getopts 'n:uh' opt; do
case $opt in
n) n_lines=$OPTARG ;;
u) OPT_USER_AGENT=true ;;
h) echo -e "$HELP_TEXT"; exit 2 ;;
esac
done
# Follow the log file and pipe it to `gawk` for processing
tail -f -n$n_lines "$LOG_FILE" | awk -v OPT_USER_AGENT="$OPT_USER_AGENT" -v t_tz="$t_tz" '{
# Regex to parse log entry
match($0, /([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - \[([^\]]+)\] <([0-9]+):([0-9]+)> ([0-9]+) \(([^\)]+)\) "([^"]*)" \| ([^S]+) \| "([^"]*)"/, arr);
# Extract components of log entry
remote_addr = arr[1]
time_local = arr[2]
bytes_sent = arr[3]
request_length = arr[4]
status = arr[5]
host = arr[6]
request = arr[7]
http_referer = arr[8]
http_user_agent = arr[9]
# Split the request to method, path, and protocol (unreliable since some requests can have malformed `request` fields)
split(request, request_parts, " ")
method = request_parts[1]
path = request_parts[2]
protocol = request_parts[3]
# Split the full timestamp into separate date and time components
split(time_local, time_local_parts, " ")
timestamp = time_local_parts[1] # leave off +0000 UTC offset
split(timestamp, timestamp_parts, ":")
t_date = timestamp_parts[1]
t_hour = timestamp_parts[2]
t_min = timestamp_parts[3]
t_sec = timestamp_parts[4]
# Define ANSI color codes
# [Bold]
f_bold="\033[1m"
f_red_b="\033[1;31m"
f_green_b="\033[1;32m"
f_yellow_b="\033[1;93m"
f_blue_b="\033[1;36m"
f_magenta_b="\033[1;35m"
# [Regular]
f_darkgray="\033[90m"
f_reset="\033[m"
# Wrap HTTP status with an ANSI color code depending on the status class
if (status >= 500) status_color=f_yellow_b; # 500: Server error
else if (status >= 400) status_color=f_red_b; # 400: Client error
else if (status >= 300) status_color=f_blue_b; # 300: Redirect
else if (status >= 200) status_color=f_green_b; # 200: Success
else status_color=f_bold;
# Print formatted log entry
printf f_magenta_b "%15s" f_darkgray " @ " f_reset "%11s %s:%s:%s %s | " status_color "%3s" f_reset " | %-16s | \"%s\"\n",
remote_addr, t_date, t_hour, t_min, t_sec, t_tz, status, host, request;
# Print the user agent on a separate line, if `-u` option was set
if (OPT_USER_AGENT == "true") printf " " f_darkgray "(%s)" reset "\n", http_user_agent;
}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment