Skip to content

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.

Instrukcja instalacji jq


Tworzenie pliku skryptu

Tworzymy plik skryptu w lokalizacji /srv/config/scripts/:

micro /srv/config/scripts/logsize

Wklejamy treść skryptu:

logsize
#!/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.