logsize
Wdrożenie skryptu logsize
Skrypt logsize służy do szybkiej analizy zajętości przestrzeni dyskowej przez logi systemowe oraz logi Dockera. Umożliwia łatwe wykrycie największych katalogów i plików logów, w tym plików *-json.log Dockera.
Skrypt automatycznie:
- analizuje logi w
/var/log, - wykrywa największe logi Dockera wraz z nazwami kontenerów,
- pokazuje największe pliki logów w
/srv.
Wymagane rozszerzenie jq
Do poprawnego wyświetlania nazw kontenerów Dockera skrypt wykorzystuje narzędzie jq do parsowania plików config.v2.json.
Tworzenie pliku skryptu
Tworzymy plik skryptu w lokalizacji /srv/config/scripts/:
micro /srv/config/scripts/logsize
Wklejamy treść skryptu:
#!/bin/bash
# Display disk usage of system and Docker logs
# Requires: sudo, jq (docker optional)
set -euo pipefail
# ---------------- Colors ----------------
C0=$'\033[0m'; B=$'\033[1m'
CY=$'\033[36m'; M=$'\033[35m'
GR=$'\033[32m'; YL=$'\033[33m'
OR=$'\033[38;5;208m'
LR=$'\033[91m'
# ---------------- Args ----------------
# New usage (reversed):
# logauth -> TABLE mode, 10 lines
# logauth 15 -> TABLE mode, 15 lines
# logauth full -> FULL mode (4 sections), 15 lines
# logauth full 40 -> FULL mode, 40 lines
#
# 't' no longer exists.
MODE="table"
LIMIT="10"
if [[ "${1:-}" == "full" ]]; then
MODE="full"
LIMIT="${2:-15}"
elif [[ "${1:-}" =~ ^[0-9]+$ ]]; then
MODE="table"
LIMIT="$1"
fi
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || (( LIMIT < 1 )) || (( LIMIT > 500 )); then
echo "Invalid limit: $LIMIT (allowed 1..500)"
exit 2
fi
# ---------------- Ensure sudo ----------------
if sudo -n true 2>/dev/null; then
:
else
echo "Script requires sudo privileges to scan logs."
sudo -v
fi
printf "${CY}${B}=== %s ===${C0}\n\n" "$(date -Is)"
# ---------------- Helpers ----------------
hbytes() { # bytes -> human (B,K,M,G)
local b="$1"
awk -v b="$b" 'BEGIN{
if (b>=1024*1024*1024) printf "%.2fG", b/1024/1024/1024;
else if (b>=1024*1024) printf "%.2fM", b/1024/1024;
else if (b>=1024) printf "%.2fK", b/1024;
else printf "%dB", b;
}'
}
col_varlog_dir() { # bytes -> color
local b="$1"
if (( b >= 500*1024*1024 )); then printf "%s" "${LR}${B}"
elif (( b >= 250*1024*1024 )); then printf "%s" "${OR}${B}"
elif (( b >= 100*1024*1024 )); then printf "%s" "${YL}${B}"
else printf "%s" "${GR}${B}"
fi
}
col_file() { # bytes -> color (for var/log files, /srv logs, docker json)
local b="$1"
if (( b >= 100*1024*1024 )); then printf "%s" "${LR}${B}"
elif (( b >= 50*1024*1024 )); then printf "%s" "${OR}${B}"
elif (( b >= 25*1024*1024 )); then printf "%s" "${YL}${B}"
else printf "%s" "${GR}${B}"
fi
}
pad_fixed() { # string, width -> fixed width (trim/pad)
local s="$1" w="$2"
awk -v s="$s" -v w="$w" 'BEGIN{
if (length(s) > w) print substr(s,1,w);
else printf "%-*s", w, s;
}'
}
# ---------------- Data collectors ----------------
get_top_varlog_dirs() {
sudo du -x -B1 /var/log 2>/dev/null | sort -nr | head -n "$LIMIT"
}
get_top_varlog_files() {
sudo find /var/log -type f -printf '%s\t%p\n' 2>/dev/null | sort -nr | head -n "$LIMIT"
}
detect_docker_root() {
local root
root="$(sudo docker info --format '{{.DockerRootDir}}' 2>/dev/null || true)"
[[ -n "$root" ]] || root="/var/lib/docker"
echo "$root"
}
get_docker_containers_dir() {
local docker_root
docker_root="$(detect_docker_root)"
echo "$docker_root/containers"
}
get_top_docker_json() {
local containers_dir
containers_dir="$(get_docker_containers_dir)"
if sudo test -d "$containers_dir"; then
sudo find "$containers_dir" -type f -name "*-json.log" -printf '%s\t%p\n' 2>/dev/null \
| sort -nr | head -n "$LIMIT"
return 0
fi
return 1
}
get_container_name_for_log() {
local file="$1"
local dir name
dir="$(dirname "$file")"
name=""
if sudo test -f "$dir/config.v2.json"; then
name="$(sudo jq -r '.Name|ltrimstr("/")' "$dir/config.v2.json" 2>/dev/null || true)"
fi
[[ -n "$name" && "$name" != "null" ]] || name="UNKNOWN"
echo "$name"
}
get_top_srv_logs() {
if [[ -d "/srv" ]]; then
sudo find /srv -xdev -type f \( -name "*.log" -o -name "*.log.*" -o -name "*.gz" -o -name "*.old" -o -name "*.journal" -o -name "*-json.log" \) \
-printf '%s\t%p\n' 2>/dev/null | sort -nr | head -n "$LIMIT"
return 0
fi
return 1
}
# ---------------- FULL mode (4 sections) ----------------
render_full_mode() {
printf "${M}${B}[1/4 TOP directories in /var/log]${C0}\n"
get_top_varlog_dirs | awk -v C0="$C0" -v B="$B" -v GR="$GR" -v YL="$YL" -v OR="$OR" -v LR="$LR" '
function col(b){
if (b>=500*1024*1024) return LR B;
if (b>=250*1024*1024) return OR B;
if (b>=100*1024*1024) return YL B;
return GR B;
}
function h(b){
if (b>=1024*1024*1024) return sprintf("%.2fG", b/1024/1024/1024);
if (b>=1024*1024) return sprintf("%.2fM", b/1024/1024);
if (b>=1024) return sprintf("%.2fK", b/1024);
return b "B";
}
{ bytes=$1; $1=""; sub(/^ /,""); path=$0; printf "%s%-8s%s %s\n", col(bytes), h(bytes), C0, path; }
'
echo
printf "${M}${B}[2/4 TOP files in /var/log]${C0}\n"
get_top_varlog_files | awk -v C0="$C0" -v B="$B" -v GR="$GR" -v YL="$YL" -v OR="$OR" -v LR="$LR" '
function col(b){
if (b>=100*1024*1024) return LR B;
if (b>= 50*1024*1024) return OR B;
if (b>= 25*1024*1024) return YL B;
return GR B;
}
{ bytes=$1; printf "%s%8.2f MB%s %s\n", col(bytes), bytes/1024/1024, C0, $2 }
'
echo
printf "${M}${B}[3/4 TOP docker json logs + container name]${C0}\n"
if lines="$(get_top_docker_json 2>/dev/null)"; then
while IFS=$'\t' read -r bytes file; do
name="$(get_container_name_for_log "$file")"
C="$(col_file "$bytes")"
mb_size="$(awk -v b="$bytes" 'BEGIN{printf "%.2f", b/1024/1024}')"
fname="$(basename "$file")"
printf "%s%8s MB%s ${CY}%-25s${C0} %s\n" "$C" "$mb_size" "$C0" "$name" "$fname"
done <<< "$lines"
else
echo " (Docker containers dir not found or access denied)"
fi
echo
printf "${M}${B}[4/4 TOP logs in /srv]${C0}\n"
if lines="$(get_top_srv_logs 2>/dev/null)"; then
echo "$lines" | awk -v C0="$C0" -v B="$B" -v GR="$GR" -v YL="$YL" -v OR="$OR" -v LR="$LR" '
function col(b){
if (b>=100*1024*1024) return LR B;
if (b>= 50*1024*1024) return OR B;
if (b>= 25*1024*1024) return YL B;
return GR B;
}
{ bytes=$1; printf "%s%8.2f MB%s %s\n", col(bytes), bytes/1024/1024, C0, $2 }
'
else
echo " (Directory /srv not found or no matching files)"
fi
echo
}
# ---------------- TABLE mode ----------------
emit_col1() { # /var/log dirs -> only sizes
local W="$1"
get_top_varlog_dirs | awk '{print $1}' | head -n "$LIMIT" | while read -r bytes; do
local c h
c="$(col_varlog_dir "$bytes")"
h="$(hbytes "$bytes")"
printf "%s%s%s\n" "$c" "$(pad_fixed "$h" "$W")" "$C0"
done
}
emit_col2() { # /var/log files -> only sizes
local W="$1"
get_top_varlog_files | awk -F'\t' '{print $1}' | head -n "$LIMIT" | while read -r bytes; do
local c mb
c="$(col_file "$bytes")"
mb="$(awk -v b="$bytes" 'BEGIN{printf "%.2fMB", b/1024/1024}')"
printf "%s%s%s\n" "$c" "$(pad_fixed "$mb" "$W")" "$C0"
done
}
emit_col3() { # docker json -> size colored + container name in CY
local W="$1"
if lines="$(get_top_docker_json 2>/dev/null)"; then
while IFS=$'\t' read -r bytes file; do
local c mb name plain txt size_part_len size_part name_part
c="$(col_file "$bytes")"
mb="$(awk -v b="$bytes" 'BEGIN{printf "%.2fMB", b/1024/1024}')"
name="$(get_container_name_for_log "$file")"
plain="${mb} ${name}"
txt="$(pad_fixed "$plain" "$W")"
size_part_len=$(( ${#mb} + 1 ))
size_part="${txt:0:size_part_len}"
name_part="${txt:size_part_len}"
printf "%s%s%s%s%s\n" "$c" "$size_part" "${CY}" "$name_part" "$C0"
done <<< "$lines"
else
for _ in $(seq 1 "$LIMIT"); do
printf "%*s\n" "$W" ""
done
fi
}
emit_col4() { # /srv logs -> only sizes
local W="$1"
if lines="$(get_top_srv_logs 2>/dev/null)"; then
echo "$lines" | awk -F'\t' '{print $1}' | head -n "$LIMIT" | while read -r bytes; do
local c mb
c="$(col_file "$bytes")"
mb="$(awk -v b="$bytes" 'BEGIN{printf "%.2fMB", b/1024/1024}')"
printf "%s%s%s\n" "$c" "$(pad_fixed "$mb" "$W")" "$C0"
done
else
for _ in $(seq 1 "$LIMIT"); do
printf "%*s\n" "$W" ""
done
fi
}
render_table_mode() {
local W1=18 W2=18 W3=34 W4=18
printf "${M}${B}[TABLE]${C0} limit=${LIMIT}\n"
printf "${CY}${B}%-${W1}s %-${W2}s %-${W3}s %-${W4}s${C0}\n" \
"TOP dir /var/log" "TOP files /var/log" "TOP docker json" "TOP logs /srv"
printf "${CY}${B}%s${C0}\n" "$(printf '%*s' $((W1+2+W2+2+W3+2+W4)) '' | tr ' ' '-')"
mapfile -t col1 < <(emit_col1 "$W1")
mapfile -t col2 < <(emit_col2 "$W2")
mapfile -t col3 < <(emit_col3 "$W3")
mapfile -t col4 < <(emit_col4 "$W4")
local i
for ((i=0; i<LIMIT; i++)); do
printf "%s %s %s %s\n" \
"${col1[i]:-$(printf '%*s' "$W1" '')}" \
"${col2[i]:-$(printf '%*s' "$W2" '')}" \
"${col3[i]:-$(printf '%*s' "$W3" '')}" \
"${col4[i]:-$(printf '%*s' "$W4" '')}"
done
echo
}
# ---------------- Main ----------------
if [[ "$MODE" == "full" ]]; then
render_full_mode
else
render_table_mode
fi
Nadanie uprawnień
Nadajemy prawa do uruchamiania:
chmod +x /srv/config/scripts/logsize
Dodanie do PATH
Tworzymy link w /usr/local/bin, aby skrypt był dostępny jako polecenie systemowe:
sudo ln -sf /srv/config/scripts/logsize /usr/local/bin/logsize
Sprawdzamy dostępność polecenia:
command -v logsize
Użycie skryptu
Uruchamiamy skrypt ręcznie w terminalu. Domyślnie wyświetlana jest tabela (TOP) z 10 wierszami:
logsize
Zmiana liczby wierszy w trybie tabeli:
logsize 20
Dostępny jest drugi tryb pełny (4 rejestry TOP) – domyślnie po 15 wierszy:
logsize full
Zmiana liczby wierszy w trybie pełnym:
logsize full 30
Uwagi utrzymaniowe
- Skrypt przeznaczony jest do ręcznego użytku diagnostycznego.
- Nie należy uruchamiać go cyklicznie (np. z crona) bez modyfikacji wyjścia.
- W przypadku problemów z nazwami kontenerów należy zweryfikować instalację
jq.