#!/usr/bin/env bash # Podgląd logów backendu LLM - jedno wejście niezależnie od tego, czy backend # działa w kontenerze (vllm-gpu) czy jako proces z plikiem logu (vllm-metal, ollama, mock). # # Użycie: # logs.sh --backend vllm-gpu [--tail 100] [--follow|--no-follow] [--grep WZORZEC] [--save PLIK] # # Domyślnie śledzi na bieżąco (jak `tail -f`). --no-follow robi jednorazowy zrzut, # co jest tym, czego się chce przy wklejaniu fragmentu do zgłoszenia. set -euo pipefail # Ctrl-C przy śledzeniu logów to normalne zakończenie, nie awaria - bez tego # go-task raportowałby "Failed to run task" za każdym razem, gdy ktoś przerwie podgląd. trap 'exit 0' INT TERM BACKEND="" RUN_DIR=".runs/llm" TAIL="100" FOLLOW="true" PATTERN="" SAVE="" COMPOSE_FILE="llm/docker-compose.yml" usage() { sed -n '2,12p' "$0" >&2; exit "${1:-1}"; } while [ $# -gt 0 ]; do case "$1" in --backend) BACKEND="${2:?}"; shift 2 ;; --run-dir) RUN_DIR="${2:?}"; shift 2 ;; --tail) TAIL="${2:?}"; shift 2 ;; --grep) PATTERN="${2:?}"; shift 2 ;; --save) SAVE="${2:?}"; shift 2 ;; --follow) FOLLOW="true"; shift ;; --no-follow) FOLLOW="false"; shift ;; -h|--help) usage 0 ;; *) echo "nieznany argument: $1" >&2; usage ;; esac done [ -n "$BACKEND" ] || { echo "podaj --backend" >&2; exit 1; } # Zapis do pliku bez sensu w trybie śledzenia - zrzut ma się skończyć. [ -n "$SAVE" ] && FOLLOW="false" log_file_for() { case "$1" in vllm-metal) echo "$RUN_DIR/vllm-metal.log" ;; ollama) echo "$RUN_DIR/ollama.log" ;; mock) echo "$RUN_DIR/mock.log" ;; *) echo "" ;; esac } # Strumień logów na stdout, zależnie od backendu. emit() { if [ "$BACKEND" = "vllm-gpu" ]; then command -v docker >/dev/null 2>&1 || { echo "brak dockera - a backend vllm-gpu działa w kontenerze" >&2; exit 1; } if [ "$FOLLOW" = "true" ]; then docker compose -f "$COMPOSE_FILE" --profile vllm-gpu logs --follow --tail "$TAIL" else docker compose -f "$COMPOSE_FILE" --profile vllm-gpu logs --tail "$TAIL" fi return fi local file file="$(log_file_for "$BACKEND")" if [ -z "$file" ]; then echo "backend '$BACKEND' nie ma znanego źródła logów" >&2 exit 1 fi if [ ! -f "$file" ]; then echo "brak pliku logu: $file" >&2 echo "backend prawdopodobnie nie był uruchamiany - zacznij od: task llm:up" >&2 exit 1 fi if [ "$FOLLOW" = "true" ]; then # --follow=name odtwarza plik po rotacji/nadpisaniu, np. przy restarcie backendu tail -n "$TAIL" -F "$file" else tail -n "$TAIL" "$file" fi } filter() { if [ -n "$PATTERN" ]; then # --line-buffered: przy śledzeniu linie mają się pojawiać od razu, a nie po zapełnieniu bufora grep --line-buffered -Ei "$PATTERN" || true else cat fi } if [ -n "$SAVE" ]; then mkdir -p "$(dirname "$SAVE")" emit | filter > "$SAVE" echo "zapisano $(wc -l < "$SAVE" | tr -d ' ') linii do $SAVE" elif [ "$FOLLOW" = "true" ]; then emit | filter else # Jednorazowy zrzut: pusty wynik przy filtrze jest dwuznaczny (brak dopasowań # czy brak logów?), więc mówimy to wprost zamiast milczeć. output="$(emit | filter)" if [ -z "$output" ] && [ -n "$PATTERN" ]; then echo "brak linii pasujących do '$PATTERN' w ostatnich $TAIL liniach logu" >&2 else printf '%s\n' "$output" fi fi