Skip to content

Instantly share code, notes, and snippets.

@Guiorgy
Last active December 31, 2025 14:09
Show Gist options
  • Select an option

  • Save Guiorgy/50b59724b07192b8f56c5b90dd73fb3a to your computer and use it in GitHub Desktop.

Select an option

Save Guiorgy/50b59724b07192b8f56c5b90dd73fb3a to your computer and use it in GitHub Desktop.
A wrapper around Systemd journalctl to print logs for the last n invocations of a unit
# ============================================================================= #
# Copyright © 2025 Guiorgy #
# #
# This program is free software: you can redistribute it and/or modify it under #
# the terms of the GNU General Public License as published by the Free Software #
# Foundation, either version 3 of the License, or (at your option) any later #
# version. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
# FOR A PARTICULAR PURPOSE. #
# #
# You can see the full GNU General Public License at #
# <https://www.gnu.org/licenses/> for more details. #
# ============================================================================= #
_logsd() {
# Load the journalctl completions if they aren't loaded yet
if ! builtin declare -F _journalctl &>/dev/null; then
if [[ -f /usr/share/bash-completion/completions/journalctl ]]; then
builtin source /usr/share/bash-completion/completions/journalctl
fi
fi
# Change the apparent command to 'journalctl --unit <input-to-complete>'
COMP_WORDS=(journalctl --unit "${COMP_WORDS[COMP_CWORD]}")
COMP_CWORD=2
# Delegate completion to journalctl
_journalctl
}
builtin complete -F _logsd logsd.sh
#!/usr/bin/env bash
# ============================================================================= #
# Copyright © 2025 Guiorgy #
# #
# This program is free software: you can redistribute it and/or modify it under #
# the terms of the GNU General Public License as published by the Free Software #
# Foundation, either version 3 of the License, or (at your option) any later #
# version. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
# FOR A PARTICULAR PURPOSE. #
# #
# You can see the full GNU General Public License at #
# <https://www.gnu.org/licenses/> for more details. #
# ============================================================================= #
# Check systemd version
if command -v systemctl &>/dev/null; then
SYSTEMD_VERSION=$(command systemctl --version | command head -n1 | command awk '{print $2}')
else
builtin echo "Error: systemd/systemctl not found on this system." >&2
builtin exit 1
fi
# Dependency Check
if ! command -v jq &>/dev/null; then
builtin echo "Error: 'jq' is required for this script. Install it with: sudo apt install -y jq" >&2
builtin exit 1
fi
# Print usage
usage() {
builtin local name="$(command basename "$0")"
command cat >&2 <<EOF
Print log entries from the systemd journal
Usage: $name [options] <unit-name> [count]
unit-name The name of the Systemd unit
count The number of invocations to print
Options:
-f, --follow Follow the journal
-h, --help Display this help message and exit
Examples:
'$name nginx 1' Current/Latest invocation only
'$name nginx 3' Everything from the last 3 invocation until now
'$name -f nginx' Follow current invocation
EOF
builtin exit 1
}
# Flags and options
FOLLOW=0
# Parse flags and options
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--follow)
FOLLOW=1
;;
-h|--help)
usage
;;
--)
builtin shift
builtin break
;;
-*)
builtin echo "Unrecognized option: '$1'" >&2
usage
;;
*)
builtin break
;;
esac
builtin shift
done
# Positional arguments
UNIT="$1"; builtin shift
COUNT="${1:-1}"; builtin shift
# Argument validation
if [[ $# -ne 0 ]]; then
builtin echo "Too many arguments: '$@'" >&2
usage
fi
if [[ -z "$UNIT" ]]; then
usage
fi
if [[ ! "$COUNT" =~ ^[0-9]+$ ]] || [[ "$COUNT" -le 0 ]]; then
builtin echo "Error: Count must be a positive integer" >&2
builtin exit 1
fi
# Get invocation IDs
INVOCATION_IDS=( \
$( \
command journalctl --unit="$UNIT" --output=json \
| command jq --raw-output '._SYSTEMD_INVOCATION_ID' \
| command grep --invert-match "null" \
| command uniq
) \
)
INVOCATION_COUNT="${#INVOCATION_IDS[@]}"
# Validate invocations and count
if [[ "$INVOCATION_COUNT" -eq 0 ]]; then
builtin echo "Warning: No log history found for unit: $UNIT"
builtin exit 0
elif [[ "$INVOCATION_COUNT" -lt "$COUNT" ]]; then
builtin echo "Warning: Only $INVOCATION_COUNT invocations found"
COUNT="$INVOCATION_COUNT"
fi
# Get the timestamp of the first invocation to display
START_INDEX=$(( TOTAL_FOUND - COUNT ))
TARGET_ID="${INVOCATION_IDS[$START_INDEX]}"
START_TIME="$(command journalctl _SYSTEMD_INVOCATION_ID="$TARGET_ID" --output=json --lines='+1' | command jq --raw-output '.__REALTIME_TIMESTAMP')"
# Print information
START_TIME_FORMATTED="$(command date --date="@$(( START_TIME / 1000000 ))" '+%Y-%m-%d %H:%M:%S')"
builtin echo "--> Displaying logs for unit [$UNIT]"
builtin echo "--> Since invocation start: $START_TIME_FORMATTED (Invocation $COUNT of $INVOCATION_COUNT)"
builtin echo "--------------------------------------------------------------------------------"
# Additional arguments
ARGS=''
if [[ "$FOLLOW" -eq 1 ]]; then
ARGS="$ARGS --follow"
fi
# Get logs
command journalctl $ARGS --unit="$UNIT" --since "$START_TIME_FORMATTED"
@Guiorgy
Copy link
Author

Guiorgy commented Dec 26, 2025

  • Move the logsd.sh file into /usr/local/bin/logsd.sh.
  • Rename and move the bash-completions.sh file into /usr/local/share/bash-completion/completions/logsd.sh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment